跳过正文

10. 泛型和特性:generic/trait

·
Rust
目录
rust-lang - 这篇文章属于一个选集。
§ 10: 本文

泛型参数
#

类型/函数/方法可以使用泛型参数,用 <CamelCase, ...> 表示。泛型参数可以使用一个或多个 lifetime 和 trait 进行限界:

// 泛型类型:T 为泛型类型名称,名称位于类型名后的 <xx> 中。
// T 没有任何限界,默认为 Sized。
struct Point<T> { x: T, y: T, }

// 使用时,如果未指定泛型参数值,编译器自动推导
let p = Point{
    x: "5".to_string(),
    y : "hello".to_string()
};

// 使用时,为泛型参数指定具体类型,相当于实例化出一个匿名的具体类型
fn gen_spec_t(_s: Point<A>) {}
fn gen_spec_i32(_s: Point<i32>) {}

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 后面需要列出泛型类型所需的所有泛型参数。

struct Point<T, U> {
    x: T,
    y: U,
}

// impl 后需要列出泛型类型的所有泛型参数
impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

函数和方法也可以定义自己的泛型参数, 它们不需要在 impl 后列出,而是在函数名后列出,调用时推导或指定。

impl<T> Val<T> {
    // 方法可以使用 impl 指定的泛型参数
    fn value(&self) -> &T {
        &self.val
    }

    // 方法可以有自己的泛型参数
    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); // 为泛型方法手动指定类型,使用比目鱼语法。

为泛型类型定义关联函数或方法时,可以对泛型参数添加额外的限制:

  • 为泛型类型定义关联函数或方法时,可使用多个 impl bock,但是为它实现 trait 时,只能使用一个 impl block。
// 对 T 无限制,等效于 Sized 限界
struct Pair<T> { x: T, y: T,}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y, }
    }
}

// 为 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);
        }
    }
}

调用泛型函数或方法时,使用比目鱼语法或由编译器自动推导:

// 比目鱼语法
let _ = (1..5).collect::<Vec<i32>>();

// 编译器根据返回值自动推导
let _: Vec<i32> = (1..5).collect();

Rust 在编译时进行泛型代码的单态化(monomorphization)来保障效率。

单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。《== 空间换时间。

泛型 trait
#

trait 可以作为泛型参数的限界约束,也可以作为函数的参数或返回值(impl Traittrait objecct):

  • 匿名函数也可以作为泛型参数的限界约束,例如函数指针 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。

// 泛型 trait,泛型参数 A、B 没有定义限界,默认为 Sized。
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));
    }
}

泛型常量
#

泛型参数也支持常量参数类型,如 <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> {
    // ...
}

// 实例化常量参数,可以直接指定常量值或者使用 {xx} 常量表达式
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>() }>();

    // OK
    let _: [u8; M];
    // Error: const expression contains the generic parameter `T`
    let _: [u8; std::mem::size_of::<T>()];
}

关联类型
#

trait 支持关联类型(使用 type alias 语法), 这些类型在定义 trait 时一般是未知的,而在实现时指定具体类型(对于泛型类型,可能是泛型参数类型)。

impl<T> Deref for Selector<T> {
    // 在实现 trait 时指定具体的类型,但也可以是泛型参数代表的类型 T
    type Target = T;

    fn deref(&self) -> &T { &self.elements[self.current] }
}

trait 函数或方法可以使用 Self::Item 来引用关联类型 Item。

在定义 trait 时,可以为关联类型指定缺省类型或用其它 trait 做限界:

trait Iterator {
    // 声明关联类型,同时对类型进行限界。使用 trait 限界语法。
    type Item: std::fmt::Display;

    fn next(&mut self) -> Option<Self::Item>;
}

pub trait Add<Rhs = Self> {
    // 关联类型也可以指定缺省类型。使用 type alias 语法。
    type Output = Self;

    // Required method
    fn add(self, rhs: Rhs) -> Self::Output;
}

在定义泛型 trait 时,泛型参数和关联类型都可以指定缺省类型, 例如各种运算符重载 trait 都指定了默认类型。

// 定义泛型 trait 时,可以为泛型参数指定缺省类型
pub trait Add<Rhs = Self> {
    // 关联类型也可以指定缺省类型
    type Output = Self;

    // Required method
    fn add(self, rhs: Rhs) -> Self::Output;
}

使用泛型 trait 时,可以同时指定泛型参数和关联类型, 泛型参数只能按顺序来指定,而关联类型可以使用 K=V 来指定:

  1. Add<&int32, Output=int32>
  2. Add<Output=int32>, 有缺省值的泛型参数可以忽略。

注:不能使用 Add<Rhs=&T, Output=T>, 因为 Rhs 是泛型类型参数而不是关联类型参数,K=V 只能用于关联类型参数:

// 定义泛型 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 做限界时,使用 Trait<Item=Type> 为关联类型指定一个具体类型,结果还是一个 trait,可以用它做限界或定义 trait object:

// 使用带关联类型的 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;
}

// 实现 trait 时,为关联常量设置常量值
impl Float for f32 {
    const ZERO: f32 = 0.0;
    const ONE: f32 = 1.0;
}

trait 限界
#

使用 trait bound 的场景:

  1. 泛型参数限界: fn f<A: Copy>() {}
  2. 关联类型限界: trait A { type B: Copy; } 等效于 trait A where Self::B : Copy { type B;}
  3. super trait: trait Circle : Shape + Color {}

如果泛型参数未指定 trait bound,则默认为 Sized。

函数传参匹配 trait 限界时不会隐式进行 type coercion 协变,例如,虽然 &mut i32 可以协变到 &i32 ,但传参时不会协变。

带来的影响:如果 Trait 作为函数参数限界,&i32 和 &mut i32 两种类型 都需要实现该 Trait。这是由于 trait 的方法参数可能是 self 或 &mut self,如果传入 &T,就不能调用这些方法。

最佳实践:

  1. 在为自定义类型实现 Trait 时,一般为三种类型 T, &T, &mut T 都实现同一个 trait。
  2. 或者,在 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`

函数传参匹配 trait 限界时也不会自动通过 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 类型

实现泛型 trait
#

使用 impl<T> 的两个场景:

  1. 为自定义类型的定义泛型的方法或关联函数:impl<T> MyStruct<T> {},需要指定泛型类型所有的泛型参数;
  2. 为自定义类型类型实现泛型 trait: impl<T> MyTrait<T> for MyStruct<T> {}

在使用 impl<T> 为泛型类型定义方法或函数时,如果为泛型类型的各泛型参数指定了具体类型,则可以忽略 impl 后的 :

struct GenericVal<T>(T);

// 对于泛型类型,在 impl 时:
// 1. 如果指定了泛型参数类型,则不需要为 impl 指定泛型参数
impl GenericVal<f32> {}
// 2. 未指定具体类型时,必须在 impl<T> 中指定该泛型类型的所有泛型参数
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 的关联函数或方法,或为自定义类型实现 trait 时,该 trait 或 type 必须是本 crate 定义的类型,这个限制称为 Orphan Rule ,这是为了避免破坏开发者原来的定义。

但是,Rust 为泛型 trait 放宽了 Orphan Rule 条件限制 :可以为其它 crate package 中定义的泛型 trait 添加方法,或为自定义泛型类型实现 trait ,这种实现称为 blanket impl

  • impl<T> 指定 trait 或自定义类型使用的所有泛型参数,其中自定义类型也可以是泛型参数类型(blanket implement),如 impl<T, U> MyTrait<T> for U {};

通过 blanket implement,可以对已知或未知的类型实现 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
    }
}

super trait
#

trait 可以有多个父 trait,在实现该 trait 的同时也必须实现这些父 trait,父 trait 也支持 lifetime 限界。

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;
}

完全限定方法调用
#

类型可以实现不同 crate package 中的 trait,它们可以有相同或不同的方法,所以在该类型对象上调用 trait 方法时,必须先将该 trait 引入作用域,否则 Rust 不知道该方法是哪一个 trait 的实现。

std::prelude::v1 moudle 中的类型、trait、macro 会被自动导入到所有 std Rust 程序中,如 std::convert::{AsRef, AsMut, Into, From} 等,所以在调用 into()/from()/as_ref()/as_mut() 方法前不需要手动导入这些 trait。

// 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);
}

TraitName::method(object) 调用方式只适合于方法而非关联函数:它们没有 self 参数,Rust 不能推导出调用哪一个类型实现的该关联函数,这时需要使用完全限定方法调用。

完全限定语法(Fully Qualified Syntax),如 <Human as Pilot>::fly(&person),明确指定对象 person 对应的类型 Human 实现的 Pilot trait 的 fly 方法。

如果类型实现的多个 trait 有同名方法而且这些 trait 都在作用域,则需要使用完全限定语法来指定要调用那个 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 对象对应的 S1 类型实现的 CalSum trait 的 sum 方法或关联函数。
    println!("Results: {}", <S1 as CalSum>::sum(&s1, 1)); // OK
}

在没有歧义的情况下, 可以直接调用对象实现的 trait 关联函数或方法, 而不需要使用完全限定调用方式:

let i: i8 = Default::default(); // default() 为 Default trait 的关联函数
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() };
}

trait object
#

参考:https://quinedot.github.io/rust-learning/dyn-trait.html

dyn TraitName 称为 trait object,它代表一个类型,表示实现 TraitName 的任意类型对象。

trait object 类型和泛型参数的差别:

  1. 泛型参数:编译器推断并绑定一具体类型,如 Vec<T: Display> , 只能保存一种实现 Display trait 的类型元素;
  2. trait object: 不和某一类型绑定,如 Vec<&dyn Display> 可以保存各种实现 Display trait 的类型对象;

trait object 可以指定 trait 或 lifetime 做限界,但有如下限制:

  1. 除了第一个 trait 外,剩余用作限界的 trait 只能是 auto trait 类型,包括:Send, Sync, Unpin, UnwindSafe 和 RefUnwindSafe,但不包含 ?Sized,Hash,Eq 等 trait。
  2. 最多指定一个 lifetime;
// 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
dyn eviction::EvictionManager + Sync

trait object 的第一个 trait 必须是对象安全(object safe)的,需要满足如下要求:

  • Trait 方法不返回 Self;
  • Trait 方法没有任何泛型参数;

注:新 Rust 版本的 object safe 也称为 Dyn Compatibility。

这是由于 trait object 在编译时是不知道自己具体类型的,而是在运行时动态派发。如果方法返回 Self,则编译时编译器不能正确检查返回值合法性。

同理,由于在运行时才能确定 trait object 的具体对象类型,所以在编译时是不能确认泛型参数的对应的实现类型,所以编译器也不能正确检查。

解决办法:修正 Self 类型,如 fn splice(&self, other: &Self) -> Self 修正为 fn splice(&self, other: &dyn MyTrait) -> Box<dyn MyTrait>

trait object 在编译时类型和大小不确定, 需要使用 &dyn TraitNameBox<dyn TraitName> 表示, 它们都是胖指针,包含两个字段:指向动态对象内存的指针 + 该对象实现 trait 定义的各种方法的虚表(vtable) 的指针。

  • &dyn TraitName 使用实现 TraitName 的对象引用赋值:let n: &dyn TraitName = &value
  • Box<dyn TraitName> 使用 Box::new(value) 赋值:let b: Box<dyn TraitName> = Box::new(value)
    • 不能直接将 value 赋值给 Box<dyn TraintName>

使用 &dyn Trait 时,如果要指定 Send/lifetime 等,则需要使用 &mut (dyn Trait + Send + 'a) 表达式,否则 Rust 报错“加号有歧义“:

// 使用 & 时必须将 dyn 和后续的限制用括号括起来,否则会因歧义而报错。
&(dyn MyTrait + Send)

// 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));

当参数类型是 &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>>) {

    // 错误:不能通过借用(共享或可变)来转移对象,my_trait 是 Box<dyn MyTrait + Send + 'static> 类型,转移了 Box 对象所有权
    // error[E0507]: cannot move out of `*say_hello` as enum variant `Some` which is behind a shared reference
    // 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 是 unsized 的, 所以编译报错。

解决办法:在返回值中指定 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);

trait object 缺省 lifetime
#

dyn Trait 的 lifetime 规则:

  1. Box<dyn Trait> 等效于 Box<dyn Trait + 'static> ,因为 Box 接收 trait object 的所有权,所以是 ‘static;
  2. &'x Box<dyn Trait> 等效于 &'x Box<dyn Trait + 'static>;
  3. &'a dyn Foo 等效于 &'a (dyn Foo + 'a) ,因为 &'a T 隐式声明了 T: 'a
  • &'a (dyn Foo+'a) 表示 dyn Foo 的 trait object 的 lifetime 要比 ‘a 长,同时它的引用也要比 ‘a 长。
  1. &'r Ref<'q, dyn Trait> 等效于 &'r Ref<'q, dyn Trait+'q> ;

1 和 2 说明 Box 中的 dyn Trait 生命周期始终为 ‘static,这是因为:使用 Box::new(value) 赋值, 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>,
}

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());
}

trait object 的 lifetime 消除规则
#

参考: lifetime-elision.html

两个 dyn trait 的 trait、lifetime 如果相同则类型互为别名,如:dyn Trait + Send + UnwindSafedyn Trait + UnwindSafe + Send 相同。

trait object 作为类型,与函数参数或返回值中的借用生命周期消除机制类似,trait object 也支持生命周期消除,也即在定义 trait object 类型时, 可以不指定 lifetime,Rust 编译器自动推导。

下面这些 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,则必须为 trait object 明确指定 liftime,如 T7;
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
// 首先检查 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> 内部要求 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,
}

// 表示实现 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 {}

如果 Rust 编译器不能正确推导出 trait object 的 lifetime,则会报错,这时需要人工指定:

// 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>;

如果 trait object 不是作为 type argument,而用于赋值,则使用对应目标类型的 trait bound 来决定 trait object 的 lifetime:

  1. 如果目标 trait 只有一个 lifetime,则使用它;
  2. 如果目标没有 lifetime,则使用 ‘static 或从 expression 推导;

impl trait
#

impl TraitName 或 &impl TraintName 和泛型类型参数类似, 但在编译时被实例化为一种特定类型, 不支持运行时动态派发。

它的主要使用场景是简化泛型参数约束的复杂性,如函数返回一个复杂的多种嵌套迭代器时,该类型可能只有编译器才能准确写出来,这时可以用 impl TraitName 来简化返回参数类型。

例如,将 iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> 简化为 impl Iterator<Item=i32>

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 TraitName 被编译器实例化为具体类型而不是 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 );
}

作为函数参数或返回值时,一般需要指定 'static lifetime

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) 报错:
    // the parameter type `impl App` may not live long enough,the parameter type `impl App` must be valid for the static lifetime...
    //
    // 这是因为 Box::new(app) 会转移 app 的所有权,所以 app 必须是 'static
    fn add(&mut self, name: &str, app: impl App + 'static) {
        self.apps.insert(name.to_string(), Box::new(app));
    }
}

由于 impl Trait 是一种编译时静态派发形式,在编译时只能被单态化为一种类型,否则报错:

// 错误:不兼容的类型
fn make_shape(shape: &str) -> impl Shape {
    match shape {
        "circle" => Circle::new(),
        "tiangle" => Triangle::new(),
        "shape" => Retangle::new(),
    }
}

由于 Fn/FnOnce/FnMut 也是 trait,所以函数可以返回 impl Fn() -> i32Box<dyn 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 // 栈分配,性能好
}

Sized/?Sized
#

Rust 为所有固定大小类型都自动实现了 std::marker::Sized trait, 用户不能自己为类型实现该 trait,它是一个标记 trait。

它的唯一用途是作为泛型类型的限界,如 T: Sized 表示 T 类型在编译时必须为大小已知(固定)的类型。

Sized 是 Rust 的隐式默认值,即 struct S<T> 的 T 未加任何限界, 则 Rust 解释为 struct S<T: Sized>

struct S<T: ?Sized>, 表示 T 可以是无固定大小的类型。

Rust 的动态大小(unsized)类型:

  1. str
  2. [T];
  3. dyn trait;

对于动态大小类型,Rust 不能将它们作为变量或函数参数,而是需要转换为固定大小的引用或固定大小的 Box,如:

  1. &str, Box<str>;
  2. &[T], Box<[T]>;
  3. &dyn trait, Box<dyn trait>;

PhantomData
#

PhantomData 是一个不占用内存的泛型 struct 类型, 唯一值是 PhantomData

pub struct PhantomData<T> where T: ?Sized;

常用在一些特殊场景来避免编译错误:

  1. lifetime:

       // Error: 没有 field 使用 'a
       struct Slice<'a, T> {
           start: *const T,
           end: *const T,
       }
    
       // OK: 用 0 长字段的 PhantomData 使用 'a
       use std::marker::PhantomData;
       struct Slice<'a, T> {
           start: *const T,
           end: *const T,
           // 隐式表示 T: 'a, 即 T 对象的引用的生命周期要比 'a 长。
           phantom: PhantomData<&'a T>,
       }
    
  2. 隐藏数据: PhantomData 类型不占用内存,可以用来隐藏字段。

       use std::marker::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);
       }
    
  3. 编译时类型检查:

       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>);  // 使用泛型参数 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;
       }
    

Copy/Clone
#

Copy 是一个标记 trait(marker trait), 是 Clone 子 trait, 在实现 Copy 的同时也必须实现 Clone:

pub trait Copy: Clone { }

Copy 和 Clone 的差别:

  1. Copy 是编译器隐式调用的(如变量赋值, 传参等), 不能被用户自定义实现或显式调用, 一般是栈内存的直接拷贝(simple bit-wise copy,共享堆内存);
  2. Clone 是需要显式调用的, 如 x.clone(), 在具体实现时可能会拷贝堆内存;

默认实现 Copy 的情况:

  1. 基本类型, 如整型, 浮点型, bool, char;
  2. Option, Result<T,E>, Bound, 共享借用类型 &T
  3. 数组 [T;N], 元组 (T, T): 前提: 元素类型 T 实现了 Copy;

没有实现 Copy 的情况:

  1. 自定义 Struct/Enum/Union 类型;
  2. 容器类型,如 std::collections::Vec/HashMap/HashSet; (但都实现了 Clone)
  3. &mut T(但 &T 实现了 Copy)
  4. 实现了 Drop 的类型(因为编译器认为该类型有自己的资源或状态管理机制)
  5. 各种智能指针,如 Box/Rc/Arc/Ref

可以使用 derive 宏来自动实现 Copy 和 Clone trait:

// OK: i32 实现了 Copy, 实现 Copy 的同时必须实现 Clone
#[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,
}

// 手动实现 Copy 和 Clone trait
struct MyStruct;

impl Copy for MyStruct { }

impl Clone for MyStruct {
    fn clone(&self) -> MyStruct {
        *self
    }
}

另外,下列类型也实现了 Copy trait:

  • fn 函数定义;
  • fn 函数指针类型,如 fn() -> i32;
  • 闭包类型:前提它没有捕获环境中的对象,或者捕获的对象都实现了 Copy;
    • 共享借用 &T 实现了 Copy
    • 可变借用 &mut T 没有实现 Copy

Default
#

按惯例, 自定义类型使用 new() 关联函数来作为类型的构造函数。Rust 并没有强制所有类型都实现该方法, 但它提供了 Default trait, 各类型可以实现它:

pub struct Second {
    value: u64,
}

impl Second {
    pub fn value(&self) -> u64 {
        self.value
    }
}

impl Default for Second {
    // 需要定义 default() 关联函数
    fn default() -> Self {
        Self { value: 0 }
    }
}
// 后续创建缺省对象
let sec: Second = Default::default();
let sec: Second = <Second as Default>::default();

Rust 基本类型和常用类型都实现了 Default trait。

如果各成员类型都实现了 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() // 返回一个缺省的 MyConfiguration 类型对象,然后用它来填充其它字段
    };
    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 作为泛型类型的定界, 在标准库中得到广泛使用, 例如:

  1. Option::unwrap_or_default() 方法;
  2. std::mem::take(&mut T) 将 T 的值 move 出来,然后将原始内存值用 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);

Drop
#

Drop trait 为对象提供自定义析构能力,在对象离开作用域时由编译器自动调用它来释放资源。

Box, Vec, String, File 和 Process 等类型都实现了 Drop。

不能手动调用对象的 drop() 方法来释放资源,这样会导致对象离开作用域被二次释放。但是可以调用 drop(obj)std::mem::drop()/forget() 来手动释放对象,这样后续编译器不会再自动释放它。

fn bar() -> Result<(), ()> {

    struct Foo;

    impl Drop for Foo {
        fn drop(&mut self) {
            println!("exit");
        }
    }

    let _exit = Foo;
    baz()?;
    Ok(())
}

如果类型实现了 Drop,则不能再实现 Copy。反之,如果类型实现了 Copy,则不能再实现 Drop。

From/Into
#

From/Into trait 都会转移对象所有权到结果对象中:

trait Into<t>: Sized {
    fn into(self) -> T;
}

trait From<T>: Sized {
    fn from(other: T) -> Self;
}

自定义类型一般只需实现 From trait,Rust 会自动生成 Into trait:

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 trait 一般作用在泛型函数的限界场景中:

pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command

From trait 在以下场景被自动调用:

  1. Rust 为任何实现了 From<T> trait 的类型自动实现 Into<U> trait

       let from_type: FromType = FromType::new();
       let into_type: IntoType = from_type.into(); // 自动调用 From<FromType> for IntoType 的实现
    
  2. 使用 ? 运算符处理 ResultOption 错误在返回错误时,将错误类型转换为函数返回的错误类型。

       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>()?)
    }
    
  3. 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();
    
  4. std::convert::from 函数会显式调用 From trait:

       let from_type = FromType::new();
       // 显式调用 From<FromType> for IntoType 的实现
       let into_type:IntoType = std::convert::From::from(from_type);
    
  5. 变量赋值和函数返回值: 如 &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>> {
           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();
           // ...
       }
    

FromStr/ToString
#

FromStr trait 从字符串来生成类型值。

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());
}

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 迭代的要求,即类型要实现 IntoIterator 才能被 for-in 迭代

  • 编译器为 for item in expressexpress 结果对象隐式调用 into_iter() 方法,返回一个迭代器,然后进行迭代。

编译为任意实现了 Iterator trait 的类型自动实现 IntoIterator trait:

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator<Item = Self::Item>;

    // Required method
    fn into_iter(self) -> Self::IntoIter;
}

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 扩充自己。

AsRef/AsMut
#

AsRef<T>/AsMut<T> 多用于泛型参数限界,如: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>

运算符重载 trait: /PartialEq/PartialOrd/Eq/Ord
#

std::ops 和 std::cmp module 定义了各种运算符重载 trait:

Operator Trait
+ Add
- Sub
* Mul
% Rem
== and != PartialEq
<, >, <=, and >= PartialOrd

Rust 为绝大部分基本类型实现了这些 trait:

// PartialEq 只需要实现 eq() 方法
pub trait PartialEq<Rhs = Self> where Rhs: ?Sized,
{
    // Required method
    fn eq(&self, other: &Rhs) -> bool;

    // Provided method
    fn ne(&self, other: &Rhs) -> bool { ... }
}

// Eq 是 PartialEq 的子 trait;
pub trait Eq: PartialEq { }

// PartialOrd 是 PartialEq 的子 trait
// `PartialOrd` 只需要实现 `partial_cmp()` 方法:
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/Eq/Ord 的实现:

#[derive(PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: String
}

也可以手动实现这些 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,
        }
    }
}

其它运算符:

  1. i..j, i.., ..j, i..=j 等:是各种 struct RangeXX 类型语法糖;
  2. ? :适用于 Result 和 Option,和 From/Into trait 协助进行自动类型转换;
  3. *val:使用 Deref/DerefMut trait 进行重载;(用于实现智能指针)
  4. a[i]: 使用 Index/IndexMut trait 进行重载;

Index/IndexMut
#

类型实现了 std::ops::Index/IndexMut trait 后,可以使用 a[i] 操作来获取和设置值。

a[i] 调用 a.index(i) 方法后再自动解引用返回对象,所以等效于 *a.index(i) 或 *a.index_mut(i)

  • 返回对象的好处是可以作为左值使用,例如 a[i] = 3
  • 方法调用的优先级比 * 高,所以 *a.index(i) 等效于 *(a.index(i));

Rust 编译器根据 a[i] 索引表达式所在的上下文自动选择调用 index() 或 index_mut() 方法。

Index/IndexMut trait 对 i 的类型没有限定,可以是 usizea[i], 或 Range<usize>a[i..j] 等,取决于类型实现 Index/IndexMut 时指定的 index 值类型。

i..j, i.., ..j, i..=j 等是 struct RangeXX 语法糖: Rust 根据 a[xx] 操作中的 xx 类型,自动生成对应的 struct RangeXX 类型值:

  • 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

Borrow/ToOwned/Cow
#

std::borrow::Borrow<T> 可以从自身创建一个 &T,但要求 &T 类型必须和自身类型以相同的方式进行哈希和比较。Rust 编译器并不会强制检查该限制,但是 Borrow 有这种约定的意图。

Borrow 用于泛型哈希表等关联集合类型:K: Borrow<Q> 表示可以从 K 对象生成 &Q,而且 K 和 Q 都是使用相同的 Eq + Hash 语义。

实现 Borrrow 一般是 owned 类型,如 String/PathBuf/OsString/Box/Arc/Rc 等, borrow 的结果是对应 unsized 类型的借用, 如 &str/&Path/&OsStr/&T;

HashMap 的 get() 方法传入任何满足上面两个约束的引用对象。

  • get() 方法的实现从自身 K.borrow() 生成 &Q 对象,然后用 K 的 Eq+Hash 值与 Q 的 Eq+Hash 值进行比较;
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 实现了 Borrow<str>, 它们具有相同的 Hash 语义,所以 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
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();

CloneToOwned trait 区别:

  1. 相同点: 两者都可以从 &self -> Self, 也即 &T -> T;
  2. 不同点: Clone 只能实现 &T -> T 的转换, 而 ToOwned trait 可以更灵活, 转换后的对象不局限于 T,只要 Owned 类型实现了 Borrow<Self> 即可。例如 String 实现了 Borrow, 则 &str 就可以转换为 Owned 类型为 String 的对象。

std::borrow::Cow 是可以保存 &BB 的 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>
impl<'a> From<OsString> for Cow<'a, OsStr>
impl<'a> From<PathBuf> for Cow<'a, Path>
impl<'a> From<String> for Cow<'a, str>
impl<'a, T> From<Vec<T>> for Cow<'a, [T]> where T: Clone

如果 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;
        }
    }
}

let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]); // 保存 &[i32]
// slice 元素都大于 0,所以不会 COW
abs_all(&mut input);


let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
// slice 有小于 0 的元素,所以会被 COW
abs_all(&mut input);

let mut input = Cow::from(vec![-1, 0, 1]);
// 不会 COW,因为 input 保存的就是 owned 类型 Vec<i32>
abs_all(&mut input);

// 在 struct 中使用 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 {
        // 如果把返回值改成 String,那么在 else 分支会有一次额外的拷贝。
        s
    }
}


// 可以使用 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
    }
}

Deref/DerefMut
#

Defer 主要使用场景是智能指针(smart pointer),例如 t=Box<U>,可以对 t 进行 *t&t 操作:

  1. *t 返回 U,等效于 *Deref.deref(&t);
  2. &t 返回 &U, 等效于 Deref.deref(&t);
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;
}

对于实现了 Deref<Target=U> 的类型 T 值, &*T 返回 &U,也就是 Deref 重载了 * 运算符, 所以 *T == *t.deref(), 返回 U 对象, 为了获得 &U 可以使用表达式 &*T;

在需要对象的 &mut 引用时,如方法调用或变量赋值时,Rust 检查对象是否实现了 DerefMut trait,如果实现了,则自动调用(mutable deref coercion) 它来实现转换。 所以 *v 作为左值的场景,Rust 使用 DerefMut<Target=U> 来对 * 操作符 进行重载,相当于用生成 U 类型对象来进行赋值。

Rust 自动调用 Deref trait 来满足赋值或传参类型的隐式转换,但是如果函数的参数是泛型限界,则 Rust 不会通过 Deref trait 来解引用来满足泛型参数限界的要求。两种解决办法:

  1. 使用 as 类型转换;
  2. 手动解引用 &*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

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>

  1. * 操作符解引用后类型都是 T,实际执行的操作为:*(v.deref()) ;
  2. 在需要 &T 的地方都可以传入 &Ref/&Rc/&Box 类型;
  3. Ref<T>/Box<T> 可以调用 T 定义的所有方法;

由于智能指针都可以调用 T 的方法, 为了避免指针的方法和 T 的方法冲突, 在调用智能指针自己的方法或实现的 trait 时使用全限定名称:

use std::sync::Arc;

let foo = Arc::new(vec![1.0, 2.0, 3.0]);
// 以下两个方法调用等价,但是建议使用第二种方式: 全限定名称的方法或关联函数调用
//
// foo 是 Arc<Vec<f64>> 类型,Arc 定义了 clone() 方法,而 Vec<f64> 也定义了 clone 方法
// 这里有限调用的是 Arc 的 clone() 方法
let a = foo.clone();
// 建议:明确调用智能指针的方法
let b = Arc::clone(&foo);
let my_weak = Arc::downgrade(&my_arc); // 完全限定语法

String/Vec<T> 也是 smart pointer, 只不过是专用的,它们占用 3 个机器字栈空间+可变长堆空间:

  1. String 实现了 Deref<Target = str>;
  2. Vec 实现了 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,最佳实践是 T 实现 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`

Rc/Arc
#

Rc<T>Box<T> 类似,也是在堆上分配内存,并拥有它,但是:

  1. Rc::clone(&a) 增加 a 引用计数,并不会重新分配堆内存;
  2. Rcclone() 返回的 Rc 对象都被 drop 后,a 对应的堆内存才会被释放;
  3. Rc 值是只读的,所以可以和 Cell/RefCell 结合使用,如 Rc<Cell<T>>,使用内部可变性机制来修改共享对象 T;

Rc<T> 主要使用场景是需要多 owner 的情况,例如:

  1. 一个链接表对象被多个其他链接表共享引用;
  2. 在多线程中共享大的数据,避免内存拷贝;
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() 方法来修改对象。

  • pub fn get_mut(this: &mut Rc<T, A>) -> Option<&mut T>: 当没有其他 Rc 对象引用 this 时, 也就是 this Rc 对象的引用计数为 1 时, 返回 &mut T, 否则返回 None。
  • pub fn make_mut(this: &mut Rc<T, A>) -> &mut T : 要求 T 对象实现 Clone trait,该方法在强引用计数 >=1 时, clone 产生一个新的 Rc 对象并替换 this,这被称为 clone-on-write, 然后返回它的 &mut T
    • 该方法在弱引用计数 >=1 时,不会 clone,而是打破弱引用计数对象引用的 T 对象,然后返回 &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;
let mut other_data = Rc::clone(&data);
// 由于 data 被 clone 过, 再次调用 make_mut() 时会 clone。
// 一个新 Rc 对象并替换 data,这时 other_data 的引用计数 2 变为 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);

pub fn into_inner(this: Rc<T, A>) -> Option<T> : 消耗传入的 Rc(其他类型的 into_inner() 方法都是类似的语义), 如果 Rc 的强引用计数 == 1, 则返回 inner value, 否则返回 None。

  • 各种包装器类型,如 Box<T>,Cell<T>,RefCell<T>, 都提供了 into_inner() 方法,如:
// 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,从而发生内存泄露:

https://img.opsnull.com/blog/20250215151310463-rc.png

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 对象数量。
  • Rc 并不会在执行清理时要求 weak_count() 为 0;
  • 一旦强引用计数为 0,任何由弱引用组成的循环就会被打破,因此弱引用不会造成循环依赖
  • 由于 Weak 引用的值可能会被释放,可以使用 weak.upgrade() 方法来验证 weak 指向的值是否存在,返回一个 Option<Rc>, 在 Rc 值存在时返回 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;

// Arc 常和内部可变性类型联用,实现全局对象的并发访问和修改
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:?}");
    });
}

auto trait
#

由于是 auto trait,除非 Rust 显式的标记类型没有实现 Send/Sync (使用 impl !Send 或 impl !Sync 语法),自定义类型都实现了 Send/Sync,例外情况:trait 定义和 trait object 需要手动标记实现 Sync + Send(见后文)。

Rust 编译器 auto trait 定义和特殊特性: https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits

The Send, Sync, Unpin, UnwindSafe, and RefUnwindSafe traits are auto traits. Auto traits have special properties.

If no explicit implementation or negative implementation is written out for an auto trait for a given type, then the compiler implements it automatically according to the following rules:

  • &T, &mut T, *const T, *mut T, [T; n], and [T] implement the trait if T does.

  • Function item types and function pointers automatically implement the trait.

  • Structs, enums, unions, and tuples implement the trait if all of their fields do.

  • Closures implement the trait if the types of all of their captures do. A closure that captures a T by shared reference and a U by value implements any auto traits that both &T and U do.

For generic types (counting the built-in types above as generic over T), if a generic implementation is available, then the compiler does not automatically implement it for types that could use the implementation except that they do not meet the requisite trait bounds. For instance, the standard library implements Send for all &T where T is Sync; this means that the compiler will not implement Send for &T if T is Send but not Sync.

Auto traits can also have negative implementations, shown as impl !AutoTrait for T in the standard library documentation, that override the automatic implementations. For example *mut T has a negative implementation of Send, and so *mut T is not Send, even if T is. There is currently no stable way to specify additional negative implementations; they exist only in the standard library.

Auto traits may be added as an additional bound to any trait object, even though normally only one trait is allowed. For instance, Box<dyn Debug + Send + UnwindSafe> is a valid type.

总结:Rust 为类型自动实现 Auto Trait 的规则如下:

  1. &T, &mut T, *const T, *mut T, [T; n], and [T] 是否实现 Auto Trait,取决于 T 是否实现对应的 Auto Trait;
  2. Structs, enums, unions, and tuples 是否实现 Auto Trait,取决于它的所有成员类型是否实现对应的 Auto Trait;
  3. 函数指针自动实现 Auto Trait;
  4. 闭包是否实现 Auto Trait,取决于它捕获的所有对象,即借用捕获 &T/&mut T 或转移捕获的 U,是否都实现了对应的 Auto Trait;

对于泛型类型,如果 Rust 定义了明确的实现规则,则 Rust 不再自动为对应类型实现 Auto Trait。例如:

  • 标准库定义了 impl<T> Send for &T where T: Sync + ?Sized 规则,则 &T 是否实现 Send 取决于 T 是否实现 Sync
  • 标准库定义了 impl <T> !Send for *mut T 规则,所以所有裸指针都没有实现 Send;

而 T 是否实现 Sync,除了编译器明确使用 impl !Sync for T 明确定义的类型(一般是 Rc 和 Cell 等内部可变性类型)和 trait object 外,其它类型都自动实现 了 Sync。

总结下借用的 Sync 和 Send 规则:

  • &T and &mut T are Sync if and only if T is Sync 《– 上面第一条规则;
  • &T is Send if and only if T is Sync 《– 上面 Rust 显式定义的规则 impl<T> Send for &T where T: Sync + ?Sized
  • &mut T is Send if and only if T is Send 《— 上面第一条规则

由于 trait 和 trait object 默认都没有实现 Send 和 Sync,需要显式的来指定它是否实现了 Auto Trait,如 Box<dyn Debug + Send + UnwindSafe>


为了将一个 struct、enum、union 自定义类型标记为实现 Send 或 Sync,可以:

  1. 使用不占用任何空间的 PhantomData 类型:
use std::marker::PhantomData;

// 如果只有一个 handle 字段,则 struct X 实现了 Sync 和 Send
// 如果添加了 _not_sync 字段,则 struct X 没有实现 Sync,但实现了 Send
struct X {
    handle: i32,
    _not_sync: PhantomData<Cell<()>>,
}
  1. 或则明确为类型实现这两个标记 trait:
// 裸指针没有实现 Send 和 Sync,所以 struct X 默认没有实现 Send 和 Sync
struct X {
    p: *mut i32,
}

// 明确为 struct X 添加 Send 和 Sync 实现
unsafe impl Send for X {}
unsafe impl Sync for X {}

Send/Sync
#

在多线程场景中,提交的闭包必须实现 Send + 'static,闭包返回值也必须实现 Send + 'static:

  • Send 意味着闭包匿名类型中的对象(参数传入、上下文捕获)可以在多线程间安全转移;
  • ‘static 意味着闭包匿名类中的借用(参数传入、上下文捕获)必须在程序运行期间一直有效;
// std::thread::spawn() 函数的闭包和返回值都需要实现 Send 和 'static
pub fn spawn<F, T>(f: F) -> JoinHandle<T>
where
    F: FnOnce() -> T + Send + 'static, // 注意:Send + 'static 是对闭包 FnOnce() -> T 的整体要求
    T: Send + 'static,

闭包优先使用 &T 和 &mut T 方式捕获上下文中的对象,而这些借用很难满足 ‘static 的要求,故一般使用 move 闭包类型,将对象的所有权转移到闭包中。

但对于那些没有实现 Send 的类型,如 Rc、裸指针(*const、*mut)类型,即使使用 move 将所有权转移到闭包中,该闭包对应的匿名类型也未实现 Send,也不满足限界要求。

  • Rc 的替代方案是使用实现了 Send 的 Arc 类型。

另外,在异步场景,Runtime 的各种 spawn*(future) 函数提交的 future 对象,也需要实现 Send+'static:

  • spawn(future):多线程执行,future 需要实现 Future+Send+'static
  • spawn_local(future): 单线程执行,future 需要实现 Future+'static
  • spawn_blocking(closure): 多线程执行,closure 是实现 Send+'static 的同步闭包;
// tokio::task::spawn() 函数的 Future 对象和返回值也需要实现 Send 和 'static
pub fn spawn<F>(future: F) -> JoinHandle<F::Output> 
where
    F: Future + Send + 'static,
    F::Output: Send + 'static,

和闭包一样,async block/closure 也优先使用 &T 和 &mut T 方式捕获上下文中的对象,而这些借用很难满足 ‘static 的要求,故一般使用 async move 的 block 和 closure 方式。


Send 和 Sync 都是编译器自动为类型实现的 auto trait, 用于标记类似对象是否可以在线程间安全转移(Send)或共享(Sync):

  • Send:对象(的所有权)可以在多个线程中转移(对象所有权转移具有原子性);
  • Sync:它们的共享借用 &T 可以被在多个线程中共享,也即如果类型 T 实现了 Sync,则意味着 &T 可以安全地在多个线程间共享,也即它的 &T 类型实现了 Send。

没有实现 Send 的类型(需要编译器显式的使用 impl !Send for XX 来定义,否则就是自动实现) :

  1. Rc<T>: 没有实现 Send 和 Sync(多线程转移 Rc 对象时,不能保证原子性),不能被转移到 thread closure 中。
  • 多线程环境中需要使用 Arc<T> 和它的 Arc::clone() 后的新对象。同时对于 T 要实现 Send、Sync trait 后,Arc 才实现 Send、Sync。
  1. std::syn 下的 MutexGurad,RwLockReadGuard,RwLockWriteGuard :没有实现 Send,所以只能在创建它的线程中使用;

  2. 裸指针 *const/*mut T:默认都没有实现 Send 和 Sync;

// 引用计数类型,如 Rc/Ref/RefMut, 没有实现 Send(但是 Arc 实现了 Send)
impl<T, A> !Send for Rc<T, A> where A: Allocator T: ?Sized
impl<T, A> !Send for UniqueRc<T, A> where A: Allocator T: ?Sized
impl<T, A> !Send for std::rc::Weak<T, A> where A: Allocator T: ?Sized
impl<'b, T> !Send for Ref<'b, T>
impl<'b, T> !Send for RefMut<'b, T>

// 裸指针类型都没有实现 Send
impl<T> !Send for *const T where T: ?Sized
impl<T> !Send for *mut T where T: ?Sized

&T/&mut T 是否实现 Send,取决于 T 类型:

  • 如果 T 实现了 Sync,则 &T 实现了 Send; (Rust 显式定义的规则 impl<T> Send for &T where T: Sync + ?Sized
  • 如果 T 实现了 Send,则 &mut T 实现了 Send;

所以,除了 Rc/Ref/RefMut、Cell、裸指针等上面显式使用 impl !Send for XX 定义的 XX 类型,Rust 编译器为其它绝大部分类型都自动实现了 Send。

没有实现 Sync 的类型,一般是 Rc 和具有内部可变性的各种 Cell 类型:

  1. Rc<T>
  2. Cell/RefCell/OnceCell/std::sync::mpsc:Receiver :没有实现 Sync,不能在多个线程中共享引用,需要使用 Mutex、RwLock、AtomicXX 类型代替;
  3. 裸指针 *const/*mut T 没有实现 Sync

另外,trait object 类型默认没有实现 Send、Sync,需要人工标记。

std::sync::mpsc:Receiver 没有实现 Sync,意味着不能能把同一个 Receiver 的借用(&Receiver<T>&mut Receiver<T>)在多个线程间共享,而只能由一个 线程来消费它:

  • Receiver<T> 实现了 Send,所以你可以把所有权转移到另一个线程,让一个线程独占地消费消息。
use std::sync::mpsc::channel;
use std::thread;

let (tx, rx) = channel::<i32>();
let rx1 = &rx;
let rx2 = &rx;

// 编译报错:Receiver<i32> 没有实现 Sync
thread::spawn(move || {
    rx1.recv().unwrap();
});
thread::spawn(move || {
    rx2.recv().unwrap();
});


// OK 的情况:
use std::sync::mpsc::channel;
use std::thread;

let (tx, rx) = channel::<i32>();

thread::spawn(move || {
    rx.recv().unwrap();
});

如果要在多个线程消费信息,可以:

  • Arc<Mutex<Receiver<T>>> 包裹(但这样会有锁竞争,且官方文档不推荐)。
  • 更推荐用 crossbeam-channel 这样的库,它的 Receiver 支持多线程消费。

除了多线程场景需要考虑 Send、Sync 外,在 async 异步场景,如各种 spawn()、spawn_*() 等函数,也需要提交的 Future 对象实现 Send

  • spawn(future):多线程执行,future 需要实现 Future+Send+'static
  • spawn_local(future): 单线程执行,future 需要实现 Future+'static
  • spawn_blocking(closure): 多线程执行,closure 是实现 Send+'static 的同步闭包;

其中 future 可以是 async block/fn/closure:

  • 一般使用 async move block/closure 来将上下文中对象所有权转移到 future 中,从而满足 ‘static 要求;

async block/fn/closure 不满足 Send 的常见情况:

  1. 跨 .await 使用 Rc 对象、MutexGuard 对象;

闭包实现 Send/Sync
#

参考前面的 Auto Trait 一节。

闭包对象是否实现 Send/Sync,取决于该比闭包捕获(或借用)的上下文对象,即借用捕获 &T/&mut T 或转移捕获的 U,是否都实现了对应的 Auto Trait;

  • 对于闭包,编译器为它生成实现 Fn/FnOnce/FnMut trait 的匿名类型,闭包对象是该匿名类型的一个值。

  • 闭包优先使用 &T/&mut T 捕获上下文对象,虽然一般情况下满足 Send 要求(例外是 Rc 类型对象),但是很难满足 'static 要求,所以需要使用 move 将上下文 对象转移到闭包中,这样该闭包才实现 Send + 'static,才可作为 std::thread::spawn() 的参数。

use std::rc::Rc;
use std::thread;

fn main() {
    let rc = Rc::new(42);
    thread::spawn(move || {
        println!("{}", rc);
    });
}

编译错误:

error[E0277]: `Rc<i32>` cannot be sent between threads safely
   --> src/main.rs:6:5
    |
6   |     thread::spawn(move || {
    |     ^^^^^^^^^^^^^ `Rc<i32>` cannot be sent between threads safely
    |
    = help: within `impl FnOnce() + Send`, the trait `Send` is not implemented for `Rc<i32>`

不需要考虑闭包内部创建的对象类似是否实现了 Send,因为它的所有权只属于该闭包,进而只会在该闭包对应的线程中执行:

// OK 的情况:
thread::spawn(move || {
    let rc = Rc::new(42);
    println!("{}", rc);
});

trait object 未实现 Send、Sync
#

对于 trait object,如 dync MyTraitBox<dyn MyTrait>,是否实现 Send/Sync 取决于它的 trait bound

  1. 定义 trait 类型时是否实现了 Send/Sync,如果实现了,则后续 dyn MyTrait 就也实现了对应的 Send、Sync。
  2. 或则,使用 trait object 时是否加了 + Send+ Sync 限定(如果 trait 类型定义没有实现 Send、Sync 的话)。

例如,Box<dyn Trait>,如果没有加 + Send,那么它本身不是 Send,即使所有实现 Trait 的类型都是 Send

错误的情况:

use std::thread;

// 错误 1: trait 类型没有实现 Send
trait MyTrait {}
impl MyTrait for i32 {}

fn main() {
    // 错误 2:trait object 没有实现 Send
    let obj: Box<dyn MyTrait> = Box::new(42);
    thread::spawn(move || {
        // ...
        let _ = obj;
    });
}

// error[E0277]: `dyn MyTrait` cannot be sent between threads safely
//    --> src/main.rs:9:5
//     |
// 9   |     thread::spawn(move || {
//     |     ^^^^^^^^^^^^^ `dyn MyTrait` cannot be sent between threads safely
//     |
//     = help: the trait `Send` is not implemented for `dyn MyTrait`

解决办法:

use std::thread;

// 1. 定义 trait 时需要实现 Send(所以,很多库定义的 trait 都标记实现 Send)
trait MyTrait: Send + Sync {}
impl MyTrait for i32 {}

fn main() {
    // 2. 或则,创建 trait object 时标记实现 Send、Sync
    let obj: Box<dyn MyTrait + Send + Sync> = Box::new(42);
    thread::spawn(move || {
        let _ = obj;
    }).join().unwrap();
}

&dyn Mytrait
#

&dyn MyTrait 是否实现 Send、Sync trait,和前面的 &T/&mut T 规则一致:

  • &mut T 是否实现 Send、Sync,取决于 T 是否实现了 Send、Sync,所以:

    • 如果 dyn MyTrait 实现了 Send, 如通过 &mut(dyn MyTrait + Send) 或则定义 MyTrait 时定义了它是 Send 的子 trait,则 &mut (dyn MyTrait + Send) 或 &mut dyn MyTrait 实现了 Send;

    • 同理,如果 trait MyTrait: Sync,则 &mut dyn MyTrait 实现了 Sync; &mut (dync MyTrait + Sync) 实现了 Sync。

  • &T 是否实现 Send 取决于 T 是否实现了 Sync,所以:

    • 如果 dyn MyTrait: Sync,则 &dyn MyTrait 实现了 Send。同理, &(dyn MyTrait + Sync) 也实现了 Send;
  • &T 是否实现 Sync 取决于 T 是否实现了 Sync,所以:

    • 如果 dyn Myrait: Sync,则 &dyn MyTrait 实现了 Sync。同理, &(dyn MyTrait + Sync) 也实现了 Sync;

总结:如果 dyn MyTrait 实现了 Sync,则 &dyn MyTrait 同时实现了 Send 和 Sync。

注意:&dyn MyTrait + Send 是不对的。

trait MyTrait {}

impl MyTrait for i32 {}

fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}

fn main() {
    // 1. &dyn MyTrait 默认不是 Send
    // assert_send::<&dyn MyTrait>(); // 编译报错

    // 2. &dyn MyTrait 默认是 Sync
    assert_sync::<&dyn MyTrait>(); // 通过

    // 3. &dyn MyTrait + Send
    assert_send::<&dyn MyTrait + Send>(); // 通过
    assert_sync::<&dyn MyTrait + Send>(); // 通过
}

&T 的 Send、Sync 实现举例
#

trait MyTrait {}
fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    let is = is_send::<dyn MyTrait>();
    println!("is: {is:?}");
    /*
        error[E0277]: the size for values of type `dyn MyTrait` cannot be known at compilation time
          --> src/main.rs:38:24
           |
        38 |     let is = is_send::<dyn MyTrait>();
           |                        ^^^^^^^^^^^ doesn't have a size known at compile-time
           |
           = help: the trait `Sized` is not implemented for `dyn MyTrait`
        note: required by an implicit `Sized` bound in `is_send`
    */

    let is = is_sync::<dyn MyTrait>();
    println!("is: {is:?}");
}

解决办法:

trait MyTrait {}

fn is_send<T: Send + ?Sized>() {}
fn is_sync<T: Sync + ?Sized>() {}

fn main() {
    let is = is_send::<dyn MyTrait>();
    println!("is: {is:?}");

    /*
    error[E0277]: `dyn MyTrait` cannot be sent between threads safely
    --> src/main.rs:37:24
    |
    37 |     let is = is_send::<dyn MyTrait>();
    |                        ^^^^^^^^^^^ `dyn MyTrait` cannot be sent between threads safely
    |
    = help: the trait `Send` is not implemented for `dyn MyTrait`
    note: required by a bound in `is_send`
    */

    let is = is_sync::<dyn MyTrait>();
    println!("is: {is:?}");
}

解决办法:

trait MyTrait: Send {}

fn is_send<T: Send + ?Sized>() {}
fn is_sync<T: Sync + ?Sized>() {}

fn main() {
    // OK
    let is = is_send::<dyn MyTrait>();
    println!("is: {is:?}");

    let is = is_sync::<dyn MyTrait>();
    println!("is: {is:?}");

    /*
    error[E0277]: `dyn MyTrait` cannot be shared between threads safely
      --> src/main.rs:40:24
       |
    40 |     let is = is_sync::<dyn MyTrait>();
       |                        ^^^^^^^^^^^ `dyn MyTrait` cannot be shared between threads safely
       |
       = help: the trait `Sync` is not implemented for `dyn MyTrait`
    note: required by a bound in `is_sync`
    */
}

解决办法:

trait MyTrait: Send + Sync {}

fn is_send<T: Send + ?Sized>() {}
fn is_sync<T: Sync + ?Sized>() {}

fn main() {
    // OK
    let is = is_send::<dyn MyTrait>();
    println!("is: {is:?}");

    // OK
    let is = is_sync::<dyn MyTrait>();
    println!("is: {is:?}");
}

或者:


trait MyTrait {}

fn is_send<T: Send + ?Sized>() {}
fn is_sync<T: Sync + ?Sized>() {}

fn main() {
    let is = is_send::<dyn MyTrait + Send>();
    println!("is: {is:?}");

    let is = is_sync::<dyn MyTrait + Sync>();
    println!("is: {is:?}");
}

&T 的 Send、Sync 举例
#

trait MyTrait {}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    let is = is_send::<&dyn MyTrait>();
    println!("is: {is:?}");

    /*
    error[E0277]: `&dyn MyTrait` cannot be sent between threads safely
    --> src/main.rs:37:24
    |
    37 |     let is = is_send::<&dyn MyTrait>();
    |                        ^^^^^^^^^^^^ `&dyn MyTrait` cannot be sent between threads safely
    |
    = help: the trait `Sync` is not implemented for `dyn MyTrait`
    = note: required for `&dyn MyTrait` to implement `Send`
    note: required by a bound in `is_send`
    */

    let is = is_sync::<&dyn MyTrait>();
    println!("is: {is:?}");
}

解决办法:

trait MyTrait: Sync {}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    // OK: &T 的 T 如果实现 Sync 则 &T 实现 Send
    let is = is_send::<&dyn MyTrait>();
    println!("is: {is:?}");

    // OK: &T 的 T 如果实现 Sync,则 &T 实现 Sync
    let is = is_sync::<&dyn MyTrait>();
    println!("is: {is:?}");

    // 由于 MyTrait 实现了 Sync,所以 &dyn MyTrait 同时实现了 Send 和 Sync
}

另外的解法:

trait MyTrait {}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    let is = is_send::<&(dyn MyTrait + Sync)>();
    println!("is: {is:?}");

    let is = is_sync::<&(dyn MyTrait + Sync)>();
    println!("is: {is:?}");
}

错误的解法:

trait MyTrait {}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    let is = is_send::<&dyn MyTrait + Send>();
    println!("is: {is:?}");

    /*
    error[E0277]: `&dyn MyTrait + Send` cannot be sent between threads safely
    --> src/main.rs:37:24
    |
    37 |     let is = is_send::<&dyn MyTrait + Send>();
    |                        ^^^^^^^^^^^^^^^^^^^ `&dyn MyTrait + Send` cannot be sent between threads safely
    |
    = help: the trait `Sync` is not implemented for `dyn MyTrait + Send`
    = note: required for `&dyn MyTrait + Send` to implement `Send`
    note: required by a bound in `is_send`
    */

    let is = is_sync::<&dyn MyTrait + Sync>();
    println!("is: {is:?}");

解决办法:

trait MyTrait {}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    let is = is_send::<&dyn MyTrait + Sync>();
    println!("is: {is:?}");

/*
error: ambiguous `+` in a type
  --> src/main.rs:37:25
   |
37 |     let is = is_send::<&dyn MyTrait + Sync>();
   |                         ^^^^^^^^^^^^^^^^^^
   |
help: try adding parentheses
   |
37 |     let is = is_send::<&(dyn MyTrait + Sync)>();
   |                         +                  +

error: ambiguous `+` in a type
*/

    let is = is_sync::<&dyn MyTrait + Sync>();
    println!("is: {is:?}");
}

解决办法:

trait MyTrait {}

fn is_send<T: Send>() {}
fn is_sync<T: Sync>() {}

fn main() {
    // OK
    let is = is_send::<&(dyn MyTrait+ Sync)>();
    println!("is: {is:?}");

    // ERROR:
    let is = is_send::<&(dyn MyTrait) + Send>();
    println!("is: {is:?}");

/*
error[E0178]: expected a path on the left-hand side of `+`
  --> src/main.rs:40:24
   |
40 |     let is = is_send::<&(dyn MyTrait) + Send>();
   |                        ^^^^^^^^^^^^^^
   |
help: try adding parentheses
   |
40 |     let is = is_send::<&((dyn MyTrait) + Send)>();  <--- 这个建议不对,即使加了括号也是报错
   |                         +                    +

error[E0277]: `&dyn MyTrait` cannot be sent between threads safely
  --> src/main.rs:40:24
   |
40 |     let is = is_send::<&(dyn MyTrait) + Send>();
   |                        ^^^^^^^^^^^^^^ `&dyn MyTrait` cannot be sent between threads safely
   |
   = help: the trait `Sync` is not implemented for `dyn MyTrait`
   = note: required for `&dyn MyTrait` to implement `Send`
note: required by a bound in `is_send`
*/

    // OK
    let is = is_sync::<&(dyn MyTrait + Sync)>();
    println!("is: {is:?}");
}

参考
#

  1. https://doc.rust-lang.org/std/marker/trait.Send.html
rust-lang - 这篇文章属于一个选集。
§ 10: 本文

相关文章

11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
18. 测试:testing
·
Rust
Rust 测试
1. 标识符和注释:identify/comment
·
Rust
Rust 标识符介绍
12. 迭代器:iterator
·
Rust
Rust 迭代器