文章目录
第五章:类型系统
Rust 是一门强类型且类型安全的静态语言,Rust 中一切皆类型,包括基本的原生类型和复合类型。
这里重点介绍 泛型 和 trait 系统
泛型
泛型是指在运行时指定数据类型的机制,优势是可以编写更为抽象和通用的代码,减少重复的工作量
表示为 泛型,,
泛型与容器
fn main() {
let mut vec_integer: Vec<i32> = vec![1,2];
vec_integer.push(3);
}
泛型与结构体
泛型类型的结构体是指
结构体的字段类型是泛型类型
,他可以拥有一个或多个泛型类型。
struct Rectangle1<T> {
width: T,
height: T,
}
struct Rectangle2<T, U> {
width: T,
height: U,
}
impl<T> Rectangle1<T> {
fn width(&self) -> &T {
&self.width
}
fn height(&self) -> &T {
&self.height
}
}
impl Rectangle1<i32> {
fn area(&self) -> i32 {
self.width * self.height
}
}
impl<T, U> Rectangle2<T, U> {
fn width(&self) -> &T {
&self.width
}
fn height(&self) -> &U {
&self.height
}
}
fn main() {
let rect1 = Rectangle1 { width: 8, height: 2 };
println!("rect1.width: {}", rect1.height);
println!(rect1.area());
let rect2 = Rectangle2 { width: 8, height: 2.2 };
print!(rect2.width);
}
泛型与枚举
泛型枚举是指枚举值类型是泛型类型。标准库提供的Option 就是一个应用广泛的泛型枚举
enum Option<T>{
Some(T),
None,
}
Option<T>
表示可能有值,也可能无值这一抽象概念,Some 表示可能的值可以使任意类型T,
None 表示不存在Option类型会被Rust自动引入,不需要再显示引用
fn option_add(x: Option<i32>, y: Option<i32>) -> Option<i32> {
return if x.is_none() && y.is_none() { None } else if x.is_none() { y } else if y.is_none() { x } else {
Some(x.unwrap() + y.unwrap()) // 如果值为None, 不能使用unwrap
};
}
泛型与函数
函数的参数和返回值都可以是泛型类型,带有泛型类型的参数或返回值的函数叫做泛型函数。
fn foo<T>(x: T) -> T {
return x;
}
fn main() {
println!("{}", foo(4));
println!("{}", foo("hello"))
}
泛型与方法
带有泛型类型的参数或返回值的方法叫作泛型方法
需要在impl后面跟着<T> 才可以在方法的参数或返回值使用泛型
impl<T> Rectange1<T>{
fn width(&self) -> &T{
&self.width
}
}
trait系统
Rust 中没有
类
和接口
这样的概念。trait 是 唯一的接口抽象方式,用于跨多个结构体以一种抽象的方式定义共享的行为(方法),即 trait 可以让不同的结构体实现相同的行为。
trait 定义与实现
trait 的本质 是一组方法原理,是实现某些目的的定位集合。
trait Geometry{
fn area(&self)-> f32;
fn perimeter(&sefl)->f32;
}
从语法来说,trait 可以包含两种形式的方法: 抽象方法和具体方法。
trait Geometry {
fn area(&self) -> f32;
fn perimeter(&self) -> f32;
}
struct Rectangle {
width: f32,
height: f32,
}
impl Geometry for Rectangle {
fn area(&self) -> f32 {
self.width * self.height
}
fn perimeter(&self) -> f32 {
(self.width + self.height) * 2
}
}
struct Circle {
radius: f32,
}
impl Geometry for Circle {
fn area(&self) -> f32 {
3.14 * self.radius * self.radius
}
fn perimeter(&self) -> f32 {
3.14 * 2.0 * self.radius
}
}
fn main() {
let rect = Rectangle { width: 10.0, height: 20.0 };
println!("rect : area :{}", rect.area())
}
trait 作为参数
trait 作为参数的两种常用方式:
- 使用impl Trait 语法表示参数类型
- 使用trait 对泛型参数进行约束
impl Trait
// 这里没有实现Geometry trait ,在调用print 函数,如果向其传递String或者i32 会报错
fn print(geometry: impl Geometry) {
println!("area : {}", geometry.area());
}
fn main() {
let rect = Rectangle { width: 10.0, height: 20.0 };
println!(rect)
}
下面是实现了Geometry trait 和 Display trait
use std::fmt::{Display, Formatter, Result};
impl Dispaly for Rectangle {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "Rectangle:({},{})", self.width, self.height)
}
}
fn print1(geometry: impl Geometry + Display) { // 表示同时实现 两种trait
println!("{},area:{}", geometry, geometry.area())
}
fn main() {
let rect = Rectangle { width: 10.0, height: 5.4 };
println!(rect)
}
impl 还支持多个参数指定类型,
fn area_add(geo1: impl Geometray,geo2:impl Geometry){
println!("{}",geo1)
}
trait 约束
约束是指使用trait 对泛型进行约束。
语法是:
fn generic<T:MyTrait + MyOtherTrait + SomeStandarTrait>(t:T){}
impl Display for Circle {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "Circle:({})", self.radius)
}
}
fn print2<T: Geometry + Display>(geometry: T) {
println!("{},area : {}", geometry, geometry.area())
}
fn main() {
let circle = Circle { radius: 3.0 };
print2(circle)
}
这里跟impl trait 相似的是,也是可以有多个trait 约束,
fn area_add<T:Geometry+Display +Clone,U:Geometry+Display+Debug>(geo1:T,geo2:U){}
在上述过于长的约束,会导致函数签名阅读性变差,,
fn area_add<T,U> (geo1:T,geo2:U) where T: Geometry+Display+Clone,U:Geometry+Dispaly+Debug{}
返回实现trait 的类型
函数的返回值类型也可以使用impl Trait 语法,返回某个实现了trait 的类型,
fn return_geomery() -> impl Geometry{
Rectangle{
width:12.5,
height: 5.5,
}
}
标准版常用trait
trait 可以应用于填结构体或枚举定义的derive属性中
例如: 对于#[derive]语法标记的类型,编译器会自动为其生成对应trait的默认实现代码。
格式化输出Debug 和Display
Debug trait 可以开启格式化字符串中的调试格式,常用于调试上下文中以
{:?}
或{:#?}
格式打印输出一个类型的实例。Display trait 是以
{}
格式打印输出信息的,主要用于面向用户的输出。但是Display不能与derive 属性一起使用。要实现Display 必须实现fmt方法
// 打印类型实例
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Display for Point {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "({},{})", self.x, self.y)
}
}
fn main() {
let origin = Point { x: 0, y: 0 };
println!("{}", origin);
println!("{:?}", origin);
println!("{:#}", origin)
}
(0,0)
Point (x:0,y:0)
Point {
x:0,
y:0
}
等值比较Eq与PartialEq
Eq 和 Partial 都是来自数学的等价关系和局部等价关系。两者都满足以下两个特性:
- 对称性,即 a==b 可推导出 b == a
- 传递性 , 即 a ==b 且 b c 可推导出 a c
eq
特殊: 反身性 : 即 a== a- 浮点数类型,两个非数字值 ,特殊 NaN != NaN>
PartialEq 可以通过判断结构体的参数是否相等,来判断实例是否相等。(这里书中并没有说地址,而是实例里面的字段)
enum BookFormat {
Paperback,
// Hardback,
Ebook,
}
// 新版rust 不再容许创建,而不使用的情况
#[allow(dead_code)]
struct Book {
isbn: i32,
format: BookFormat,
}
impl PartialEq for Book {
fn eq(&self, other: &Self) -> bool {
self.isbn == other.isbn
}
}
fn main() {
let b1 = Book { isbn: 3, format: BookFormat::Paperback };
let b2 = Book { isbn: 3, format: BookFormat::Ebook };
let b3 = Book { isbn: 5, format: BookFormat::Paperback };
assert!(b1 == b2);
assert!(b1 != b3);
}
次序比较Ord 与 PartialOrd
Ord 时表示 全序关系的trait ,全序关系 是指集合中任何一对元素都是相互可比较。以下特性:
- 完成反对称,即任何一对元素之间的关系只能是a<b, a == b 或 a > b 中的其中一种
- 传递性,即 a < b 且 b < c 可推导出 a <c , “==” 和 > 同理。
partialOrd 基于排序目的对类型实例进行比较的,可以直接使用 <,>,<= 和 >= 运算符进行比较。特性:
- 反对称性 a <b 则 !(a > b) ,反之亦然
- 传递性, 即 a <b 且 b < c 可推导出 a < c , == , > 同理
两者都要求能进行元素是否相等的比较,因此对Eq 和 PartialEq 有以下依赖要求:
- PartialOrd 要求类型实现 PartialEq
- Ord 要求类型实现 partialOrd 和 Eq
复制值 Clone 与 Copy
这里涉及 栈内存 和堆内存 ,按位复制和深复制以及引用概念
Clone trait 用于标记 可以对值进行深复制的类型,即对栈上和堆上的数据一起复制。
实现CLone,需要实现clone方法。如果使用 trait -》 #[derive(Clone)] 标记结构或者枚举,必须要求结构体每个字段或枚举都可调用clone 方法。换句话说 都要必须实现Clone 。
Copy trait 用于标记 按位复制其值的类型,即复制栈上的数据。且必须实现Clone 的 clone 方法。简化方法->
#[derive(Copy,Clone)]
等于
impl Copy for mystruct {}
impl Clone for myStruct {
fn clone () …
}
Copy 是一个隐式行为,开发者不能重载Copy 行为,就是一个简单的位复制。
常发生 在 执行变量绑定,函数参数传递,函数返回的场景中。
默认值Default
Default trait 为类型提供有用的默认值,通常用于结构体的字段提供默认值。如果每个字段都实现了Default ,那么Default 可以与 derive 属性一起使用,对每个字段都用默认值
#[derive(Default, Debug)]
struct MyStruct {
foo: i32,
bar: f32,
}
fn main() {
let options1: MyStruct = Default::default();
println!("options: {:?}", options1)
}
类型转换
类型转换 分为隐式转换 和显示类型转换 。
隐式类型转换有编译器来完成的
显示类型是有开发者来指定的。
原生类型间的转换
as 关键字 用于rust 中 原生数据类型间的转换,需要注意的是 , 短类型转换为长类型是没有的,但是长类型转换为短类型会被截断处理。有无符号,也不适合as关键字
这个与每个语言都这相似的规则
fn main() {
let x: u16 = 7;
let y = x as u32;
println!("u16 : {}, u32 : {}", x, y);
}
数字与String 类型间的转换
使用to_string 方法可以将任意数字转换为String 类型,使用 parse 方法可以将 String 类型解析为指定的数字类型
let x = 7;
let y = x.to_string();
let x1 = String::from("8");
let y1 = x1.parse::<f64>().unwrap();
&str 与 String 类型间转换
- 使用 as_str 方法可以将 String 类型转换为&str 类型
- 使用to_string 方法可以将 &str 转换为String 类型