泛型类型/函数/方法的泛型参数使用 <CamelCase, ...>
表示,泛型参数可以使用 trait 进行限界,没有任何限界时默认为 Sized。
// 泛型类型:T 为泛型类型名称,名称位于类型名后的 <xx> 中。
struct SGen<T>(T); // T 没有任何限界,默认为 Sized
struct Point<T> { x: T, y: T, }
// 未指定泛型参数值时,编译器自动推导
let p = Point{
x: "5".to_string(),
y : "hello".to_string()
};
struct Val<T> {
val: T,
}
// x 是 Val<f64> 类型
let x = Val{ val: 3.0 };
// y 是 Val<String> 类型
let y = Val{ val: "hello".to_string()};
// 3.0, hello
println!("{}, {}", x.value(), y.value());
// 泛型函数:类型参数位于函数名后的 <xx> 中。
fn generic<T>(_s: SGen<T>) {}
// 调用泛型方法时,使用比目鱼语法:method::<type>() 来指定泛型参数的具体类型。
generic::<char>(__);
// 为泛型类型实现关联函数或方法时,impl 后面需要列出泛型类型所需的所有泛型参数
//
// 函数和方法也可以定义自己的泛型参数, 它们不需要在 impl 后列出,而是在调用时推导或指定。
impl<T> Val<T> {
// 方法可以使用 impl 的泛型参数
fn value(&self) -> &T {
&self.val
}
// 方法可以有自己的泛型参数 O
fn add<O: Debug>(&self, other: O) {
println!("{:?}", other);
}
}
let x = Val { val: 3.0 };
let y = Val { val: "hello".to_string(), };
println!("{}, {}", x.value(), y.value());
x.add(123); // 推断
x.add::<i32>(123); // 为泛型方法手动指定类型,使用比目鱼语法。
// 为泛型参数指定具体的类型,相当于实例化出一个匿名的具体类型
fn gen_spec_t(_s: SGen<A>) {}
fn gen_spec_i32(_s: SGen<i32>) {}
在使用 trait 提供的方法或函数时,该 trait 必须先引入到当前 scope, 否则编译器不知道应该使用该类型实现的哪个 trait 的同名函数或方法。
std::prelude::v1 moudle
列出的 item 会被自动导入到所有 std Rust 程序中,如
std::convert::{AsRef, AsMut, Into, From} 等,所以在调用对象的
into()/from()/as_ref()/as_mut() 方法前不需要手动导入它们。
trait 支持关联类型, 这些类型在定义 trait 时一般是未知的,但在实现该 trait 时需要指定
具体类型(type alias 语法)
,trait 函数可以使用 Self::Item 来引用关联类型 Item。在定义 trait 时,可以为关联类型指定 缺省类型或用其它 trait 做限界
。
使用带关联类型的 trait 做限界时,使用 Trait<Item=Type>
语法来为该 trait 的关联类型指定一个具体类型,结果还是一个 trait,可以用它做限界或定义 trait object,例如:
trait Iterator {
// 声明关联类型时,可以对类型进行限界。
type Item: std::fmt::Display;
fn next(&mut self) -> Option<Self::Item>;
}
// 使用带关联类型的 trait 做限界时:
// 1.可以不指定关联类型,在使用时由编译器推导;
// 2.可以为关联类型做进一步限界
// 3.可以使用关联类型
fn collect_into_vec<I: Iterator>(iter: I) -> Vec<I::Item>
where I::Iterm: Debug {} // 对关联类型做限界
// Iterator<Item=u8> 还是一个 trait,故
// 1. 可以用作限界:
fn my_func<Iter: Iterator<Item=u8>>(iter: Iter) {}
// 2. 可以用作 trait object(需要将所有关联类型明确定义出来,否则编译时不能正确检查而报错)
fn dump(iter: &mut dyn Iterator<Item=String>) {}
struct EvenNumbers { count: usize, limit: usize, }
impl Iterator for EvenNumbers {
// 实现时必须为关联类型指定 `具体类型` ,该具体类型需要满足 trait 定义的限界要求。
type Item = usize;
// trait 的函数/方法,通过 Self::Item 引用关联类型
fn next(&mut self) -> Option<Self::Item> {
if self.count > self.limit {
return None;
}
let ret = self.count * 2;
self.count += 1;
Some(ret)
}
}
trait 支持关联常量,可以在定义 trait 时指定缺省值,或者在实现 trait 时指定常量值:
trait Float {
// 定义常量时必须指定类型(因编译器不会自动推导 const 值类型)
const ZERO: Self;
const ONE: Self;
}
trait Greet{
// 为关联常量指定缺省值
const GRETING: &'static str = "Hello",
fn greet(&self) -> String;
}
impl Float for f32 {
const ZERO: f32 = 0.0; // 实现 trait 时设置常量值
const ONE: f32 = 1.0;
}
使用 trait bound 的场景:
- 泛型参数: fn f<A: Copy>() {}
- super trait: trait Circle : Shape + Color {}
- trait 关联类型: trait A { type B: Copy; } 等效于 trait A where Self::B: Copy { type B;}
如果泛型参数未指定 trait bound,则默认为 Sized。
在函数传参匹配 trait bound 时不会自动隐式进行 type coercion 协变,例如,虽然 &mut i32
可以协变到 &i32, 但传参时不会协变。带来的影响是:如果 Trait 作为函数参数限界,&i32 和
&mut i32 两种类型都需要实现该 Trait
。这是由于 trait 的方法参数可能是 self 或
&mut self,如果这时传入 &T,就不能调用这些方法。
最佳实践:为了避免用户惊讶,在为自定义类型实现 Trait 时,一般为三种类型 T, &T, &mut T
都实现同一个 trait。 最好在 T 上实现 trait,这样内部可以使用 self 或 &mut self;
trait Trait {}
fn foo<X: Trait>(t: X) {}
impl<'a> Trait for &'a i32 {}
fn main() {
let t: &mut i32 = &mut 0;
foo(t); // 报错。
}
// error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
// --> src/main.rs:9:9
// |
// 3 | fn foo<X: Trait>(t: X) {}
// | ----- required by this bound in `foo`
// ...
// 9 | foo(t);
// | ^ the trait `Trait` is not implemented for `&mut i32`
// |
// = help: the following implementations were found:
// <&'a i32 as Trait>
// = note: `Trait` is implemented for `&i32`, but not for `&mut i32`
1 泛型 trait #
泛型 trait 是有泛型参数的 trait。trait 可以作为泛型参数的限界(bound)约束,也可以作为函数的参数或返回值(impl Trait 或 trait objecct):
- 匿名函数也可以作为类型参数的 bound 约束,例如函数指针 fn() -> u32 或闭包 Fn() -> u32,但后续只能传入 fn 而不能是闭包;
- 除了对泛型参数 T 直接限界外,也可以使用 where 对
使用 T 的其它类型进行限界
,如: Option<T>: Debug; - 做泛型限界的 trait 类型和数量没有限制。作为对比,定义 trait object 时的 trait 必须是
safe trait 列表
(一般一个自定义 trait 类型,再加上:Send, Sync, Unpin, UnwindSafe 和 RefUnwindSafe,不包含 ?Sized,Hash,Eq 等 trait)。
// 对 T 无限制,等效于 Sized 限界
struct Pair<T> { x: T, y: T,}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y, }
}
}
// 实现泛型 trait 时,可以对 T 添加额外的限制
impl<T: std::fmt::Debug + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {:?}", self.x);
} else {
println!("The largest member is y = {:?}", self.y);
}
}
}
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
// 实现泛型 trait 时,可以指定具体类型或者继续使用泛型参数。
impl Contains<i32, i32> for Container {
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
fn first(&self) -> i32 { self.0 }
fn last(&self) -> i32 { self.1 }
}
// 泛型参数可以使用多个 trait 或 lifetime 作为限界。
fn compare_prints<T: Debug + Display>(t: &T) {
println!("Debug: `{:?}`", t);
println!("Display: `{}`", t);
}
fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
println!("t: `{:?}`", t);
println!("u: `{:?}`", u);
}
// 对于复杂的、较长的限界,可以使用 where 来定义
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// 等效于
impl <A, D> MyTrait<A, D> for YourType
where
A: TraitB + TraitC,
D: TraitE + TraitF
{
//..
}
// 有些场景, 必须使用 where 来限界,比如这里限界的是 Option<T>
trait PrintInOption {
fn print_in_option(self);
}
impl<T> PrintInOption for T where Option<T>: Debug {
fn print_in_option(self) {
println!("{:?}", Some(self));
}
}
// 定义泛型 trait 时,可以为泛型参数指定缺省类型
pub trait Add<Rhs = Self> {
// 关联类型也可以指定缺省类型
type Output = Self;
// Required method
fn add(self, rhs: Rhs) -> Self::Output;
}
在定义泛型 trait 时, 泛型参数和关联类型都可以有缺省类型
, 例如各种运算符重载 trait
都指定了默认类型。
使用泛型 trait 时可以同时指定新的泛型类型参数+关联类型, 需要:按顺序指定泛型参数的具体类型,使用 K=V 来指定关联类型 。
例如使用 Add 进行限界时:
- Add<&int32, Output=int32>
- Add<Output=int32> (省略有缺省值的参数)
但不能使用 Add<Rhs=&T, Output=T>, 因为 Rhs 不是关联类型参数:
// 定义泛型 trait 时,可以为泛型参数指定缺省类型
pub trait Add<Rhs = Self> {
// 关联类型也可以指定缺省类型
type Output = Self;
// Required method
fn add(self, rhs: Rhs) -> Self::Output;
}
// 实现 trait 时,需要为关联类型指定具体类型
impl Add<Bar> for Foo {
type Output = FooBar;
fn add(self, _rhs: Bar) -> FooBar {
FooBar
}
}
// 定义一个泛型函数,使用 Add trait 作为泛型参数的限界,同时指定 Rhs 类型和关联类型。
// 不能使用 Rhs = Rhs, 否则 Rust 会报错没有 Rhs 关联类型。
fn add_values<T, Rhs>(a: T, b: Rhs) -> T where T: Add<Rhs, Output = T>,
{ a + b }
trait 也支持泛型常量参数(例如 <const N: usize>
,非关联常量),这样可以在泛型类型/函数/方法中来作为 Array 的长度。实例化常量参数时,可以使用 {XXX}
常量表达式:
struct ArrayPair<T, const N: usize> {
left: [T; N],
right: [T; N],
}
impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> {
// ...
}
// 实例化常量参数
fn foo<const N: usize>() {}
fn bar<T, const M: usize>() {
foo::<M>();
foo::<2021>();
// OK:使用 <{xx}> 常量表达式
foo::<{20 * 100 + 20 * 10 + 1}>();
// Error: 常量表达式不支持泛型常量运算
foo::<{ M + 1 }>();
// Error: 常量表达式不支持泛型参数
foo::<{ std::mem::size_of::<T>() }>();
let _: [u8; M]; // OK
// Error: const expression contains the generic parameter `T`
let _: [u8; std::mem::size_of::<T>()];
}
2 impl 方法和宏 #
impl<T> 场景:
- 定义泛型类型的方法或关联函数:impl<T> MyStruct<T> {}
- 为类型定义泛型 trait 的实现: impl<T> MyTrait<T> for MyStruct<T> {}
impl<T> 指定 trait 或类型使用的泛型参数,甚至整个类型都可以是泛型参数(blanket implement),如 impl<T, U> MyTrait<T> for U {};
如果明确指定了泛型 trait 的类型参数, 则 impl 该 trait 时可以不指定泛型参数:
struct GenericVal<T>(T);
// 对于泛型类型,在 impl 时
// 1. 如果指定了泛型参数类型,则不需要为 imp 指定泛型参数
impl GenericVal<f32> {}
// 2. 未指定具体类型时,必须在 impl<T> 中指定 GenericVal 的所有泛型参数
impl<T> GenericVal<T> {}
fn main() {
// 创建泛型类型的值时,可以不指定泛型参数 T,由编译器自动推导
let y = GenVal { gen_val: 3i32 };
println!("{}, {}", x.value(), y.value());
}
struct Container(i32, i32);
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
// 不需要使用 impl<i32,i32>
impl Contains<i32, i32> for Container {
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
fn first(&self) -> i32 { self.0 }
fn last(&self) -> i32 { self.1 }
}
一般情况下,为 trait 添加函数或方法时或为 type 实现 trait 时,trait 或 type 必须是本
crate 定义的类型,这个限制称为 Orphan Rule
,这是为了避免破坏开发者原来的定义。
对于泛型 trait 则放宽了这个条件限制:可以为其它 crate package 中定义的 trait 添加方法,或为 type 实现 trait ,这种实现称为 blanket impl
。这样可以批量对已知或未知的类型实现
trait。例如为任意 T 类型实现 Borrow<T> trait:
// 泛型 trait
trait DoubleDrop<T> {
fn double_drop(self, _: T);
}
// 为 U 实现泛型 trait,U 本身也是泛型参数,所以相当于为所有 Sized 类型实现了
// DoubleDrop trait。
impl<T, U> DoubleDrop<T> for U {
fn double_drop(self, _: T) {}
}
// 为所有 &T 或 &mut T 实现 Deref<Target = T> trait:
// https://doc.rust-lang.org/src/core/ops/deref.rs.html#84
impl<T: ?Sized> Deref for &T {
type Target = T;
#[rustc_diagnostic_item = "noop_method_deref"]
fn deref(&self) -> &T {
*self
}
}
impl<T: ?Sized> Deref for &mut T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
Rust 不会自动通过 Deref trait 解引用来满足泛型参数限界的要求:
use std::ops::{Deref, DerefMut};
impl<T> Deref for Selector<T> {
type Target = T;
fn deref(&self) -> &T { &self.elements[self.current] }
}
impl<T> DerefMut for Selector<T> {
fn deref_mut(&mut self) -> &mut T { &mut self.elements[self.current] }
}
// OK: 函数传参时, Rust 会自动隐式解引用, &Selector -> &str
let s = Selector { elements: vec!["good", "bad", "ugly"], current: 2 };
fn show_it(thing: &str) { println!("{}", thing); }
show_it(&s);
// Error: 使用 Trait 作为泛型参数的限界时, Rust 不会自动解引用到 &str
// 类型来满足类型限界的要求
use std::fmt::Display;
fn show_it_generic<T: Display>(thing: T) { println!("{}", thing); }
show_it_generic(&s);
// 两种解决办法:
show_it_generic(&s as &str); // 使用 as 类型转换;
show_it_generic(&*s) // 手动解引用 &*V 来得到 &str 类型
3 super trait #
trait 可以有多个父 trait(称为 super trait),类型在实现该 trait 的同时也必须实现这些父 trait,supertrait 也支持 lifetime bound。
trait Person {
fn name(&self) -> String;
}
// 等效于 trait Student where Self: Person
trait Student: Person {
fn university(&self) -> String;
}
trait Programmer {
fn fav_language(&self) -> String;
}
// 使用 + 运算符指定多个父 trait:
trait CompSciStudent: Programmer + Student {
fn git_username(&self) -> String;
}
// 为父 trait 指定 'lifetime
trait Circle<'a> : Shape + 'a {
fn radius(&self) -> f64;
}
4 完全限定方法调用 #
类型可以实现不同 crate package 定义的同名 trait,它们可以有相同或不同的方法,所以在 Vec<u8> 上调用 trait 定义的方法时,必须先将对应的 trait 引入到作用域,否则 Rust 不知道该调用哪一个 trait 的方法实现。
位于 std::prelude::v1 module 中的 trait,如 From/Into/Cone/Iterator,会被自动导入到所有 std 程序中,故不需要手动导入。
// write_all() 方法是 std::io::Write trait 定义的,调用它前需要引入 Write trait。
use std::io::Write
let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?;
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
fn main() {
let person = Human;
// 调用 Human 自身实现的 fly() 方法。
person.fly();
// 调用实现的 trait 的方法,这里为了避免歧义,使用的是完全限定方法调用或
// TraitName::method(object).
Pilot::fly(&person); // 等效于:<Human as Pilot>::fly(&person)
Wizard::fly(&person);
}
Pilot::fly(&person)
调用方式不适合没有 self 参数的关联函数:调用 trait 关联函数时,
Rust 不能推导出调用哪一个类型实现的该关联函数,这时需要使用完全限定方法调用。
完全限定方法调用: Rust 完全限定语法(Fully Qualified Syntax)明确指定了要调用哪个 trait 的方法,可以解决命名冲突或混淆的问题。
trait CalSum {
fn sum(&self, a: i32) -> i32;
}
trait CalSumV2 {
fn sum(&self, a: i32) -> i32;
}
#[derive(Clone, Copy, Debug)]
struct S1(i32);
impl CalSum for S1 {
fn sum(&self, a: i32) -> i32 {
self.0 + a
}
}
impl CalSumV2 for S1 {
fn sum(&self, a: i32) -> i32 {
self.0 + a
}
}
fn main() {
let s1 = S1(0);
// 调用指定 trait 的方法
println!("Results: {}", CalSum::sum(&s1, 1));
println!("Results: {}", CalSumV2::sum(&s1, 1));
// ERROR: S1 同时实现了 CalSum 和 CalSumV2, 它们都提供了 sum() 方法,
// Rust 不确定该调用哪一个。
//println!("Results: {}", s1.sum(1)); // Error: multiple `sum` found
// 使用完全限定方法调用:调用 S1 实现的 CalSum 的 sum 方法。
println!("Results: {}", <S1 as CalSum>::sum(&s1, 1)); // OK
}
在没有歧义的情况下, 可以直接调用对象实现的 trait 的方法, 而不需要使用完全限定调用的方式:
let i: i8 = Default::default();
let (x, y): (Option<String>, f64) = Default::default();
let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();
#[derive(Default)]
struct SomeOptions {
foo: i32,
bar: f32,
}
fn main() {
// 如果 SomeOptions 没有实现除 Default 外的其它 trait 的 default() 方法,
// 则 <SomeOptions as Default>::default() 可以简写为 Default::default().
let options: SomeOptions = Default::default();
let options = SomeOptions { foo: 42, ..Default::default() };
}
5 trait object #
参考:https://quinedot.github.io/rust-learning/dyn-trait.html
dyn TraitName
称为 trait object,它是一个类型,表示实现 TraitName 的任意类型对象。
trait object 和泛型参数的差别:
- 对于 trait bound 的泛型参数,Rust 编译器推断并绑定一种实际具体类型。如 Vec<T: Display> , 只能保存一种实现 Display trait 的类型元素;
- 对于 trait object,不和某一种类型绑定,而是在运行时来根据调用方法的对象指针(类型)来查找编译期间构造的 vtab 来调用对应对象的方法。所以
Vec<&dyn Display>
是可以保存各种实现了Display trait
的多种类型对象;
trait object,如 dyn Trait + Send + 'static
限制:
- 除了第一个 trait 外,其它 trait 只能是 auto trait ,包括:Send, Sync, Unpin, UnwindSafe 和 RefUnwindSafe。不包含 ?Sized,Hash,Eq 等 trait。
- 最多指定一个 lifetime;
- trait 路径可以用括号括起来,适用于复杂的定义场景;
// Rust 2021 前版本 dyn 关键字是可选的,2021 版本开始 dyn 关键字是必选的。
dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static
dyn Trait + Send + 'static
dyn Trait + Send + Sync + 'static
dyn (Trait)
dyn Trait + 'a // 指定 lifetime
dyn eviction::EvictionManager + Sync // 使用 path 来完整指定 trait
// 使用 & 时必须将 dyn 和后续的限制用括号括起来,否则会因歧义而报错。
&(dyn MyTrait + Send)
支持 trait object 的 trait 必须是对象安全(object safe)的,需要满足如下要求:
- Trait 方法不返回 Self,因为 trait object 是运行时派发,编译时不能对确定 Self 类型,故不能做检查;
- Trait 方法没有任何泛型参数。
这是由于 trait object 在编译时是不知道自己具体类型的,而是在运行时动态派发,所以在编译器时,如果返回 Self,则编译器不能正确检查返回值合法性。同理,由于在运行时才能确定 trait object 的具体对象类型,所以在编译阶段是不能确认泛型参数的对应的实现类型,所以编译器也不能正确检查。
- 解决办法:
fn splice(&self, other: &Self) -> Self
修正为fn splice(&self, other: &dyn MyTrait) -> Box<dyn MyTrait>
由于在编译时类型和大小不确定, 所以一般使用 &dyn TraitName
或 Box<dyn TraitName>
来表示, 它们都是胖指针,包含两个字段:指向动态对象内存的指针 + 该对象实现 trait 定义的各种方法的虚表(vtable) 的指针。
&dyn TraitName
使用实现 TraitName 的对象引用赋值:let n: &dyn TraitName = &value
;Box<dyn TraitName>
使用 Box::new(value) 赋值,不能直接将 value 赋值给 Box<dyn TraintName>:let b: Box<dyn TraitName> = Box::new(value)
,
使用 &dyn Trait
时,如果要指定 Send/lifetime 等,则需要使用
&mut (dyn Trait + Send + 'a) 格式
,否则 Rust 报错加号有歧义。
// error: expected expression, found keyword `dyn`
let b = &'a dyn MyTrait + Send + 'static;
// 表达式不能指定 'lifetime, 'lifetime 只能用于函数或方法的签名中。
// error: borrow expressions cannot be annotated with lifetimes
// let b = &'a (dyn MyTrait + Send + 'static);
// error: borrow expressions cannot be annotated with lifetimes
// printf_hello(Some(&'static my_struct));
当参数类型是 &dyn Trait 时,需要传入实现该 Trait 的借用类型值,如 MyStruct 实现了
MyTrait, 则需要使用 &dyn Trait
的地方需要传入 &MyStruct
值。
当参数类型是 &Box<dyn Trait>
时,不能传入 &Box::new(my_struct)
,Rust
报错类型不匹配,两个解决办法:
- 将参数类型修改为
Box<dyn Trait>
, 后续可以直接传入Box::new(my_struct)
; - 先创建一个
Box<dyn Triat>
的对象 b,然后再通过&b
赋值;
trait MyTrait {
fn say_hello(&self);
}
struct MyStruct(String);
impl MyTrait for MyStruct {
fn say_hello(&self) {
println!("hello {}", self.0);
}
}
fn printf_hello(say_hello: Option<&(dyn MyTrait + Send)>) {
// my_trait 是 &(dyn MyTrait + Send) 类型
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
fn printf_hellov2(say_hello: Option<&Box<dyn MyTrait + Send + 'static>>) {
// 错误:不能通过借用(共享或可变)来转移对象
// error[E0507]: cannot move out of `*say_hello` as enum variant `Some`
// which is behind a shared reference
// my_trait 是 Box<dyn MyTrait + Send + 'static> 类型,转移了 Box 对象所有权
// if let Some(&my_trait) = say_hello {
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
fn printf_hellov3(say_hello: Option<Box<dyn MyTrait + Send + 'static>>) {
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
fn main() {
let my_struct = MyStruct("abc".to_string());
// expected `&dyn MyTrait`, found `MyStruct`
// printf_hello(Some(my_struct));
// OK
printf_hello(Some(&my_struct));
// error[E0308]: mismatched types,expected `&Box<dyn MyTrait + Send>`,
// found `&Box<MyStruct>`
// printf_hellov2(Some(&Box::new(my_struct)));
// 解决办法:先创建一个 Box 对象,然后再借用。
// 赋值时,通过 unsized type coercion 隐式自动类型转换。
let st1: Box<dyn MyTrait + Send + 'static> = Box::new(my_struct);
printf_hellov2(Some(&st1));
let my_struct2 = MyStruct("def".to_string());
printf_hellov3(Some(Box::new(my_struct2)));
}
使用 Box 保存 trait object 时, 有三种标准格式, Rust 标准库为这三种格式定义了对应的方法和函数:
Box<dyn TraitName + 'static>
Box<dyn TraitName + Send + 'static>
Box<dyn TraitName + Sync + Send + 'static>
Fn/FnMut/FnOnce 也是 trait,故上面的 TraitName 可以是闭包函数,如:
Box<dyn Fn(int32) -> int32>
struct Sheep {}
struct Cow {}
trait Animal {
fn noise(&self) -> &'static str;
}
impl Animal for Sheep {
fn noise(&self) -> &'static str {
"baaaaah!"
}
}
impl Animal for Cow {
fn noise(&self) -> &'static str {
"moooooo!"
}
}
// 返回一个实现 Animal trait 的 Box 对象,编译期间不感知具体的实现类型
fn random_animal(random_number: f64) -> Box<dyn Animal> {
if random_number < 0.5 {
Box::new(Sheep {})
} else {
Box::new(Cow {})
}
}
创建 Box<dyn MyTrait>
时,不能使用 Box::<dyn MyTrait>::new(value)
,因为
Box new()
方法是定义在 impl <T> Box<T>
上的, T 没有任何 trait bound, 所以 T
默认是 Sized, 而 dyn MyTrait 是 un-sized 的, 所以编译报错。
解决办法:在返回值中指定 Box::<dyn MyTrait> 类型, 让 Rust 编译器自动转换。
let val: Box<dyn Any + 'a> = Box::new(*val); // OK
// function or associated item cannot be called on `Box<dyn Any>` due to unsatisfied trait bounds,
// 115 | pub trait Any: 'static {
// | ---------------------- doesn't satisfy `dyn std::any::Any: std::marker::Sized`
// let val: Box<dyn Any + 'a> = Box::<dyn Any + 'a>::new(*val);
// Box::<&dyn Any>::new() 是 OK 的, 因为 &dyn Any 是 Sized 对象.
// let val: Box<&dyn Any> = Box::<&dyn Any>::new(&val);
5.1 Default trait object lifetime #
dyn Trait 的 lifetime 规则:
Box<dyn Trait>
等效于Box<dyn Trait + 'static>
,因为 Box 接收了 trait object 的所有权,所以具有 ‘static 生命周期;&'x Box<dyn Trait>
等效于&'x Box<dyn Trait + 'static>
;&'a dyn Foo
等效于&'a (dyn Foo + 'a)
,因为 &‘a T 隐式声明了T: 'a
,&'a (dyn Foo+'a)
表示 dyn Foo 的 trait object 的 lifetime 要比 ‘a 长,同时它的引用也要比 ‘a 长。&'r Ref<'q, dyn Trait>
等效于&'r Ref<'q, dyn Trait+'q>
;
理解 Box<dyn Trait>
等效于 Box<dyn Trait + 'static>
:
- 使用 Box::new(value) 来赋值, 其中 value 的类型实现了 Trait。value 所有权被转移到 Box 中, Box 可以随意决定 value 的生命周期, 相当于具有了 ‘static 语义;
- 当将一个对象被转移给函数时,该对象满足函数的 ‘static 要求(因为转移给函数后,函数可以自由决定对象的生命周期)。如果对象有借用,则不能转移给函数;
let Box<dyn Trait> = Box::new(value);
// Parent 拥有 Box 的所有权,进而拥有 trait object 所有权。
//
// 当 Parent 对象被 drop 时,child 的 Box 的 trait object 也会被 drop
struct Parent {
child: Box<dyn OtherTrait + 'static>,
}
trait App {}
struct AppRegistry {
apps: HashMap<String, Box<dyn App>>,
}
impl AppRegistry {
fn new() -> AppRegistry {
Self { apps: HashMap::new() }
}
// impl App 必须加加 'static,否则 Box::new(app) 报错。
fn add(&mut self, name: &str, app: impl App + 'static) {
self.apps.insert(name.to_string(), Box::new(app));
}
}
struct MyApp {}
impl App for MyApp {}
fn main() {
let mut registry = AppRegistry::new();
// paas by value 传递对象,add() 拥有这个对象,所以满足 'static
registry.add("thing", MyApp {});
println!("apps: {}", registry.apps.len());
}
两个 dyn trait 的 trait、lifetime 如果相同的话,则类型互为别名,例如:
dyn Trait + Send + UnwindSafe
和 dyn Trait + UnwindSafe + Send
相同。
trait Foo { }
// 下面两个类型等效
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;
// 这两个 impl 也是等效(dyn 是可选的)
impl dyn Foo {}
impl dyn Foo + 'static {}
// 下面两个类型等效,因为 &'a T 意味着 T: 'a
// 类型别名支持泛型参数
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);
// 下面两个相同,因为 std::cell::Ref<'a, T> 内部要求 T: 'a
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
// T 的 lifetime 需要同时满足 'a + 'b, 所以应该比 'a 和 'b 都长
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
f1: &'a i32,
f2: &'b i32,
f3: T,
}
// Error: the lifetime bound for this object type cannot be deduced from context
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
// trait object 只能指定一个 'lifetime
//
// error[E0226]: only a single explicit lifetime bound is permitted
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo + 'a + 'b>;
// OK
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo + 'a>;
// OK
type T7<'a, 'b, 'c> = TwoBounds<'a, 'b, dyn Foo + 'c>;
// 表示实现 Bar trait 的类型的对象 lifetime 不能长于 'a
trait Bar<'a>: 'a { }
// 下面两种类型相同
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// 下面两种类型相同
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
https://doc.rust-lang.org/reference/lifetime-elision.html
The assumed lifetime of references held by a trait object is called its
default object lifetime bound
. These were defined in RFC 599 and amended in
RFC 1156.
如果 trait object 作为 trait bound 且没有指定 lifetime bound, 则使用这里说明的 default trait object lifetime bound。如果为 trait object 指定 ‘_ liftime bound,则使用 lifetime elide rule 来自动添加 lifetime。
If the trait object is used as a type argument of a generic type then
the containing type is first used to try to infer a bound
.
- If there is
a unique bound
from the containing type then that is the default - If there is more than one bound from the containing type then an explicit
bound
must be specified
If neither of those rules apply, then the bounds on the trait are used
:
- If the trait is defined with
a single lifetime bound
then that bound is used. - If ‘static is used for any lifetime bound then
'static
is used. - If the trait has no lifetime bounds, then the lifetime is
inferred in expressions and is 'static outside of expressions
.
trait Foo { }
// 下面这些 T1 到 T7 都是将 trait object 用作 type argument, 所以先检查对应
// containing type 的 lifetime 来推导 trait object 的 lifetime。主要是 3 条规则:
//
// 1. 如果 containing type 没有 lifetime,如 T1/T2 则使用 'static;
// 2. 如果 containing type 只指定了一个 lifetime, 则使用对应的 lifetime,如 T3/T4/T5/T6;
// 3. 如果 containing type 有多个 lifetime,则必须为 trit object 明确指定 liftime,如 T7;
// These two are the same because Box<T> has no lifetime bound on T
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;
// ...and so are these:
impl dyn Foo {}
impl dyn Foo + 'static {}
// ...so are these, because &'a T requires T: 'a
// 首先检查 containing type 是否有 lifetime,如果有的话且唯一,则使用它。
// 这里的 containing type 指的是 &'a dyn Foo 中的 &'a.
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);
// std::cell::Ref<'a, T> also requires T: 'a, so these are the same
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
// This is an example of an error.
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
f1: &'a i32,
f2: &'b i32,
f3: T,
}
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
// ^^^^^^^
// Error: the lifetime bound for this object type cannot be deduced from context
// 如果 trait object 不是作为 type argument,而是用于赋值,则使用对应目标类型的 trait
// bound 来决定 trait object 的 lifetime:
//
// 1. 如果目标 trait 只有一个 lifetime, 则使用它;
// 2. 如果目标没有 lifetime,则使用 'static 或从 expression 推导;
Note that the innermost object sets the bound, so &'a Box<dyn Foo>
is
still &'a Box<dyn Foo + 'static>
.
// For the following trait...
trait Bar<'a>: 'a { }
// ...these two are the same:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// ...and so are these:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
6 impl trait #
impl TraitName 或 &impl TraintName
和泛型类型参数类似, 但在编译时实例化为一种特定类型,
不支持运行时动态派发。
impl Traitname
的主要使用场景是简化泛型参数约束的复杂性,如函数返回一个复杂的多种嵌套迭代器时,该类型可能只有编译器才能准确写出来,这时可以用 impl TraitName 来简化返回参数类型。例如将 iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>>
简化为
impl Iterator<Item=i32>
。
impl trait 可以作为函数输入参数和返回值类型,但是不能作为泛型参数的 bound,因为 impl trait 被编译时实例化为具体类型而不是 trait,所以不用用于泛型参数限界。
// impl trait 作为函数参数的类型时,不需要引入泛型函数。
fn print_it1(input: impl Debug + 'static ) {
println!( "'static value passed in is: {:?}", input );
}
// 等效为泛型参数版本,这个版本的优势是实例化时可以指定 T 的类型,但是 impl Debug 不行。
fn print_it<T: Debug + 'static>(input: T) {
println!( "'static value passed in is: {:?}", input );
}
use std::iter;
use std::vec::IntoIter;
fn combine_vecs_explicit_return_type(v: Vec<i32>, u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
v.into_iter().chain(u.into_iter()).cycle()
}
// 等效为
fn combine_vecs(v: Vec<i32>, u: Vec<i32>, ) -> impl Iterator<Item=i32> {
v.into_iter().chain(u.into_iter()).cycle()
}
由于 impl Trait 是一种编译时静态派发形式,所以在编译时只能被单态化为一种类型,否则报错:
// 错误:不兼容的类型
fn make_shape(shape: &str) -> impl Shape{
match shape {
"circle" => Circle::new(),
"tiangle" => Triangle::new(),
"shape" => Retangle::new(),
}
}
由于闭包 Fn 也是 trait,所以函数可以返回 Box<dyn Fn() -> i32> 或 impl Fn() -> i32:
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
let closure = move |x: i32| { x + y };
closure
}
fn main() {
let plus_one = make_adder_function(1);
assert_eq!(plus_one(2), 3);
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1) // 堆分配空间,性能差些
}
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1 // 栈分配,性能好
}
7 Sized/?Sized #
Rust 为所有固定大小类型都自动实现了 std::marker::Sized trait
, 用户不能自己为类型实现该
trait,该 trait 是一个标记 trait。它的唯一用途是作为泛型类型的限界,如 T: Sized
表示 T
类型在编译时必须为大小已知(固定)的类型。
Sized 是 Rust 的隐式默认值,即如果写 struct S<T> 的 T 未加任何限界, 则 Rust 解释为 struct S<T: Sized>, 如果不想以这种方式约束 T 则必须显示的指出 struct S<T: ?Sized>, 表示 T 可以是无固定大小的类型。
Rust 的动态大小(unsized)类型:
- str;
- [T];
- dyn trait;
对于上面的无固定大小的类型,Rust 不能将它们作为变量或函数参数来传递,而是需要转换为固定大小的引用或 Box(它们都是 2 个 usize 的胖指针),如:
- &str, Box<str>;
- &[T], Box<[T]>;
- &dyn trait, Box<dyn trait>;
8 PhantomData #
PhantomData 是一个 zero-sized 泛型 struct, 唯一的一个值是 PhantomData,主要的用处是规避一些特殊场景的编译时检查报错:
pub struct PhantomData<T> where T: ?Sized;
使用举例:
-
lifetime:
// Error: 没有 field 使用 'a struct Slice<'a, T> { start: *const T, end: *const T, } // OK: 用 0 长字段来使用 'a use std::marker::PhantomData; struct Slice<'a, T> { start: *const T, end: *const T, // 隐式表示 T: 'a, 即 T 对象的引用的生命周期要比 'a 长。 phantom: PhantomData<&'a T>, }
-
隐藏数据:
use std::marker::PhantomData; // 由于 PhantomData 类型没有字段,编译器不为其分配内存,所以该类型字段不可访问,可以用来隐藏字段。 // 第二个字段是隐藏的 #[derive(PartialEq)] struct PhantomTuple<A, B>(A, PhantomData<B>); // 第二个字段是隐藏的 #[derive(PartialEq)] struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> } fn main() { // PhantomData 只有 PhantomData 一个类型值。 let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData); let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData); let _struct1: PhantomStruct<char, f32> = PhantomStruct { first: 'Q', phantom: PhantomData, }; let _struct2: PhantomStruct<char, f64> = PhantomStruct { first: 'Q', phantom: PhantomData, }; // Compile-time Error! Type mismatch so these cannot be compared: // println!("_tuple1 == _tuple2 yields: {}", // _tuple1 == _tuple2); // Compile-time Error! Type mismatch so these cannot be compared: // println!("_struct1 == _struct2 yields: {}", // _struct1 == _struct2); }
-
编译时类型检查:
use std::ops::Add; use std::marker::PhantomData; /// Create void enumerations to define unit types. #[derive(Debug, Clone, Copy)] enum Inch {} #[derive(Debug, Clone, Copy)] enum Mm {} #[derive(Debug, Clone, Copy)] struct Length<Unit>(f64, PhantomData<Unit>); impl<Unit> Add for Length<Unit> { type Output = Length<Unit>; fn add(self, rhs: Length<Unit>) -> Length<Unit> { Length(self.0 + rhs.0, PhantomData) } } fn main() { let one_foot: Length<Inch> = Length(12.0, PhantomData); let one_meter: Length<Mm> = Length(1000.0, PhantomData); let two_feet = one_foot + one_foot; let two_meters = one_meter + one_meter; println!("one foot + one_foot = {:?} in", two_feet.0); println!("one meter + one_meter = {:?} mm", two_meters.0); // Nonsensical operations fail as they should: // Compile-time Error: type mismatch. //let one_feter = one_foot + one_meter; }
9 Copy/Clone #
Copy 是一个 marker trait, 是 Clone 的 子 trait, 所以在实现 Copy 的同时也必须实现它。
pub trait Copy: Clone { }
Clone 和 Copy 的差别:
- Copy 是 Rust 隐式调用的(如变量赋值, 传参等), 不能被重载或控制, 底层是 simple bit-wise copy;
- Clone 是需要显式调用的, 如 x.clone(), 在具体实现时可能会拷贝堆内存,而 Copy 的实现一般只会做栈内存的拷贝而共享堆内存;
默认实现 Copy 的情况:
- 基本类型, 如整型, 浮点型, bool, char,
- Option<T>, Result<T,E>, Bound<T>, 共享引用 &T, 如 &str;
- 数组 [T;N], 元组 (T, T): 前提: 元素类型 T 实现了 Copy;
没有实现 Copy 的情况:
- 自定义的 Struct/Enum;
- std::collections 容器类型, 如 Vec/HashMap/HashSet; (但都实现了 Clone)
- &mut T 没有实现 Copy, 但是 &T 实现了 Copy.
- 任何实现了 Drop 的对象也没有实现 Copy.
- 各种智能指针,如 Box/Rc/Arc/Ref 都没有实现 Copy。
实现 Copy 方式: derive 宏或手动实现:
// OK: i32 实现了 Copy
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
// 错误: Vec<T> 没有实现 Copy
#[derive(Copy, Clone)]
struct PointList {
points: Vec<Point>,
}
// OK: 因为 &T 实现了 Copy, 即使 T 没有实现 Copy, &T 也实现了 Copy
#[derive(Copy, Clone)]
struct PointListWrapper<'a> {
point_list_ref: &'a PointList,
}
// 或者
struct MyStruct;
impl Copy for MyStruct { }
impl Clone for MyStruct {
fn clone(&self) -> MyStruct {
*self
}
}
如果类型实现了 Drop,则不能再实现 Copy。
10 Default #
按照惯例, 各类型使用 new() 关联函数来作为类型的 constructor。Rust 并没有强制所有类型都实现该方法, 但它提供了 Default trait, 各类型可以实现该 trait 提供的 default() -> Self 方法:
/// Time in seconds.
///
/// # Example
///
/// ```
/// let s = Second::default();
/// assert_eq!(0, s.value());
/// ```
pub struct Second {
value: u64,
}
impl Second {
pub fn value(&self) -> u64 {
self.value
}
}
impl Default for Second {
fn default() -> Self {
Self { value: 0 }
}
}
Rust 基本类型和 &str, &CStr, &OsStr, Box<str>, Box<CStr>, Box<OsStr>, CString, OsString, Error,PathBuf, String, &[T], Option<T>, [T; N], Rc<T>, Arc<T>, Vec<T>, HashSet<T, S> 等都实现了 Default。
如果类型成员都实现了 Default,则可以使用 derive macro 来为自定义类型实现 Default:
use std::{path::PathBuf, time::Duration};
#[derive(Default, Debug, PartialEq)]
struct MyConfiguration {
output: Option<PathBuf>,
search_path: Vec<PathBuf>,
timeout: Duration,
check: bool,
}
impl MyConfiguration {
}
fn main() {
// 调用 Default trait 的 default() 方法来创建对象.
let mut conf = MyConfiguration::default();
conf.check = true;
println!("conf = {conf:#?}");
// struct 对象部分输出化, 没有指定的 field 都使用 default() 方法的值来填充.
let conf1 = MyConfiguration {
check: true,
..Default::default()
};
assert_eq!(conf, conf1);
}
// 另一个例子
#[derive(Default)]
struct SomeOptions {
foo: i32,
bar: f32,
}
fn main() {
let options: SomeOptions = Default::default();
// 自动根据目标类型调用对应实现的方法, 等效于 <SomeOptions as Default>::default()
}
对于 enum 类型, 需要明确定义哪一个 enum variant 是 default:
#[derive(Default)]
enum Kind {
#[default]
A,
B,
C,
}
// 或者
enum Kind {
A,
B,
C,
}
impl Default for Kind {
fn default() -> Self { Kind::A }
}
Default trait 作为泛型类型的定界, 在标准库中得到广泛使用, 例如:
- 很多 Option::unwrap_or_default() 方法;
- std::mem::take(&mut T) 将 T 的值 move 出来,然后将原始的 T 的值用他的 Default 填充;
let x: Option<u32> = None;
let y: Option<u32> = Some(12);
assert_eq!(x.unwrap_or_default(), 0);
assert_eq!(y.unwrap_or_default(), 12);
11 Drop #
Drop trait 为对象提供了自定义析构能力,一般在对象离开 scope 时由编译器自动调用来释放资源:
- Box, Vec, String, File 和 Process 等都实现了 Drop;
一般不能直接调用对象的 drop() 方法来释放资源,这样会导致对象离开作用域被二次释放。但是可以调用 drop(obj) 或 std::mem::drop/forget() 来手动释放对象,这时 Rust 不会再自动释放它。
fn bar() -> Result<(), ()> {
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
println!("exit");
}
}
let _exit = Foo;
baz()?;
Ok(())
}
如果类型实现了 Drop,则不能再实现 Copy。
12 From/Into #
实现 From/Into trait 时,相应的方法都会转移对象所有权到结果对象中:
trait Into<t>: Sized {
fn into(self) -> T;
}
trait From<T>: Sized {
fn from(other: T) -> Self;
}
自定义类型一般只需实现 From,Rust 会自动生成相反方向的 Into:
use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
fn main() {
let num = Number::from(30);
println!("My number is {:?}", num);
}
Into 一般作用在泛型函数的限界场景中:
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command
From trait 在以下场景自动调用:
-
Into trait
为任何实现了From<T>
的类型,自动实现Into<U>
let from_type: FromType = FromType::new(); let into_type: IntoType = from_type.into(); // 自动调用 From<FromType> for IntoType 的实现
-
使用 `?` 运算符处理 `Result` 或 `Option` 错误在返回错误时,将错误类型转换为函数返回的错误类型。
fn from_type() -> Result<IntoType, IntoError> { let result: Result<FromType, FromError> = do_something_may_fail(); let from_type = result?; // 如果出错,自动调用 From<FromError> for IntoError 的实现并返回 }
标准库提供了一个 blanket From trait 的实现,用于将 std:error::Error 转换为 Box<dyn std::error::Error>, ?会自动使用 From trait 来做类似转换:
impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a> {
fn from(err: E) -> Box<dyn Error + Send + Sync + 'a> {
Box::new(err)
}
}
type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
type GenericResult<T> = Result<T, GenericError>;
fn parse_i32_bytes(b: &[u8]) -> GenericResult<i32> {
Ok(std::str::from_utf8(b)?.parse::<i32>()?)
}
-
`collect` 方法将 `Iterator<Item=T>` 转换为其他容器类型:
let vec: Vec<FromType> = get_some_from_types(); // 自动调用 From<FromType> for IntoType 的实现 let result: Result<Vec<IntoType>, _> = vec.into_iter().collect();
-
`std::convert::from` 函数会显式调用 `From` trait:
let from_type = FromType::new(); // 显式调用 From<FromType> for IntoType 的实现 let into_type:IntoType = std::convert::From::from(from_type);
-
变量赋值和函数返回值: 如 &str 实现了 Into Box<dyn std::error::Error> 的 trait, 则可以直接调用 &str.into() 方法。如果 impl From<T> for U, 则可以 let u: U = U::from(T) 或 let u: U = T.into().
fn main() { if let Err(e) = run_app() { eprintln!("Application error: {}", e); std::process::exit(1); } } fn run_app() -> Result<(), Box<dyn std::error::Error>> { // Ok(()) 执行这行将会正常返回 return Err("main will return 1".into()); } use std::net::Ipv4Addr; // A 是任意能转换为 Ipv4Addr 的类型 fn ping<A>(address: A) -> std::io::Result<bool> where A: Into<Ipv4Addr> { let ipv4_address = address.into(); // ... }
13 FromStr/ToString #
FromStr 从字符串来生成类型值。
Rust 基本类型,如整数、浮点数、bool、char、String、PathBuf、IpAddr、 SocketAddr、Ipv4Addr、 Ipv6Addr 都实现了该 trait。
pub trait FromStr: Sized {
type Err;
// Required method
fn from_str(s: &str) -> Result<Self, Self::Err>;
}
str::parse::<T>() 方法使用 FromStr trait 将字符串转换为 T 类型对象:
pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where F: FromStr
在使用 &str.parse() 方法时一般需要指定目标对象类型,否则编译器不知道该调用哪个类型的 FromStr 实现:
let four: u32 = "4".parse().unwrap();
assert_eq!(4, four);
let four = "4".parse::<u32>();
assert_eq!(Ok(4), four);
ToString trait 用于从对象返回一个字符串对象。
Rust 对实现 Display trait 的类型自动实现了 ToString trait,例如实现了 std::error:Error trait 的对象也实现了 ToString trait:
use std::fmt;
struct Circle {
radius: i32
}
impl fmt::Display for Circle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Circle of radius {}", self.radius)
}
}
fn main() {
let circle = Circle { radius: 6 };
println!("{}", circle.to_string());
}
14 FromIterator/IntoIterator #
FromIterator: 从一个 IntoIterator<Item=A>
迭代器创建一个 Self 类型对象:
pub trait FromIterator<A>: Sized {
// Required method
fn from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = A>;
}
FromIterator/IntoIterator 的典型使用场景是迭代器方法 collect::<TargetType>() ,用将前序迭代器对象的值创建一个 TargetType:
// 使用 collect 可以构建出 Rust 标准库中的任意集合,只要能迭代生成合适的条目类型即可
let args: Vec<String> = std::env::args().collect();
let args: HashSet<String> = std::env::args().collect();
let args: BTreeSet<String> = std::env::args().collect();
let args: LinkedList<String> = std::env::args().collect();
let args: HashMap<String, usize> = std::env::args().zip(0..).collect();
let args: BTreeMap<String, usize> = std::env::args().zip(0..).collect();
IntoIterator 是 for-in 迭代循环对 type 的要求,即 type 要实现 IntoIterator 才能被 for-in 迭代:
pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
// Required method
fn into_iter(self) -> Self::IntoIter;
}
15 Extend #
std::iter::Extend trait 用于消费传入的迭代器对象,将元素插入到自身对象中:
pub trait Extend<A> {
// Required method
fn extend<T>(&mut self, iter: T) where T: IntoIterator<Item = A>;
// Provided methods
fn extend_one(&mut self, item: A) { ... }
fn extend_reserve(&mut self, additional: usize) { ... }
}
Rust 各种集合类型都实现了 Extend trait:
let mut message = String::from("The first three letters are: ");
message.extend(&['a', 'b', 'c']);
assert_eq!("abc", &message[29..32]);
Extend 和 std::iter::FromIterator 非常类似,但后者会创建新的集合,而 Extend 扩充 Self 自己。
16 AsRef/AsMut #
AsRef/AsMut 多用于泛型参数限界,例如:open() 的实现依赖于 &Path,通过限定 P 实现了 AsRef<Path>,在 open() 内部就可以通过 P.as_ref() 方法调用返回 &Path 的类型对象:
fn open<P: AsRef<Path>>(path: P) -> Result<file>
标准库各种类型,如 String/str/OsString/OsStr/PathBuf/Path 等实现了 AsRef<Path>, 所以这些类型对象都可以作为 open() 的参数:
// https://doc.rust-lang.org/src/std/path.rs.html#3170
impl AsRef<Path> for String {
#[inline]
fn as_ref(&self) -> &Path {
Path::new(self)
}
}
String 实现了 AsRef<str>, AsRef<[u8]>, AsRef<Path>。
17 运算符重载/PartialEq/PartialOrd/Eq/Ord #
Operator | Trait |
---|---|
+ | Add |
- | Sub |
* | Mul |
% | Rem |
== and != |
PartialEq |
<, >, <=, and >= | PartialOrd |
PartialEq 只包含 eq/ne() 方法,而 PartialOrd 只需要实现 partial_cmp() 方法:
- Eq 是 PartialEq 的 子 trait;
- Ord 是 PartialOrd + Eq 的子 trait;
- PartialOrd 是 PartialEq 的子 trait;
Rust 为绝大部分基本类型实现了这些 trait。
pub trait PartialEq<Rhs = Self> where Rhs: ?Sized,
{
// Required method
fn eq(&self, other: &Rhs) -> bool;
// Provided method
fn ne(&self, other: &Rhs) -> bool { ... }
}
pub trait Eq: PartialEq { }
// PartialOrd 是 PartialEq 的子 trait
pub trait PartialOrd<Rhs = Self>: PartialEq<Rhs> where Rhs: ?Sized,
{
// Required method
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
// Provided methods
fn lt(&self, other: &Rhs) -> bool { ... }
fn le(&self, other: &Rhs) -> bool { ... }
fn gt(&self, other: &Rhs) -> bool { ... }
fn ge(&self, other: &Rhs) -> bool { ... }
}
// 实现 Ord 的同时需要实现:Eq 和 PartialOrd,以及 PartialEq
pub trait Ord: Eq + PartialOrd {
// Required method
fn cmp(&self, other: &Self) -> Ordering;
// Provided methods
fn max(self, other: Self) -> Self where Self: Sized
fn min(self, other: Self) -> Self where Self: Sized
fn clamp(self, min: Self, max: Self) -> Self where Self: Sized + PartialOrd
}
可以使用 derive macro 来自动生成 PartialEq/PartialOrd 的实现:
#[derive(PartialEq)]
struct Ticket {
title: String,
description: String,
status: String
}
也可以手动实现 PartialEq/PartialOrd trait:
#[derive(PartialEq)]
enum BookFormat {
Paperback,
Hardback,
Ebook,
}
#[derive(PartialEq)]
struct Book {
isbn: i32,
format: BookFormat,
}
// PartialEq 是泛型 trait,可以在实现时指定泛型参数的类型
impl PartialEq<BookFormat> for Book {
fn eq(&self, other: &BookFormat) -> bool {
self.format == *other
}
}
impl PartialEq<Book> for BookFormat {
fn eq(&self, other: &Book) -> bool {
*self == other.format
}
}
fn main() {
let b1 = Book { isbn: 1, format: BookFormat::Paperback };
let b2 = Book { isbn: 2, format: BookFormat::Paperback };
assert!(b1 == BookFormat::Paperback);
assert!(BookFormat::Paperback == b2);
}
std::ops module
下的各 trait 用以支持运算符重载:
use std::ops;
struct Foo;
struct Bar;
#[derive(Debug)]
struct FooBar;
#[derive(Debug)]
struct BarFoo;
impl ops::Add<Bar> for Foo {
type Output = FooBar;
fn add(self, _rhs: Bar) -> FooBar {
println!("> Foo.add(Bar) was called");
FooBar
}
}
impl ops::Add<Foo> for Bar {
type Output = BarFoo;
fn add(self, _rhs: Foo) -> BarFoo {
println!("> Bar.add(Foo) was called");
BarFoo
}
}
fn main() {
println!("Foo + Bar = {:?}", Foo + Bar);
println!("Bar + Foo = {:?}", Bar + Foo);
}
// 实现泛型 trait
impl<L, R> Add<Complex<R>> for Complex<L> where: L: Add<R>,
{
type Output = Complex<L::Output>;
fn add(self, rhs: Complex<R>) -> Self::Output {
Complex{
re: self.re + rhs.re,
im: self.im + rhs.im,
}
}
}
其它运算符:
- i..j, i.., ..j, i..=j 等:是 RangeXX struct 类型语法糖;
- ?:适用于 Result 和 Option,而且可以和 From/Into trait 协作,进行自动类型转换;
- *val:使用 Deref/DerefMut trait 来进行重载;
对于 * 和 & 运算符,使用 Deref/DerefMut trait 重载。
18 Index/IndexMut #
类型实现了 std::ops::Index/IndexMut trait 后,就可以使用 a[i] 语法来获取和设置值。
a[i] 调用a.index(i) 方法后再自动解引用,所以等效于 *a.index(i) 或 *a.index_mut(i)
。返回一个对象的好处是,可以作为左值使用,例如 a[i] = 3。
Rust 编译器根据 a[i] 索引表达式所在的上下文自动选择调用 index() 或 index_mut() 方法。
Index/IndexMut 对 i 的类型没有限定,可以是 usize 如 a[i], 或 Range<usize> 如 a[i..j] 等,取决于类型实现 Index/IndexMut 时指定的 index 值类型。
i..j, i.., ..j, i..=j 等表达式是 struct RangeXX 类型语法糖。Rust 根据 a[xx] 操作中的 xx 类型,自动生成对应的 structRangeXX struct 类型,如:
- Range: m..n
- RangeFrom: m..
- RangeFull: ..
- RangeInclusive: m..=n
- RangeTo: ..n
- RangeToInclusive: ..=n
#[stable(feature = "rust1", since = "1.0.0")]
impl<I> ops::Index<I> for str where I: SliceIndex<str>
{
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &I::Output {
index.index(self)
}
}
// SliceIndex 文档页面的 Implementors 部分可以看到实现 SliceIndex<str> 的类型列表,以及对应的关联类型 Output 的具体类型
impl SliceIndex<str> for (Bound<usize>, Bound<usize>)
type Output = str
impl SliceIndex<str> for Range<usize>
type Output = str
impl SliceIndex<str> for RangeFrom<usize>
type Output = str
impl SliceIndex<str> for RangeFull
type Output = str
impl SliceIndex<str> for RangeInclusive<usize>
type Output = str
impl SliceIndex<str> for RangeTo<usize>
type Output = str
impl SliceIndex<str> for RangeToInclusive<usize>
type Output = str
19 Borrow/ToOwned/Cow #
Borrow/BorrowMut 和 AsRef/AsMut 类似:Borrow<T> 是从自身创建一个 &T 借用,但要求 &T 类型必须和 Self 类型以相同的方式进行哈希和比较。Rust 编译器并不会强制该限制,但是 Borrow 有这种约定的意图。
Borrow 用于解决泛型哈希表和其他关联集合类型的情况:K 和 Q 都是 Eq + Hash 语义,K: Borrow<Q> 表示可以从 K 对象生成 &Q 的引用。所以可以给 HashMap 的 get() 方法传入任何满足上面两个约束的引用对象。
- 在 get() 方法的内部实现中,会从自身的 K.borrow() 生成 &Q 对象,然后用 K 的 Eq+Hash 值与 Q 的 Eq+Hash 值进行比较;
- self 一般是 owned 类型, 如 String/PathBuf/OsString/Box/Arc/Rc 等, borrow 的结果是对应的 unsized 类型的借用, 如 &str/&Path/&OsStr/&T;
impl<K,V> HashMap<K, V> where K: Eq + Hash
{
// key 必须是借用类型 &Q,而且需要 K 实现了 Borrow<Q>
fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V> where K: Borrow<Q>, Q: Eq + Hash
// ...
}
String 和 &str 保证做相同的 hash,所以 String 只实现按了 Borrow<str>, 所以 HashMap<String, i32> 的 get() 方法可以传入 &String 或 &str 类型值。
// 从 Ownerd 类型生成 unsized 的类型引用
impl Borrow<str> for String
impl Borrow<CStr> for CString
impl Borrow<OsStr> for OsString
impl Borrow<Path> for PathBuf
impl<'a, B> Borrow<B> for Cow<'a, B> where B: ToOwned + ?Sized,
// 从 &T/&mut T/T 类型值产生 &T
impl<T> Borrow<T> for &T where T: ?Sized,
impl<T> Borrow<T> for &mut T where T: ?Sized,
impl<T> Borrow<T> for T where T: ?Sized,
impl<T, A> Borrow<[T]> for Vec<T, A> where A: Allocator,
impl<T, A> Borrow<T> for Box<T, A> where A: Allocator, T: ?Sized,
impl<T, A> Borrow<T> for Rc<T, A> where A: Allocator, T: ?Sized,
impl<T, A> Borrow<T> for Arc<T, A> where A: Allocator, T: ?Sized,
impl<T, const N: usize> Borrow<[T]> for [T; N] // &[T;N] -> &[T]
std::borrow::ToOwned trait
从 &T 生成 Owned 类型:
- &str -> String
- &CStr -> CString
- &OsStr -> OsString
- &Path -> PathBuf
- &[T] -> Vec<T>
- &T -> T
pub trait ToOwned {
// Owned 是实现了 Borrow<Self> 的任意类型
type Owned: Borrow<Self>;
// Required method
fn to_owned(&self) -> Self::Owned;
// Provided method
fn clone_into(&self, target: &mut Self::Owned) { ... }
}
// 实现 ToOwned 的类型都是 unsized 类型, 它们的 Owned 类型都是 sized 版本
impl ToOwned for str type Owned = String
impl ToOwned for CStr type Owned = CString
impl ToOwned for OsStr type Owned = OsString
impl ToOwned for Path type Owned = PathBuf
impl<T> ToOwned for [T] where T: Clone, type Owned = Vec<T>
impl<T> ToOwned for T where T: Clone, type Owned = T
// 示例
let s: &str = "a";
let ss: String = s.to_owned();
let v: &[i32] = &[1, 2];
let vv: Vec<i32> = v.to_owned();
Clone 和 ToOwned 区别:
- 相同点: 两者都可以从 &self -> Self, 也即 &T -> T;
- 不同点: Clone trait 只能实现 &T -> T 的转换, 而 ToOwned trait 可以更灵活, 转换后的对象不局限于 T,只要 Owned 类型实现了 Borrow<Self> 就可以转换为该 Owned 类型。 例如 String 实现了 Borrow<str>, 则 &str 就可以转换为 Owned 类型为 String 的对象。
std::borrow::Cow
是可以保存 &B 或 B 的 Owned 类型的枚举类型(cow:clone on write):
// B 必须实现 ToOwned trait
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B), // &B
Owned(<B as ToOwned>::Owned), // &B 的 Owned 类型
}
Cow 实现了各种 From trait,可以从多种类型对象生成 Cow:
impl<'a, T> From<&'a [T]> for Cow<'a, [T]> where T: Clone
impl<'a, T, const N: usize> From<&'a [T; N]> for Cow<'a, [T]> where T: Clone
impl<'a> From<&'a CStr> for Cow<'a, CStr>
impl<'a> From<&'a CString> for Cow<'a, CStr>
impl<'a> From<&'a OsStr> for Cow<'a, OsStr>
impl<'a> From<&'a OsString> for Cow<'a, OsStr>
impl<'a> From<&'a Path> for Cow<'a, Path>
impl<'a> From<&'a PathBuf> for Cow<'a, Path>
impl<'a> From<&'a String> for Cow<'a, str>
impl<'a, T> From<&'a Vec<T>> for Cow<'a, [T]> where T: Clone
impl<'a> From<&'a str> for Cow<'a, str>
impl<'a> From<CString> for Cow<'a, CStr>
如果 Cow 实际保存的是 Borrowed<&B> 对象, 则在调用 into_owned()/to_mut()
方法时, 会调用 &B 实现的
ToOwned trait 来生成 Owned 对象, 也就是实现了 Clone on Write 的特性。
Cow<'a, B> 实现了 Deref<Target=B>
, 所以可以直接调用 B 的方法。
// Cow 方法:
pub fn is_borrowed(&self) -> bool
pub fn is_owned(&self) -> bool
pub fn into_owned(self) -> <B as ToOwned>::Owned // 消耗 Cow, 生成 Owned 对象
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned // 返回 Owned 对象的 &mut 引用。
// 函数参数是 Cow<'_, [i32> ], 故可以保存 &[i32] 或它的 Owned 类型 Vec<i32>
fn abs_all(input: &mut Cow<'_, [i32]>) {
// Cow 实现了 Deref<Target=B>, 所以可以调用 B 的方法。
for i in 0..input.len() {
let v = input[i];
if v < 0 {
// 这里是通过 &[T] 实现的 ToOwned trait 来生成一个 &mut Vec<T> 变量,然后修改它。
input.to_mut()[i] = -v;
}
}
}
// slice 元素都大于 0,所以不会 clone
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]); // 保存 &[i32]
abs_all(&mut input);
// slice 有小于 0 的元素,所以会被 clone
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
abs_all(&mut input);
// 不会 clone,因为 input 保存的就是 owned 类型 Vec<i32>
let mut input = Cow::from(vec![-1, 0, 1]);
abs_all(&mut input);
// Another example showing how to keep Cow in a struct:
use std::borrow::Cow;
struct Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
values: Cow<'a, [X]>,
}
impl<'a, X: Clone + 'a> Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
fn new(v: Cow<'a, [X]>) -> Self {
Items { values: v }
}
}
let readonly = [1, 2];
let borrowed = Items::new((&readonly[..]).into());
match borrowed {
Items { values: Cow::Borrowed(b) } => println!("borrowed {b:?}"),
_ => panic!("expect borrowed value"),
}
let mut clone_on_write = borrowed;
clone_on_write.values.to_mut().push(3);
println!("clone_on_write = {:?}", clone_on_write.values);
match clone_on_write {
Items { values: Cow::Owned(_) } => println!("clone_on_write contains owned data"),
_ => panic!("expect owned data"),
}
Cow 语义看成『potentially owned』,即可能拥有所有权,可以用来避免一些不必须的拷贝。比如保存函数内临时对象的引用:
fn foo(s: &str, some_condition: bool) -> &str {
if some_condition {
// 错误:在 block 结束时临时对象被释放,临时对象的引用无效
&s.replace("foo", "bar")
} else {
s
}
}
// 如果把返回值改成 String,那么在 else 分支会有一次额外的拷贝,这时 Cow 就可以派上用场了。
fn foo(s: &str, some_condition: bool) -> Cow<str> {
if some_condition {
Cow::from(s.replace("foo", "bar")) // 保存 Owned 的 String 对象
} else {
Cow::from(s) // 保存 &str
}
}
20 Deref/DerefMut #
Deref/DerefMut trait 定义:
pub trait Deref {
type Target: ?Sized;
// Required method
fn deref(&self) -> &Self::Target;
}
pub trait DerefMut: Deref {
// Required method
fn deref_mut(&mut self) -> &mut Self::Target;
}
Defer 主要使用场景是智能指针,例如 t=Box<U>,可以对 t 进行 *t 和 &t 操作:
- *t 返回 U,等效于 *Deref.deref(&t);
- &t 返回 &U, 等效于 Deref.deref(&t);
在需要对象的 &mut 引用时,如方法调用或变量赋值,Rust 会检查对象是否实现了 DerefMut trait,如果实现了,则 自动调用(被称为 mutable deref coercion) 它来实现转换
, 所以,*v 作为左值的场景,Rust 使用 DerefMut<Target=U> 来对 * 操作符进行重载,相当于用生成 U 类型对象来进行赋值。
对于实现了 Deref<Target=U> 的类型 T 值, &*T 返回 &U
:
- Deref 重载了 * 运算符, 所以
*T == *t.deref()
, 返回 U 对象, 为了获得 &U 可以使用表达式&*T
;
Rust 自动调用 Deref trait 来满足赋值或传参类型的隐式转换,但是如果函数的参数是泛型限界,则 Rust 不会通过 Deref trait 来解引用来满足泛型参数限界的要求,两种解决办法:
- 使用 as 类型转换;
- 手动解引用 &*V;
use std::ops::{Deref, DerefMut};
impl<T> Deref for Selector<T> {
type Target = T;
fn deref(&self) -> &T { &self.elements[self.current] }
}
impl<T> DerefMut for Selector<T> {
fn deref_mut(&mut self) -> &mut T { &mut self.elements[self.current] }
}
// OK: 在函数传参时, Rust 会自动隐式解引用, &Selector -> &str
let s = Selector { elements: vec!["good", "bad", "ugly"], current: 2 };
fn show_it(thing: &str) { println!("{}", thing); }
show_it(&s);
// Error: 当使用 Trait 作为泛型参数的限界时, Rust 不会自动解引用到 &str 类型来满足类型限界的要求
use std::fmt::Display;
fn show_it_generic<T: Display>(thing: T) { println!("{}", thing); }
show_it_generic(&s);
// 两种解决办法:
show_it_generic(&s as &str); // 使用 as 类型转换;
show_it_generic(&*s) // 手动解引用 &*V 来得到 &str 类型:
类型自动转化是 Rust 为了追求语言简洁,使用 Deref/DerefMut 实现的另一种隐式操作,比如下面的赋值都是正确的:
let s: String = "hello".to_string();
let s1: &String = &s;
let s2: &str = s1;
let s3: &str = &&s;
let s4: &str = &&&s;
let s5: &str = &&&&s;
无论有多少个 & ,Rust 都能正确的将其转为 &str 类型
, 也就是 Rust 自动进行嵌套的隐式解引用,究其原因,是因为 deref coercions
,它允许在 T: Deref<U> 时,&T 可以自动转为 &U 。
21 smart pointer #
只要实现了 Deref trait 的类型都可以称为 smart pointer,例如:
// https://doc.rust-lang.org/std/ops/trait.Deref.html
impl Deref for String
type Target = str
impl<B> Deref for Cow<'_, B> where B: ToOwned + ?Sized, <B as ToOwned>::Owned: Borrow<B>
type Target = B
impl<P> Deref for Pin<P> where P: Deref
type Target = <P as Deref>::Target
impl<T> Deref for &T where T: ?Sized
type Target = T
impl<T> Deref for &mut T where T: ?Sized
type Target = T
impl<T> Deref for Ref<'_, T> where T: ?Sized
type Target = T
impl<T> Deref for RefMut<'_, T> where T: ?Sized
type Target = T
impl<T, A> Deref for Box<T, A> where A: Allocator, T: ?Sized
type Target = T
impl<T, A> Deref for Rc<T, A> where A: Allocator, T: ?Sized
type Target = T
impl<T, A> Deref for Arc<T, A> where A: Allocator, T: ?Sized
type Target = T
impl<T, A> Deref for Vec<T, A> where A: Allocator
type Target = [T]
Ref<T>/RefMut<T>/Rc<T>/Arc<T>/Box<T> :
- * 操作符解引用后类型都是 T,实际执行的操作为:*(v.deref()).
- 在需要 &T 的地方都可以传入 &Ref/&Rc/&Box 类型;
- Ref<T>/Box<T> 可以调用 T 定义的所有方法;
由于智能指针都可以调用 &T 的方法, 为了避免指针的方法和 &T 方法冲突, 在调用智能指针自己的方法或实现的 trait 时, 一般使用全限定名称:
use std::sync::Arc;
let foo = Arc::new(vec![1.0, 2.0, 3.0]);
// 以下两个方法调用等价,但是建议使用第二种方式: 全限定名称的方法或关联函数调用
let a = foo.clone();
let b = Arc::clone(&foo);
let my_weak = Arc::downgrade(&my_arc); // 全限定语法
String/Vec<T> 其实也是 smart pointer, 只不过是专用的,它们占用 3 个机器字栈空间+可变长堆空间:
- String 实现了 Deref<Target = str>;
- Vec<T> 实现了 Deref<Target = [T]>;
fn read_slice(slice: &[usize]) {
// ...
}
let v = vec![0, 1];
read_slice(&v); // Deref 自动转换
let u: &[usize] = &v; // Deref 自动转换
在函数传参匹配 trait bound 时不会进行协变和自动 Deref trait 调用,例如虽然 &mut i32 可以协变到 &i32, 但传参时不会协变。带来的影响是:如果 Trait 作为函数参数限界,&i32 和 &mut i32 两种类型都需要实现该 Trait。
trait Trait {}
fn foo<X: Trait>(t: X) {}
impl<'a> Trait for &'a i32 {}
fn main() {
let t: &mut i32 = &mut 0;
foo(t);
}
// error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
// --> src/main.rs:9:9
// |
// 3 | fn foo<X: Trait>(t: X) {}
// | ----- required by this bound in `foo`
// ...
// 9 | foo(t);
// | ^ the trait `Trait` is not implemented for `&mut i32`
// |
// = help: the following implementations were found:
// <&'a i32 as Trait>
// = note: `Trait` is implemented for `&i32`, but not for `&mut i32`
22 Rc/Arc #
Rc<T> 和 Box<T> 类似,也是在堆上分配内存,并拥有它,但是:
- Rc::clone(&a) 增加 a 的引用计数,并不会重新分配堆内存;
- Rc 和 clone() 返回的 Rc 对象都被 drop 后,a 对应的堆内存才会被释放;
- Rc 值是只读的,可以和 Cell/RefCell 结合使用,使用内部可变性机制来修改共享对象;
Rc<T> 主要使用场景是需要多 owner 的情况:
- 一个链接表对象被多个其他链接表共享引用;
- 在多线程中共享大的数据,避免内存拷贝;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
// 不能使用 Box<T>, 否则这里会发生所有权转移,后续不能再使用它。
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
打印引用计数:
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
// $ cargo run
// Compiling cons-list v0.1.0 (file:///projects/cons-list)
// Finished dev [unoptimized + debuginfo] target(s) in 0.45s
// Running `target/debug/cons-list`
// count after creating a = 1
// count after creating b = 2
// count after creating c = 3
// count after c goes out of scope = 2
Rc 提供了 get_mut()/make_mut()/into_inner() 方法来修改对象,Rc::get_mut() 方法
get_mut() 函数: 当没有其他 Rc 对象引用 this 时
, 也就是 this Rc 对象的引用计数为 1 时,
返回 &mut T 否则返回 None。
- pub fn get_mut(this: &mut Rc<T, A>) -> Option<&mut T>
make_mut() 要求 T 对象实现 Clone traig,方法在强引用计数 >=1 时, clone 产生一个新的 Rc 对象并替换 this,这被称为 clone-on-write, 然后返回它的 &mut T。该方法在弱引用计数 >=1 时,不会 clone,而是打破弱引用计数对象引用的 T 对象,然后返回 &mut T。
- pub fn make_mut(this: &mut Rc<T, A>) -> &mut T
use std::rc::Rc;
let mut x = Rc::new(3);
*Rc::get_mut(&mut x).unwrap() = 4;
assert_eq!(*x, 4);
let _y = Rc::clone(&x);
assert!(Rc::get_mut(&mut x).is_none());
let mut data = Rc::new(5);
*Rc::make_mut(&mut data) += 1; // Won't clone anything
let mut other_data = Rc::clone(&data); // Won't clone inner data
// 由于 data 被 clone 过, 再次调用 data 的 make_mut 时会 clone 一个新 Rc 对象并替换
// data 这时 other_data 的引用计数 -1 变为 1, data 的引用计数也为 1
*Rc::make_mut(&mut data) += 1; // Clones inner data
// data 是刚才 clone 生成的新对象, 计数为 1, 所以这次不会再 clone
*Rc::make_mut(&mut data) += 1; // Won't clone anything
// other_data 引用计数为 1, 所以也不会 clone
*Rc::make_mut(&mut other_data) *= 2; // Won't clone anything
// Now `data` and `other_data` point to different allocations.
assert_eq!(*data, 8);
assert_eq!(*other_data, 12);
Rc::into_inner() 方法:消耗传入的 Rc(其他类型的 into_inner() 方法都是类似的语义). 如果 Rc 只有一个强引用,则返回 inner value, 否则返回 None。
- pub fn into_inner(this: Rc<T, A>) -> Option<T>
into_inner() 是一个常见的类型方法, 都是从包装类型生成对应的 T, 例如:
- pub fn into_inner(boxed: Box<T, A>) -> T
- Cell/RefCell
// pub fn into_inner(self) -> T
use std::cell::Cell;
let c = Cell::new(5);
let five = c.into_inner();
assert_eq!(five, 5);
使用 Rc/Arc 可能创建出一些循环引用的数据结构,导致 Rc/Arc 管理的对象引用计数 strong_count() 不为 0,从而发生内存泄露。
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
// block 结束时, b 被 drop 后 strong_count() 为 1,
// 所以堆内存不会被释放, a 也是同样的情况.
}
解决办法:使用 Rc::downgrade() 而非 Rc::clone() 来增加对象弱引用计数 weak_count(), 而不会增加 strong_count(), 返回一个 Weak<T> 智能指针。
Weak<T> 对 T 进行弱引用计数,在它 >=1 时对象也可能因为强引用计数为 0 而被回收,所以 Weak 引用的对象可能是 None。Weak 对象的 upgrade() 方法返回 Rc,但是如果 Weak 引用的对象不存在(为 None)则该方法返回 None。
- weak_count() 记录 Rc 对象的 Weak<T> 对象数量。
- Rc<T> 并不会在执行清理时要求 weak_count() 为 0;
- 一旦强引用计数为 0,任何由弱引用组成的循环就会被打破,因此
弱引用不会造成循环依赖
; - 由于 Weak<T> 引用的值可能会被释放,可以使用 weak.upgrade() 方法来验证 weak 指向的值是否存在,返回一个 Option<Rc<T>>, 在 Rc<T> 值存在时返回 Some, 否则返回 None.
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
{
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
);
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
Arc 是线程安全的 Rc:
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
let val = Arc::new(AtomicUsize::new(5));
for _ in 0..10 {
let val = Arc::clone(&val);
thread::spawn(move || {
let v = val.fetch_add(1, Ordering::Relaxed);
println!("{v:?}");
});
}
23 Pin/Unpin/pin!()/.await #
由于 move 机制的存在,导致 Rust 很难去正确表达『自引用』的结构,比如链表、树等。这是由于 move 只会进行值本身的拷贝,指针的指向则不变。如果被 move 的结构有指向其他字段的指针,那么这个指向被 move 后就是非法的,因为原始指向已经换地址了。
Pin 的常用场景是 std::future::Feature trait 的 poll 方法:由于 Future trait object
必须保存执行上下文中 stack 上的变量,而 Future 下一次被 wake 执行的时机是不定的,所以为了避免 Future 对象上保存的 stack 变量的地址发生变化导致引用出错,需要将 Future 对象设置为
Pin<&mut Self>
类型,表示该对象不能被 move 转移所有权。
pub trait Future {
type Output;
// Required method
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
#![feature(noop_waker)]
use std::future::Future;
use std::task;
let waker = task::Waker::noop();
let mut cx = task::Context::from_waker(&waker);
let mut future = Box::pin(async { 10 });
assert_eq!(future.as_mut().poll(&mut cx), task::Poll::Ready(10));
Pin 是 struct std::pin::Pin struct
类型,而 Unpin 是标准库提供的 marker trait,标准库 为绝大部分类型实现了 Unpin
。
类型实现 Unpin trait 的意味着该类型对象不提供任何 Pin 的语义,它表示一个类型可以安全的在内存中移动(move)。如果类型实现了 Unpin,则可以从 Pin 中安全地获取可变引用,Unpin 时候 Pin 的对立面,表示不需要 Pin 的保护。
fn example<T: Unpin>(x: Pin<&mut T>) {
let mut_ref: &mut T = Pin::into_inner(x); // 安全,因为 T: Unpin
}
几乎所有类型都实现了 Unpin,例外的情况如下:
- Future:因为 Future 可能包含对自身的引用,移动它可能会使这些引用失效。
- 包括 async/await 语法糖展开后的 Future 类型;
- Pin<P> 类型:Pin 本身就是为了处理不能移动的数据而设计的,Pin<P> 对于大多数 P 类型都不会实现 Unpin;
- PhantomPinned:这是一个标记类型,用来显式标记类型不应该实现 Unpin, struct MyType { _pin: PhantomPinned }
Pin 在异步编程中得到广泛应用:
- async/await 生成的 Future 可能包含自引用结构, Future 没有实现 Unpin;
- 编译器将 async 函数转换为状态机时,会产生包含内部引用的结构;
调用 async fn 返回一个 Future 类型值,它没有实现 Unpin(也就是实现了 !Unpin), 在对它进行 poll 前,需要将值 pinned。
=对 Future 对象调用 .await 时=,如 future.await, 会自动对 future 对象进行 Pin,但同时也会消耗 future 对象。
Pin<T> 是一个封装类型, 用于确保被包装的数据不会在内存中被移动。
使用 Pin::new(&mut T)
函数来为 T 创建 Pin 类型值时,要求 T 必须实现 Unpin trait。
- 不能对 !Unpin 类型,如 Future 对象,调用 Pin:new(),而需要使用 Box::pin() 或 pin!();
- 如果在 non_std 环境创建 Pin 对象或则 T 没有实现 Unpin,则需要使用
pin!()
宏。
创建 Pin 对象的几种方式:
- Pin 到栈上:let mut data=43; let pinned = Pin::new(&mut data);
- Pin 到堆上: let boxed = Box::new(42); let pinned = Pin::new(boxed);
- 直接在堆上创建 Pin:let pinned = Box::pin(42);
use std::pin::Pin;
let mut unpin_future = std::future::ready(5);
// 传入的是 &mut T
let my_pinned_unpin_future: Pin<&mut _> = Pin::new(&mut unpin_future);
// 如果要 Pin<Box<T>>, 则需要使用 Box::pin(value) 函数
use std::pin::Pin;
async fn add_one(x: u32) -> u32 {
x + 1
}
let fut = add_one(42);
let pinned_fut: Pin<Box<_>> = Box::pin(fut);
// 如果要 Pin<Box<dyn Trait>> ,则需要使用 Box::into_pin(value) 函数
async fn add_one(x: u32) -> u32 {
x + 1
}
fn boxed_add_one(x: u32) -> Box<dyn Future<Output = u32>> {
Box::new(add_one(x))
}
let boxed_fut = boxed_add_one(42);
let pinned_fut: Pin<Box<_>> = Box::into_pin(boxed_fut);
Pin<Ptr> 类型的方法:
- pinned.as_ref():返回一个 Pin<& impl Future<Output=xx>> 类型值;
- pinned.as_mut(): 返回一个 Pin<&mut impl Future<Output=xx>> 类型值,它实现了 Future。
注意:这两个方法返回的都是新的 Pin 对象,而非它们的借用。
impl<Ptr> Pin<Ptr> where Ptr: Deref, <Ptr as Deref>::Target: Unpin,
pub fn new(pointer: Ptr) -> Pin<Ptr>
pub fn into_inner(pin: Pin<Ptr>) -> Ptr
impl<Ptr> Pin<Ptr> where Ptr: Deref,
pub unsafe fn new_unchecked(pointer: Ptr) -> Pin<Ptr>
pub fn as_ref(&self) -> Pin<&<Ptr as Deref>::Target>
impl<Ptr> Pin<Ptr> where Ptr: DerefMut,
pub fn as_mut(&mut self) -> Pin<&mut <Ptr as Deref>::Target>
pub fn set(&mut self, value: <Ptr as Deref>::Target) where <Ptr as Deref>::Target: Sized,
impl<'a, T> Pin<&'a T> where T: ?Sized,
pub fn get_ref(self) -> &'a T
impl<'a, T> Pin<&'a mut T> where T: ?Sized,
pub fn into_ref(self) -> Pin<&'a T>
pub fn get_mut(self) -> &'a mut T where T: Unpin,
Pin<Ptr> 实现了 Deref 和 DerefMut trait,所以可以调用 Ptr 指向的类型的方法:
impl<Ptr> Deref for Pin<Ptr> where Ptr: Deref,
type Target = <Ptr as Deref>::Target
fn deref(&self) -> &<Ptr as Deref>::Target
impl<Ptr> DerefMut for Pin<Ptr> where Ptr: DerefMut, <Ptr as Deref>::Target: Unpin,
fn deref_mut(&mut self) -> &mut <Ptr as Deref>::Target
Pin<Ptr> 实现了 Future,其中对于 Ptr 要求是 &mut impl Future 类似:
impl<P> Future for Pin<P> where P: DerefMut, <P as Deref>::Target: Future,
type Output = <<P as Deref>::Target as Future>::Output
fn poll(
self: Pin<&mut Pin<P>>,
cx: &mut Context<'_>,
) -> Poll<<Pin<P> as Future>::Output>
.await 表达式用于对 Future 对象进行 poll,直到获得 Ready 值。它内部自动对 future 对象进行 Pin(通过获得 future 对象的 &mut 引用),然后调用它的 poll() 方法来实现:
- Pin::new(&mut future) 返回的 Pin<Ptr> 其中 Ptr 为 &mut impl Future<Output=xx> 实现了 Future,返回的 Output 类型为 xx。
let mut future = async{println!("hello")};
future.await;
// future.await 大致等同于:
loop {
// Pin::new(&mut future) 实现了 Future, Output 为 future 的 Output。
match Pin::new(&mut future).poll(&mut cx) {
Poll::Ready(value) => break value,
Poll::Pending => yield_to_runtime(), // 让出执行权
}
}
类似的,tokio 等异步运行时也会自动对 Future 对象进行 Pin 操作:
use futures::executor::block_on;
async fn hello_world() {
println!("hello, world!");
}
fn main() {
let future = hello_world();
// block_on() 自动对 future 进行 Pin 操作。
block_on(future); // `future` is run and "hello, world!" is printed
}
// block_on() 的一种简化实现方式:
use waker_fn::waker_fn; // Cargo.toml: waker-fn = "1.1"
use futures_lite::pin; // Cargo.toml: futures-lite = "1.11"
use crossbeam::sync::Parker; // Cargo.toml: crossbeam = "0.8"
use std::future::Future;
use std::task::{Context, Poll};
fn block_on<F: Future>(future: F) -> F::Output {
let parker = Parker::new();
let unparker = parker.unparker().clone();
let waker = waker_fn(move || unparker.unpark());
let mut context = Context::from_waker(&waker);
// 为 future 创建一个 Pin 对象
pin!(future);
loop {
// future.as_mut() 返回
match future.as_mut().poll(&mut context) {
Poll::Ready(value) => return value,
Poll::Pending => parker.park(),
}
} }
但是 .await 不支持对 Future 对象的 &mut 借用进行 poll:
- 这是因为 Future 的 poll() 方法的 self 签名是 self: Pin<&mut Self>
async fn my_async_fn() {
// async logic here
}
#[tokio::main]
async fn main() {
let mut future = my_async_fn();
// .await 不支持 &mut Future 的自动 Pin
(&mut future).await;
}
需要显式将 Future 对象 Pin 住(使用 pin!() 宏),然后才能 Pin 住对象的 &mut 进行 .await:
// 下面代码 Ok
#[tokio::main]
async fn main() {
let future = my_async_fn();
pin!(future);
// future 类型是 Pin<Ptr> 其中的 Ptr 是 &mut future 的结果,即 &mut impl Future.
//
// &mut Pin<Ptr>
(&mut future).await;
}
注意:
-
pin!() 的参数必须是 Future 对象,而不是能是表达式:
async fn my_async_fn() { // async logic here } #[tokio::main] async fn main() { // 错误 // let mut future = pin!(my_async_fn()); // OK let mut future = my_async_fn() pin!(future); (&mut future).await; }
-
pin!{} 支持同时创建多个 Future 对象并 Pin 住:
use tokio::{pin, select}; async fn my_async_fn() { // async logic here } #[tokio::main] async fn main() { pin! { let future1 = my_async_fn(); let future2 = my_async_fn(); } select! { _ = &mut future1 => {} _ = &mut future2 => {} } }
pin!(future) 宏的实现方式等效于 std::pin::Pin::new(&mut future), 它存入 future 对象的 &mut 借用(所以 future 类型需要是 mut 类型),返回一个 Pin<Ptr> 类型的同名值 pinned,其中 Ptr 是 &mut impl Future<Output=xx> 类型:
- pinned.as_ref() 方法返回 Pin<&<Ptr as Deref>::Target> 即 Pin<& impl Future<Output=xx>> 类型;
- pinned.as_mut() 方法返回 Pin<&mut <Ptr as Deref>::Target> 即 Pin<&mut impl Future<Output=xx>> 类型;
pinned.as_mut() 返回的 Pin<&mut impl Future<Output=xx>> 类型对象 实现了 Future trait
:
- 它满足实现 Futre trait 的 Pin<P> 中对 P 的要求: P: DerefMut, <P as Deref>::Target: Future
- &mut XX 实现了 DerefMut,Target 是 XX。
- 该 Future 的 Output 是 Future 的 Output 值 xx。
在 loop+select!{} 宏中,要对 future 对象重复 poll,而 select!{} 在并发 .await 时,如果有返回值,则会 drop 其它 branch 的 Future 对象,所以在 select branch 中不能直接使用 future 对象,以避免它被 drop 后下一次 loop 失败 select 失败。
常见的解决办法是:
- 创建一个 future mut 对象;
- 使用 pin!(future) 来创建一个同名的,但类型是 Pin<&mut impl Future<Output=xx>> 的对象 future;
- 在 select branch 中使用 &mut future 来进行轮询。
async fn action() {
// Some asynchronous logic
}
#[tokio::main]
async fn main() {
let (mut tx, mut rx) = tokio::sync::mpsc::channel(128);
let operation = action(); // 返回一个 Future 对象, 没有对它 .await
// 必须先 Pin 该 Future 对象,创建一个同名的类型为
// Pin<&mut impl Future<Output=i32> 类型对象 ;
tokio::pin!(operation);
loop {
tokio::select! {
// &mut operation 类型是已经 Pin 住类型的 &mut 借用,
// 即 &mut Pin<&mut impl Future<Output=i32>
// 被 .await 表达式所支持, 所以可以被 poll 轮询。
//
// 当该 branch 因不匹配或无返回值而被 drop/cancel 时,operation 任然有效,
// 下一次 select!{} 可以接续使用 &mut operation
_ = &mut operation => break,
Some(v) = rx.recv() => {
if v % 2 == 0 {
break;
}
}
}
}
}
除了 &mut future 外,使用 future.as_mut() 来创建一个
Pin<&mut impl Future<Output=xx>> 类型对象(注意不是 &mut XX 借用对象),该类型
实现了 Future<Output=xx> trait
,常在 loop 循环中使用。
- as_mut() 返回的类型 Pin<&mut impl Future<Output=xx>> 满足
impl<P> Future for Pin<P> where P: DerefMut, <P as Deref>::Target: Future, 中对 P 的要求,所以它实现了 Future.
use std::{
future::Future,
pin::pin,
task::{Context, Poll},
thread,
};
fn block_on<Fut: Future>(fut: Fut) -> Fut::Output {
let waker_that_unparks_thread = {/*.*/};
let mut cx = Context::from_waker(&waker_that_unparks_thread);
// Pin the future so it can be polled.
// 为 fut 显式的创建 Pin 对象, 类型为 Pin<&mut impl Future<Output=xx>>
let mut pinned_fut = pin!(fut);
loop {
// as_mut() 方法返回实现 Future 的 Pin<&mut impl Future<Output=xx>> 类型
// 对象, 故可以 poll
match pinned_fut.as_mut().poll(&mut cx) {
Poll::Pending => thread::park(),
Poll::Ready(res) => return res,
}
}
}
core::pin::pin!(T) 宏创建一个 Pin<&mut T> 对象:
- 如果 T 没有实现 Unpin(如 Future 对象),则该宏将 T 值 pin 到内存中,不允许 move;
- 如果 T 实现了 Unpin,则 Pin<&mut T> 效果和 &mut 类似,mem::replace() 或 mem::take() 可以 move 该值。
Pin 在异步编程中得到广泛应用:
- async/await 生成的 Future 可能包含自引用结构, Future 没有实现 Unpin;
- 编译器将 async 函数转换为状态机时,会产生包含内部引用的结构;
异步运行时如 tokio 或 .await 表达式会 自动处理 Future 对象的 Pin
,然后调用它的
poll 方法:
// Future 执行器通常这样使用 Pin
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>
// 实际使用时,执行器会自动处理 Pin
async fn my_async_fn() -> i32 { 42 }
// tokio 或 await 自动
tokio::spawn(my_async_fn());
但是对于 tokio::select!{} 宏,则由于没有使用 .await 表达式,需要开发者显式的从 Future 对象创建 Pin,然后再调用 poll 方法。
use core::pin::{pin, Pin};
fn stuff(foo: Pin<&mut Foo>) {
// …
}
let pinned_foo = pin!(Foo { /* … */ });
stuff(pinned_foo);
// Manually polling a Future (without Unpin bounds)
use std::{
future::Future,
pin::pin,
task::{Context, Poll},
thread,
};
fn block_on<Fut: Future>(fut: Fut) -> Fut::Output {
let waker_that_unparks_thread = {/*.*/};
let mut cx = Context::from_waker(&waker_that_unparks_thread);
// Pin the future so it can be polled.
let mut pinned_fut = pin!(fut);
loop {
match pinned_fut.as_mut().poll(&mut cx) {
Poll::Pending => thread::park(),
Poll::Ready(res) => return res,
}
}
}
24 Send/Sync #
Send 和 Sync 都是 marker trait:
- Send:对象可以在多个线程中转移 move (也就是对象在多线程间转移具有原子性);
- Sync:可以在多个线程中共享引用对象;
对于 thread 闭包函数,一般使用 move 来转移引用的对象,所以对象类型必须实现 Send。对于 scope thread 的闭包函数,可以不使用 move,它们可以共享引用父 thread 的 stack 变量,所以要求这些对象必须实现 Sync。
绝大部分 Rust 类型,如 Vec/Map/Array 等实现了 Send/Sync,可以被 move 到 thread closure 中。
自定义类型的各成员如果实现了 Send/Sync,则该类型也自动实现了 Send/Sync。
没有实现 Send/Sync 的类型:
- Rc<T> :没有实现 Send 和 Sync(多线程转移 Rc 对象时,不能保证原子性),不能被 move 到 thread closure 中。多线程环境中需要使用 Arc<T> 和它的clone()对象;
- Cell/RefCell/mpsc:Receiver :没有实现 Sync,不能在多个线程中共享引用;
- std::sync::MutexGurad :没实现 Send;