跳过正文

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 后面需要列出泛型类型所需的所有泛型参数
//
// 函数和方法也可以定义自己的泛型参数, 它们不需要在 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); // 为泛型方法手动指定类型,使用比目鱼语法。

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

// 对 T 无限制,等效于 Sized 限界
struct Pair<T> { x: T, y: T,}

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

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
#

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

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

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

泛型常量
#

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

关联类型
#

trait 支持关联类型(type alias 语法), 这些类型在定义 trait 时一般是未知的,在实现时指定具体类型,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 时,可以同时指定泛型参数和关联类型, 泛型参数只能按顺序来指定,而关联类型可以使用 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;
}

impl Float for f32 {
    const ZERO: f32 = 0.0; // 实现 trait 时,为关联常量设置常量值
    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> 中指定 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 定义关联函数或方法,或为自定义类型实现 trait 时,该 trait 或 type 必须是本 crate 定义的类型,这个限制称为 Orphan Rule ,这是为了避免破坏开发者原来的定义。

impl<T> 指定 trait 或自定义类型使用的所有泛型参数,其中自定义类型也可以是泛型参数类型(blanket implement),如 impl<T, U> MyTrait<T> for U {};。所以,Rust 为泛型 trait 放宽了 Orphan Rule 条件限制 :可以为其它 crate package 中定义的 trait 添加方法,或为自定义类型实现 trait ,这种实现称为 blanket impl 。这样可以批量对已知或未知的类型实现 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 会被自动导入到所有 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();
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 方法没有任何泛型参数;

这是由于 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 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 或 trait object 也可以是闭包,如: 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());
}

参考: lifetime-elision.html

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

trait object 的 lifetime 消除规则:

下面这些 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,
}
// 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 {}

// 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 {}

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

它可以作为函数输入参数和返回值类型,但是不能作为泛型参数的限界(因为 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()
}

作为函数参数或返回值时,一般需要指定 ‘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(它们都是 2 个 usize 的胖指针),如:

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

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 长字段来使用 '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, 如 &str;
  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

实现 Copy 方式: derive 宏自动实现(bit-copy)或手动实现:

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

Default
#

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

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, Box, Box, CString, OsString, Error,PathBuf, String, &[T], Option, [T; N], Rc, Arc, Vec, 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 作为泛型类型的定界, 在标准库中得到广泛使用, 例如:

  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() 来手动释放对象,后续 Rust 不会再自动释放它。

fn bar() -> Result<(), ()> {
    struct Foo;
    impl Drop for Foo {
        fn drop(&mut self) {
            println!("exit");
        }
    }

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

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

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. Into trait 为任何实现了 From<T> 的类型,自动实现 Into<U>

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

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

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

运算符重载/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,
        }
    }
}

其它运算符:

  1. i..j, i.., ..j, i..=j 等:是 RangeXX struct 类型语法糖;
  2. ? :适用于 Result 和 Option,和 From/Into trait 协作进行自动类型转换;
  3. *val:使用 Deref/DerefMut 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

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
#

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

Borrow 用于解决泛型哈希表和其它关联集合类型的情况:K: Borrow<Q> 表示可以从 K 对象生成 &Q,K 和 Q 都是使用相同的 Eq + Hash 语义,所以可以给 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 实现了 Borrow, 它们具有相同的 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 即可。例如 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;
        }
    }
}

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

// 在 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 {
        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
    }
}

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

在需要对象的 &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 来解引用来满足泛型参数限界的要求。两种解决办法:

  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>

    • 操作符解引用后类型都是 T,实际执行的操作为:*(v.deref()) ;
  1. 在需要 &T 的地方都可以传入 &Ref/&Rc/&Box 类型;
  2. 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 个机器字栈空间+可变长堆空间:

  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<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 过, 再次调用 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);

pub fn into_inner(this: Rc<T, A>) -> Option<T> : 消耗传入的 Rc(其他类型的 into_inner() 方法都是类似的语义), 如果 Rc 只有一个强引用, 则返回 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 智能指针。

Weak 对 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;

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:?}");
    });
}

Pin/Unpin/pin!()/.await
#

Pin 常用场景是 std::future::Feature traitpoll 方法:由于 Future trait object 必须保存执行上下文中 stack 上的变量,而 Future 下一次被 wake 执行的时机是不定的,所以为了避免 Future 对象上保存的 stack 变量的地址发生变化导致引用出错,需要将 Future 对象设置为 Pin<&mut Self> 类型,这样该对象将不能被转移,从而确保内部保存的栈地址有效。

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

Unpin 是标准库提供的 marker trait, 它是一个标记类型。类型实现 Unpin trait 意味着该类型不提供任何 Pin 语义,即该类型对象可以安全的移动(move),即不需要 Pin 提供的保护语义。

如果类型实现了 Unpin,则可以从 Pin 中安全地获取可变引用:

fn example<T: Unpin>(x: Pin<&mut T>) {
    let mut_ref: &mut T = Pin::into_inner(x); // 安全, 因为 T: Unpin
}

Rust 为几乎所有类型都实现了 Unpin。但例外的情况如下:

  1. Future:因为实现 Future trait object 可能包含对自身引用或保存指针类型的栈变量, 移动它可能会使这些地址指向失效。
    • 包括 async/await 语法糖展开后的 Future 类型;
  2. Pin

    :Pin 本身就是为了处理不能移动的数据而设计的,Pin

    对于大多数 P 类型都不会实现 Unpin;

  3. PhantomPinned:标记 trait,用来显式标记类型不应该实现 Unpin: struct MyType { _pin: PhantomPinned }

Pin<T> 是一个 struct std::pin::Pin struct 类型,用于确保被包装的对象不能被移动。

创建 Pin 对象的几种方式:

  1. Pin 到栈上:let mut data=43; let pinned = Pin::new(&mut data);
  2. Pin 到堆上:let boxed = Box::new(42); let pinned = Pin::new(boxed);
  3. 直接在堆上创建 Pin:let pinned = Box::pin(42);

使用 Pin::new(T) 函数来为 T 创建 Pin 类型值,要求 T 必须实现 Unpin trait, 参数可以是 future 对象或者 &mut T:

  • 不能对 !Unpin 类型,如 Future 对象调用 Pin:new(),而需要使用 Box::pin()pin!();
  • 如果在 non_std 环境创建 Pin 对象或则 T 没有实现 Unpin,则需要使用 pin!() 宏;

Pin<Ptr> 类型的方法:

  • pinned.as_ref():返回新的 Pin 对象 Pin<& impl Future<Output=xx>>
  • pinned.as_mut(): 返回新的 Pin 对象 Pin<&mut impl Future<Output=xx>>
  • pinned.get_ref(): 返回 &T
  • pinned.get_mut(): 返回 &mut T
//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,

示例:

use std::pin::Pin;

let mut unpin_future = std::future::ready(5);
// 传入的是 &mut T,T 需要实现 Unpin
let my_pinned_unpin_future: Pin<&mut _> = Pin::new(&mut unpin_future);

// 如果要 Pin<Box<T>>, 则需要使用 Box::pin(value) 函数
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> 实现了 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 类型,例如 pin!(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>

Pin 在异步编程中得到广泛应用:

  • Future 没有实现 Unpin,因为 async/await 生成的 Future 可能包含自引用结构;
  • 编译器将 async 函数转换为状态机时, 会产生包含内部引用的结构;

调用 async fn 返回一个 Future 类型值,它没有实现 Unpin(也就是实现了 !Unpin), 在对它进行 poll 前,需要显式将值 Pin。

对 Future 对象调用 .await 表达式,用于对 Future 对象进行 poll,直到获得 Ready 值。编译器自动对 future 对象进行 Pin(通过获得 future 对象的 &mut 引用),然后调用它的 poll() 方法(同时也会消耗 future 对象):

类似的,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);
}

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

需要显式使用 core::pin::pin!(T) 将 Future 对象 T Pin 住,返回 Pin<&mut T> 对象,然后对 &mut pinned_future 进行 .await:

  • 不能使用 pin::new(T),因为要求 T 必须实现 Unpin,而 Future 对象没有实现 Unpin;
  • 如果 Pin 的 Ptr 实现了 Future,则 Pin 也实现了 Future, 所以可以 .await;

如果 T 没有实现 Unpin(如 Future 对象),则该宏将 T 值 pin 到内存中(通过 &mut T 可变借用的方式),不允许 move。

如果 T 实现了 Unpin,则 Pin<&mut T> 效果和 &mut 类似,mem::replace() 或 mem::take() 可以 move 该值。

pin!(future) 宏的实现方式等效于 std::pin::Pin::new(&mut future), 它存入 future 对象的 &mut 借用(所以 future 类型需要是 mut 类型),返回一个 Pin<Ptr> 类型的同名对象,其中 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>> 类型;
// 下面代码 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;
}
  1. 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;
        }
    
  2. 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 => {}
            }
        }
    

loop+select!{} 语句中,要对 Future 对象进行重复 poll,而 select!{} 在并发 .await 时,如果有返回值,则会 drop 其它 branch 的 Future 对象,所以在 select branch 中不能直接使用 future 对象,以避免它被 drop 后下一次 loop 失败 select 失败。

解决办法是:

  1. 创建一个 mut 类型的 Future 对象;
  2. 使用 pin!(future) 来创建一个同名,但类型是 Pin<&mut impl Future<Output=xx>> 的 Pin 对象, 它也实现了 Future;
  3. 在 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 类型是 &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 pinned_future 外,还可以调用 pinned_future.as_mut() 方法来创建一个 Pin<&mut impl Future<Output=xx>> 类型对象(注意不是 &mut XX 借用对象),满足 impl<P> Future for Pin<P> where P: DerefMut, <P as Deref>::Target: Future 中对 P 的要求,所以它也实现了 Future,可以被 poll:

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

Send/Sync
#

Send 和 Sync 都是标记 trait:

  • Send:对象可以在多个线程中转移(也就是对象在多线程间转移具有原子性);
  • Sync:可以在多个线程中共享引用对象;

thread 一般使用 move 闭包,这样该闭包才能实现 Send+'static。对于 scope thread 使用的闭包函数,可以不使用 move,它们可以共享引用父 thread 的 stack 变量,所以要求这些对象必须实现 Sync。

绝大部分 Rust 类型,如 Vec/Map/Array 等实现了 Send/Sync,可以被 move 到 thread closure 中。

自定义类型的各成员如果实现了 Send/Sync,则该类型也自动实现了 Send/Sync。

没有实现 Send/Sync 的类型:

  1. Rc<T> :没有实现 Send 和 Sync(多线程转移 Rc 对象时,不能保证原子性),不能被转移到 thread closure 中。
  • 多线程环境中需要使用 Arc 和它的 clone() 后的新对象;
  1. Cell/RefCell/mpsc:Receiver :没有实现 Sync,不能在多个线程中共享引用;
  2. std::sync::MutexGurad :没实现 Send;
rust-lang - 这篇文章属于一个选集。
§ 10: 本文

相关文章

1. 标识符和注释:identify/comment
·
Rust
Rust 标识符介绍
11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
12. 迭代器:iterator
·
Rust
Rust 迭代器
13. 包和模块:package/crate/module
·
Rust
Rust 项目的包和模块组织结构