这一篇介绍Rust的核心:泛型、trait和生命周期~
Option
会被转化为Option_i32
一样。也就是说,Rust的泛型更像C++的模板,不需要担心运行时的性能损耗。
语法表示泛型类型,其中T
可以代表任意数据类型。Option
泛型枚举,表示可能有值,也可能无值这一抽象概念:enum Option<T> {
Some(T),
None,
}
Some(T)
表示可能的值可以是任意类型T
。None
表示不存在任何值。Option
类型会被Rust自动引入,因此不需要显式引入作用域,直接使用Some(T)
和None
。fn function_name<T1, T2>(val1: T1, val2: &T2, val3: &[T2]) -> T1 {
// ...
}
let f1 = function_name(参数列表) // 根据参数列表中的类型自动推导
let f2 = function_name::<i32,i32>(参数列表) // 手动给出类型
为泛型列表,在声明了有哪些泛型以后即可在参数列表、返回值以及函数体中使用泛型。function_time::(参数列表)
的形式给出。struct StructName<T> {
x: T,
y: T,
}
let s1 = StructName{x: 10, y: 10}; // T被推导为i32
let s2 = StructName{x: 1.0, y: 1.0}; // T被推导为f32
let s2 = StructName::<f64>{x: 1.0, y: 1.0}; // 手动给出类型
Option
,Result
。enum EnumName<T> {
E1(T),
E2,
}
// 定义使用泛型的结构体
struct StructName<T> {
x: T,
y: T,
}
// 泛型实现:实现泛型方法
// 需要在impl关键字后面带上,然后在结构体名后也要带上
// impl的表示在impl声明的作用域内有哪些通配符
// 结构体名后面的表示指定结构体的泛型使用哪种类型实现
impl<T> StructName<T> {
fn method1(&self) -> &T { /* 具体实现 */ }
fn method2(&self, v: T) { /* 具体实现 */ }
}
// 具体实现:实现指定类型方法的方法
// 结构体后面的表示使用i32作为泛型结构体的具体实现
impl StructName<i32> {
fn method3(&self) -> i32 { /* 具体实现 */ }
fn method4(&self) -> i32 { /* 具体实现 */ }
}
fn main() {
let p = StructName{x:5,y:10};
println!("p.x={}",p.x);
println!("{}", p.method1());
println!("{}", p.method3());
}
impl
的作用于是impl
是整个实现块,包括结构体的部分,因此对于像impl StructName {}
这样的声明,实际上第一个T
是指定了实现块内的占位符T
,第二个T
(结构体的T
)是表示结构体使用impl
的T
作为其实现。let mut v1: Vec<i32> = vec![10, 20];
let mut v2 = Vec::<32>::new();
从语法上来说,trait可以包含两种形式的方法:
;
。// 一个trait例子
trait Geometry {
fn area(&self) -> f32; // 抽象方法
fn perimeter(&self) -> f32; // 抽象方法
fn get_msg(&self) -> str { // 具体方法
format!("{} - {}", self.area(), self.perimeter())
}
}
假如有一个类型MyType
,需要实现一个trait MyTrait
,则使用impl MyTrait for MyType
语法(表示MyType实现MyTrait特质)。在impl
块中,先使用trait
定义的方法签名,再在方法体内编写具体的行为。
struct MyType {
}
trait MyTrait {
fn method1(&self);
}
impl MyTrait for MyType {
fn method1(&self) { /* 具体实现 */ }
}
例子:
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.0
}
}
impl Trait
语法表示参数类型trait
对泛型参数进行约束impl Trait
:假如有一个特质MyTrait
,有一个函数的一个入参或出参为实现了MyTrait
的参数,则参数类型为impl MyTrait
,如果需要入参出参同时实现TraitA
和TraitB
,可以使用impl TraitA + TraitB
代表该参数的类型:fn function_name1(param: impl MyTrait) { // 接收实现了MyTrait的入参
// 函数体
}
fn function_name2(param: impl TraitA + TraitB) { // 接收实现了TraitA和TraitB的入参
// 函数体
}
fn function_name3() -> impl MyTrait { // 返回实现了MyTrait的入参
// 函数体
}
trait约束:指使用泛型时限制泛型类型必须实现哪一些trait。trait约束与泛型类型的参数声明在一起,适用于复杂的开发环境,语法如下:
fn function_name<T: TraitA + TraitB + TraitC>(t: T) {
// 函数体
}
fn function_name<T: TraitA + TraitB + TraitC>() -> T {
// 函数体
}
如果泛型参数有多个trait约束,那么拥有多个泛型参数的参数会导致函数名和参数列表之间会有很长的trait约束信息,使得函数签名可读性差,此时可以使用where
关键字处理这种情况:
fn function_name<T: TraitA + TraitB + TraitC>(t: T) {}
// 使用where关键字
fn function_name<T>(t: T)
where T: TraitA + TraitB + TraitC {
}
fn function_name<T>() -> T
where T: TraitA + TraitB + TraitC {
}
在使用泛型类型参数的impl块上使用约束,可以有条件地为实现了特定trait的类型实现方法。
struct StructName<T> {
// 结构体实现
}
impl<T: Display + PartialOrd> StructName<T> {
// 如果T是实现了Display和PartialOrd两种trait的类型,则这里实现的方法会生效
}
// 实现了TraitName1的类型同时也就实现了TraitName2的方法
impl<T: TraitName1> TraitName2 for T {
// 这里可以实现TraitName2中的方法
// 此时传入的self即有TraitName2的方法也有TraitName1的方法
}
// 为实现了Display的类型实现ToString trait
impl<T: Display> ToString for T {
// 跳过
}
trait可以被继承,语法如下:
trait B: A
表示trait B
继承了A
,即在B
中能使用A
中声明的方法。
Self
代表当前的类型,比如 File
类型实现了 Write
,那么实现Wirte
的逻辑过程中有可能需要用到当前类型File
,此时使用到的 Self
就指代 File
。self
在用作方法的第一个参数时,实际上是 self: Self
的简写,所以 &self
是 self: &Self
, 而 &mut self
是 self: &mut Self
。标准库中提供了很多有用的trait,其中一些trait可应用于结构体或枚举定义的derive
属性中。对于使用#[derive]
语法标记的类型,编译器会自动为其生成对应的trait的默认实现代码。
Debug
trait:
{:?}
或{:#?}
格式打印输出一个类型的实例;std::fmt::Debug
;#[derive(Debug)]
派生出相关代码;Display
trait:
{}
格式化输出一个值的内容,主要用于面向用户的输出,std::fmt::{Display, Result}
;Display
的fmt(&self, f: &mut Formatter<'_>) -> Result
方法;例子:
use std::fmt::{Display, Formatter, Result};
// Debug的例子
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
// Display的例子
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); // (0,0)
println!("{:?}", origin); // Point{ x: 0, y: 0 }
println!("{:#?}", origin); // Point {
// x : 0,
// y : 0,
// }
}
Eq与PartialEq的区别:
关系 | 中文名 | 对称性(a==b => b==a ) |
传递性(a==b, b==c => a==c ) |
反身性(a==a ) |
例子 |
---|---|---|---|---|---|
Eq |
等价关系 | √ | √ | √ | 整数 |
PartialEq |
局部等价关系 | √ | √ | × | 浮点数 |
PartialEq
trait:
#[derive(PartialEq)]
;PartialEq
的 fn eq(&self, other: &Self) -> bool
方法;Eq
trait:
PartialEq
,再实现#[derive(Eq)]
即可;例子:
// 实现Eq的例子
#[derive(PartialEq, Eq)]
enum BookFormat {
Paperback,
Hardback,
Ebook,
}
struct Book {
isbn: i32,
format: BookFormat,
}
// 实现 PartialEq 的例子
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);
}
次序关系说明:
a、a==b
、a>b
中其中一种;
a则!(a>b)
,反之亦然;
a a,==
和>
同理;
Ord与PartialOrd的区别:
关系 | 中文名 | 完全反对称性 | 反对称性 | 传递性 |
---|---|---|---|---|
Ord |
全序比较 | √ | × | √ |
PartialOrd |
偏序比较 | × | √ | √ |
Ord
trait:
use std::cmp::{Ord, Ordering};
,Ord
会被预导入,所以可以不用手动导入;#[derive(Ord)]
;Ord
的fn cmp(&self, other: &Self) -> Ordering;
方法;Ord
中有 fn max(self, other: Self) -> Self
和 fn min(self, other: Self) -> Self
等默认实现的方法方便比较操作;PartialOrd
trait:
use std::cmp::{PartialOrd, Ordering};
,PartialOrd
会被预导入,所以可以不用手动导入;#[derive(PartialOrd)]
;PartialOrd
的fn partial_cmp(&self, other: &Rhs) -> Option;
方法;PartialOrd
中有 fn lt(&self, other: &Rhs) -> bool
、fn le(&self, other: &Rhs) -> bool
、fn gt(&self, other: &Rhs) -> bool
、fn ge(&self, other: &Rhs) -> bool
等默认实现的方法方便比较操作;例子:
use std::cmp::Ordering;
#[derive(PartialEq, Eq)]
struct Person {
id: u32,
name: String,
height: u32,
}
impl Ord for Person {
fn cmp(&self, other: &Person) -> Ordering {
self.height.cmp(&other.height)
}
}
impl PartialOrd for Person {
fn partial_cmp(&self, other: &Person) -> Option<Ordering> {
Some(self.cmp(other))
}
}
fn main() {
let person1 = Person {id:1, name: String::from("zhangsan"), height: 168};
let person2 = Person {id:2, name: String::from("lisi"), height: 175};
let person3 = Person {id:3, name: String::from("wangwu"), height: 180};
assert_eq!(person1 < person2, true);
assert_eq!(person2 > person3, false);
assert_eq!(person1.lt(&person2), true);
assert_eq!(person3.gt(&person2), true);
let tallest_person = person1.max(person2).max(person3);
println!("id: {}, name: {}", tallest_person.id, tallest_person.name);
// id: 3, name: wangwu
}
Clone
trait:
Clone
可以使类型获得深复制的能力,即对栈上和堆上的数据一起复制;Clone
会被预导入,所以可以不用收到导入;#[derive(Clone)]
,要求结构体的每个字段或枚举的每一个值都可调用clone
方法,意味着所有字段或值的类型都必须实现Clone
;Clone
的fn clone(&self) -> Self;
方法;Copy
trait:
Copy
的类型具有按位复制其值的能力;Copy
会被预导入,所以可以不用收到导入;Copy
继承自Clone
,这意味着要实现Copy
的类型,必须实现Clone
的clone
方法。#[derive(Copy)]
Clone
的fn clone(&self) -> Self;
方法;例子:
#[derive(Copy, Clone)]
struct MyStruct {};
// 等价于
struct MyStruct;
impl Copy for MyStruct { }
impl Clone for MyStruct {
fn clone(&self) -> MyStruct {
*self
}
}
Copy
。Copy
,这个结构体才算实现了Copy
。Copy
是一个隐式行为。开发者不能重载Copy
行为,它永远是简单的位复制。Copy的隐式行为常发生在执行变量绑定、函数参数传递、函数返回等场景中。Clone
,开发者可以按需实现clone
方法。Copy
可以作为类型是值语义还是引用语义的依据。Copy
(会报错)。Default
会被预导入,所以可以不用手动导入;#[derive(Default)]
,要求结构体的每个字段或枚举都实现了Default
的fn default() -> Self;
方法;Default
的fn default() -> Self;
方法;例子:
#[derive(Default, Debug)]
struct MyStruct {
foo: i32,
bar: f32,
}
// 下面的代码等价于 #[derive(Default)]
/*
impl Default for MyStruct {
fn default() -> Self {
MyStruct {foo: 0, bar: 0.0}
}
}
*/
fn main() {
let options1: MyStruct = Default::default();
let options2: = MyStruct {foo: 7, ..Default::default()};
println!("options1: {:?}", options1); // options1: MyStruct {foo:0, bar:0.0}
println!("options1: {:?}", options2); // options2: MyStruct {foo:7, bar:0.0}
}
'
开头加上小写字母,如'a
读作“生命周期a
”。生命周期注解位于引用的&
操作符之后,并用一个空格将生命周期注解与引用类型分隔开,比如&'a i32
。'static
:Rust预定义了一种特殊的生命周期注解'static
,它具有和整个程序运行时相同的生命周期。'static
生命周期。let s: &'static str = "I have a static lifetime.";
在函数签名中使用生命周期注解,类似于声明泛型类型的语法:将生命周期声明在函数名和参数列表间的尖括号中。生命周期注解语法用于函数是为了将入参与返回值的生命周期形成某种关联,这样Rust就有足够的信息来保证内存安全,以防止产生悬垂引用等不安全的行为。
fn long_str<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
impl
关键字后面的尖括号中声明生命周期。例子:
struct Foo<'a, 'b> {
x: &'a i32,
y: &'b i32,
}
impl<'a, 'b> Foo<'a, 'b> {
fn get_x(&self) -> &i32 { self.x }
fn get_y(&self) -> &i32 { self.y }
fn max_x(&'a self, f: &'a Foo) -> &'a i32 {
if self.x > f.x {
self.x
} else {
f.x
}
}
}
fn main() {
let f1 = Foo {x: &3, y: &5};
let f2 = Foo {x: &7, y: &9};
println!("x: {}, y: {}", f1.get_x(), f1.get_y());
println!("max_x: {}", f1.max_x(&f2));
}
// x: 3, y: 5
// max_x: 7
fn foo(x: &i32, y: &i32)
等价于fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
。fn foo(x: &i32) -> &i32
等价于fn foo<'a>(x: &'a i32) -> &'a i32
。self
的生命周期会赋给所有被省略的输出生命周期。