跳过正文

泛型和特性:generic/trait

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

泛型类型/函数/方法的泛型参数使用 <CamelCase, ...> 表示:

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

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

struct Val<T> {
    val: T,
}
let x = Val{ val: 3.0 }; // x 是 Val<f64> 类型
let y = Val{ val: "hello".to_string()}; // y 是 Val<String> 类型
println!("{}, {}", x.value(), y.value()); // 3.0, hello

// 泛型函数:类型参数位于函数名后的 <xx> 中。
fn generic<T>(_s: SGen<T>) {}

// 调用泛型方法时,使用比目鱼语法:method::<type>() 来指定泛型参数的具体类型。
generic::<char>(__);

// 为泛型类型实现关联函数或方法时,impl 后面需要列出泛型类型所需的所有泛型参数
//
// 函数和方法也可以定义自己的泛型参数, 它们不需要在 impl 后列出,而是在调用时推导或指定。
impl<T> Val<T> {
    // 方法可以使用 impl 的泛型参数
    fn value(&self) -> &T {
        &self.val
    }

    // 方法可以有自己的泛型参数 O
    fn add<O: Debug>(&self, other: O) {
        println!("{:?}", other);
    }
}

let x = Val { val: 3.0 };
let y = Val { val: "hello".to_string(), };
println!("{}, {}", x.value(), y.value());
x.add(123); // 推断
x.add::<i32>(123); // 为泛型方法手动指定类型,使用比目鱼语法。

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

在使用 trait 提供的方法或函数时,该 trait 必须先引入到当前 scope, 否则编译器不知道应该使用该类型实现的哪个 trait 的同名函数或方法。 std::prelude::v1 moudle 列出的 item 会被自动导入到所有 std Rust 程序中,如 std::convert::{AsRef, AsMut, Into, From} 等,所以在调用对象的 into()/from()/as_ref()/as_mut() 方法前不需要手动导入它们。

trait 支持关联类型, 这些类型在定义 trait 时一般是未知的,但在实现该 trait 时需要指定 具体类型(type alias 语法) ,trait 函数可以使用 Self::Item 来引用关联类型 Item:

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

使用带关联类型的 trait 做限界时,可以使用 Trait<Item=Type> 语法来为该 trait 的关联类型指定一个具体类型,结果还是一个 trait,可以用它做限界或定义 trait object,例如:

  • 也可以不指定关联类型的具体类型,在使用时,由编译器推导。
// 使用带关联类型的 trait 做限界时:
// 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>) {}


trait Iterator {
    // 关联类型, 可以对类型进行限界。
    type Item: std::fmt::Display;

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

struct EvenNumbers { count: usize, limit: usize, }
impl Iterator for EvenNumbers {
    // 实现时必须为关联类型指定 具体类型,该具体类型需要满足 trait 定义的限界要求。
    type Item = usize;

    // trait 的函数/方法,通过 Self 引用关联类型
    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 Greet{
    const GRETING: &'static str = "Hello", // 为关联常量指定缺省值

    fn greet(&self) -> String;
}

trait Float {
    const ZERO: Self; // 定义常量时必须指定类型(因编译器不会自动推导 const 值类型),但是可以不指定缺省值
    const ONE: Self;
}

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

使用 trait bound 的场景:

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

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

在函数传参匹配 trait bound 时不会自动隐式进行 type coercion 协变,例如,虽然 &mut i32 可以协变到 &i32, 但传参时不会协变。带来的影响是:如果 Trait 作为函数参数限界,&i32 和 &mut i32 两种类型都需要实现该 Trait:

  • 这是由于 trait 的方法参数可能是 self 或 &mut self,如果这时传入 &T,就不能调用这些方法;
  • 最佳实践:为了避免用户惊讶,在为自定义类型实现 Trait 时,一般为三种类型 T, &T, &mut T 都实现同一个 trait。 在 T 上实现 trait,这样内部可以使用 self 或 &mut self;
trait Trait {}

fn foo<X: Trait>(t: X) {}

impl<'a> Trait for &'a i32 {}

fn main() {
    let t: &mut i32 = &mut 0;
    foo(t); // 报错。
}

// error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
//  --> src/main.rs:9:9
//   |
// 3 | fn foo<X: Trait>(t: X) {}
//   |           ----- required by this bound in `foo`
// ...
// 9 |     foo(t);
//   |         ^ the trait `Trait` is not implemented for `&mut i32`
//   |
//   = help: the following implementations were found:
//             <&'a i32 as Trait>
//   = note: `Trait` is implemented for `&i32`, but not for `&mut i32`

1 泛型 trait
#

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

  • 匿名函数也可以作为类型参数的 bound 约束,例如函数指针 fn() -> u32 或闭包 Fn() -> u32,但后续只能传入 fn 而不能是闭包;
  • 除了对泛型参数 T 直接限界外,也可以使用 where 对 使用 T 的其它类型进行限界 ,如:Option<T>: Debug;
// 对 T 无限制,等效于 Sized 限界
struct Pair<T> { x: T, y: T,}

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

// 实现泛型 trait 时,可以对 T 添加额外的限制
impl<T: std::fmt::Debug + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {:?}", self.x);
        } else {
            println!("The largest member is y = {:?}", self.y);
        }
    }
}

trait Contains<A, B> {
    fn contains(&self, _: &A, _: &B) -> bool;
    fn first(&self) -> i32;
    fn last(&self) -> i32;
}

// 实现泛型 trait 时,可以指定具体类型或者继续使用泛型参数。
impl Contains<i32, i32> for Container {
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }
    fn first(&self) -> i32 { self.0 }
    fn last(&self) -> i32 { self.1 }
}

// 泛型参数可以使用多个 trait 或 lifetime 作为限界。
fn compare_prints<T: Debug + Display>(t: &T) {
    println!("Debug: `{:?}`", t);
    println!("Display: `{}`", t);
}

fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
    println!("t: `{:?}`", t);
    println!("u: `{:?}`", u);
}

// 对于复杂的、较长的限界,可以使用 where 来定义
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// 等效于
impl <A, D> MyTrait<A, D> for YourType where A: TraitB + TraitC, D: TraitE + TraitF
 {
     //..
 }

// 有些场景, 必须使用 where 来限界,比如这里限界的是 Option<T>
trait PrintInOption {
    fn print_in_option(self);
}
impl<T> PrintInOption for T where Option<T>: Debug {
    fn print_in_option(self) {
        println!("{:?}", Some(self));
    }
}

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

使用 有泛型参数 + 关联类型的泛型 trait 时可以同时指定新的泛型类型参数+关联类型, 其中 按顺序指定泛型参数的具体类型,使用 K=V 来指定关联类型

例如使用 Add 进行限界时:

  1. Add<&int32, Output=int32>
  2. Add<Output=int32> (省略有缺省值的参数)

但不能使用 Add<Rhs=&T, Output=T>, 因为 Rhs 不是关联类型参数:

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

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

// 实现 trait 时,需要为关联类型指定具体类型
impl Add<Bar> for Foo {
    type Output = FooBar;

    fn add(self, _rhs: Bar) -> FooBar {
        FooBar
    }
}

// 定义一个泛型函数,使用 Add trait 作为泛型参数的限界,同时指定 Rhs 类型和关联类型。
// 不能使用 Rhs = Rhs, 否则 Rust 会报错没有 Rhs 关联类型。
fn add_values<T, Rhs>(a: T, b: Rhs) -> T where T: Add<Rhs, Output = T>,
  { a + b }

trait 也支持泛型常量参数(例如 <const N: usize> ,非关联常量),这样可以在泛型类型/函数/方法中使用 Array。实例化常量参数时,可以使用 {XXX} 常量表达式:

struct ArrayPair<T, const N: usize> {
    left: [T; N],
    right: [T; N],
}

impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> {
    // ...
}

// 实例化常量参数
fn foo<const N: usize>() {}
fn bar<T, const M: usize>() {
    foo::<M>();
    foo::<2021>();
    foo::<{20 * 100 + 20 * 10 + 1}>(); // OK:使用 <{xx}> 常量表达式

    foo::<{ M + 1 }>(); // Error: 常量表达式不支持泛型常量运算
    foo::<{ std::mem::size_of::<T>() }>(); // Error: 常量表达式不支持泛型参数

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

2 impl trait 和 blanket impl
#

impl<T> 场景:

  1. 定义泛型类型的方法或关联函数:impl<T> MyStruct<T> {}
  2. 为类型定义泛型 trait 的实现: impl<T> MyTrait<T> for MyStruct<T> {}

impl<T> 指定 trait 或类型使用的泛型参数,甚至整个类型都可以是泛型参数(blanket implement),如 impl<T, U> MyTrait<T> for U {};

如果明确指定了泛型 trait 的类型参数, 则 impl 该 trait 时不需要使用 impl<T, U> 来指定泛型参数:

struct GenericVal<T>(T);

// 对于泛型类型,在 impl 时
// 1. 如果指定了泛型参数类型,则不需要为 imp 指定泛型参数
impl GenericVal<f32> {}
// 2. 未指定具体类型时,必须在 impl<T> 中指定 GenericVal 的所有泛型参数
impl<T> GenericVal<T> {}

fn main() {
    // 创建泛型类型的值时,可以不指定泛型参数 T,由编译器自动推导
    let y = GenVal { gen_val: 3i32 };
    println!("{}, {}", x.value(), y.value());
}

struct Container(i32, i32);
trait Contains<A, B> {
    fn contains(&self, _: &A, _: &B) -> bool;
    fn first(&self) -> i32;
    fn last(&self) -> i32;
}

// 不需要使用 impl<i32,i32>
impl Contains<i32, i32> for Container {
    fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
        (&self.0 == number_1) && (&self.1 == number_2)
    }
    fn first(&self) -> i32 { self.0 }
    fn last(&self) -> i32 { self.1 }
}

一般情况下,为 trait 添加函数或方法时,或为 type 实现 trait 时,trait 或 type 必须是本 crate 定义的类型,这个限制称为 Orphan Rule ,这是为了避免破坏开发者原来的定义。

对于泛型 trait 则放宽了这个条件限制:可以为其它 crate package 中定义的 trait 添加方法,或为 type 实现 trait ,这种实现称为 blanket impl 。这样可以批量对已知或未知的类型实现 trait。例如为任意 T 类型实现 Borrow<T> trait:

// 泛型 trait
trait DoubleDrop<T> {
    fn double_drop(self, _: T);
}
// 为 U 实现泛型 trait,U 本身也是泛型参数,所以相当于为所有 Sized 类型实现了 DoubleDrop trait。
impl<T, U> DoubleDrop<T> for U {
    fn double_drop(self, _: T) {}
}

// 为所有 &T 或 &mut T 实现 Deref<Target = T> trait:
// https://doc.rust-lang.org/src/core/ops/deref.rs.html#84
#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for &T {
    type Target = T;

    #[rustc_diagnostic_item = "noop_method_deref"]
    fn deref(&self) -> &T {
        *self
    }
}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> !DerefMut for &T {}

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Deref for &mut T {
    type Target = T;

    fn deref(&self) -> &T {
        *self
    }
}

Rust 不会自动通过 Deref trait 解引用来满足泛型参数限界的要求:

use std::ops::{Deref, DerefMut};

impl<T> Deref for Selector<T> {
    type Target = T;
    fn deref(&self) -> &T { &self.elements[self.current] }
}

impl<T> DerefMut for Selector<T> {
    fn deref_mut(&mut self) -> &mut T { &mut self.elements[self.current] }
}

// OK: 函数传参时, Rust 会自动隐式解引用, &Selector -> &str
let s = Selector { elements: vec!["good", "bad", "ugly"], current: 2 };
fn show_it(thing: &str) { println!("{}", thing); }
show_it(&s);

// Error: 使用 Trait 作为泛型参数的限界时, Rust 不会自动解引用到 &str 类型来满足类型限界的要求
use std::fmt::Display;
fn show_it_generic<T: Display>(thing: T) { println!("{}", thing); }
show_it_generic(&s);

// 两种解决办法:
show_it_generic(&s as &str); // 使用 as 类型转换;
show_it_generic(&*s) // 手动解引用 &*V 来得到 &str 类型

3 super trait
#

trait 可以有多个父 trait(称为 super trait),类型在实现该 trait 的同时也必须实现这些父 trait。 supertrait 也支持 lifetime bound。

trait Person {
    fn name(&self) -> String;
}

trait Student: Person { // 等效于 trait Student where Self: Person
    fn university(&self) -> String;
}

trait Programmer {
    fn fav_language(&self) -> String;
}

// 使用 + 运算符指定多个父 trait:
trait CompSciStudent: Programmer + Student {
    fn git_username(&self) -> String;
}

// 为父 trait 指定 'lifetime
trait Circle<'a> : Shape + 'a {
    fn radius(&self) -> f64;
}

4 完全限定方法调用
#

类型可以实现不同 crate package 定义的同名 trait,它们可以有相同或不同的方法,所以在 VecK<u8> 上调用 trait 定义的方法时,必须先将对应的 trait 引入到作用域,否则 Rust 不知道该调用哪一个 trait 的方法实现。

位于 std::prelude::v1 module 中的 trait,如 From/Into/Cone/Iterator,会被自动导入到所有 std 程序中,故不需要手动导入。

// write_all() 方法是 std::io::Write trait 定义的,调用它前需要引入 Write trait。
use std::io::Write
let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?;


trait Pilot {
    fn fly(&self);
}
trait Wizard {
    fn fly(&self);
}

struct Human;
impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}
impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}
impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    // 调用 Human 自身实现的 fly() 方法。
    person.fly();

    // 调用实现的 trait 的方法,这里为了避免歧义,使用的是完全限定方法调用或 TraitName::method(object).
    Pilot::fly(&person); // 等效于:<Human as Pilot>::fly(&person)
    Wizard::fly(&person);
}

Pilot::fly(&person) 调用方式不适合没有 self 参数的关联函数:调用 trait 关联函数时,Rust 不能推导出调用哪一个类型实现的该关联函数,这时需要使用完全限定方法调用。

完全限定方法调用: Rust 完全限定语法(Fully Qualified Syntax)明确指定了要调用哪个 trait 的方法,可以解决命名冲突或混淆的问题。

trait CalSum {
    fn sum(&self, a: i32) -> i32;
}

trait CalSumV2 {
    fn sum(&self, a: i32) -> i32;
}

#[derive(Clone, Copy, Debug)]
struct S1(i32);

impl CalSum for S1 {
    fn sum(&self, a: i32) -> i32 {
        self.0 + a
    }
}

impl CalSumV2 for S1 {
    fn sum(&self, a: i32) -> i32 {
        self.0 + a
    }
}

fn main() {
    let s1 = S1(0);

    // 调用指定 trait 的方法
    println!("Results: {}", CalSum::sum(&s1, 1));
    println!("Results: {}", CalSumV2::sum(&s1, 1));

    // ERROR: S1 同时实现了 CalSum 和 CalSumV2, 它们都提供了 sum() 方法, Rust 不确定该调用哪一个。
    //println!("Results: {}", s1.sum(1)); // Error:  multiple `sum` found

    // 使用完全限定方法调用:调用 S1 实现的 CalSum 的 sum 方法。
    println!("Results: {}", <S1 as CalSum>::sum(&s1, 1)); // OK
}

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

let i: i8 = Default::default();
let (x, y): (Option<String>, f64) = Default::default();
let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();

#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}

fn main() {
    // 如果 SomeOptions 没有实现除 Default 外的其它 trait 的 default() 方法, 则<SomeOptions as
    // Default>::default() 可以简写为 Default::default().
    let options: SomeOptions = Default::default();
    let options = SomeOptions { foo: 42, ..Default::default() };
}

5 trait object
#

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

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

trait object 和泛型参数的差别:

  1. 对于 trait bound 的泛型参数,Rust 编译器推断并绑定一种实际具体类型。如 Vec<T: Display> , 只能保存一种实现 Display trait 的类型元素;
  2. 对于 trait object,不和某一种类型绑定,而是在运行时来根据调用方法的对象指针(类型)来查找编译期间构造的 vtab 来调用对应对象的方法。所以 Vec<&dyn Display> 是可以保存各种实现了 Display trait 的多种类型对象;

trait object,如 dyn Trait + Send + 'static 限制:

  1. 除了第一个 trait 外,其它 trait 只能是 auto trait ,包括:Send, Sync, Unpin, UnwindSafe 和 RefUnwindSafe。注意:不包含 ?Sized,Hash,Eq 等 trait。
  2. 最多指定一个 lifetime;
  3. trait 的路径可以用括号括起来,适用于复杂的定义场景;
// Rust 2021 前版本 dyn 关键字是可选的,2021 版本开始 dyn 关键字是必选的。
dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static
dyn Trait + Send + 'static
dyn Trait + Send + Sync + 'static
dyn (Trait)
dyn Trait + 'a // 指定 lifetime
dyn eviction::EvictionManager + Sync  // 使用 path 来完整指定 trait

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

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

  • Trait 方法不返回 Self,因为 trait object 是运行时派发,编译时不能对确定 Self 类型,故不能做检查;
    • 解决办法: fn splice(&self, other: &Self) -> Self 修正为 fn splice(&self, other: &dyn MyTrait) -> Box<dyn MyTrait>
  • Trait 方法没有任何泛型参数。

这是由于 trait object 在编译时是不知道自己具体类型的,而是在运行时动态派发,所以在编译器时,如果返回 Self,则编译器不能正确检查返回值合法性。同理,由于在运行时才能确定 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 Trait 时,需要传入实现该 Trait 的借用类型值,如 MyStruct 实现了 MyTrait, 则需要使用 &dyn Trait 的地方需要传入 &MyStruct 值。

当参数类型是 &Box<dyn Trait> 时,不能传入 &Box::new(my_struct) ,Rust 报错类型不匹配,两个解决办法:

  • 将参数类型修改为 Box<dyn Trait> , 后续可以直接传入 Box::new(my_struct)
  • 先创建一个 Box<dyn Triat> 的对象 b,然后再通过 &b 赋值,这时通过 unsized type coercion 隐式自动类型转换。
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)>) {
    if let Some(my_trait) = say_hello {
        my_trait.say_hello();
    }
}

fn printf_hellov2(say_hello: Option<&Box<dyn MyTrait + Send + 'static>>) {

    // 错误:不能通过借用(共享或可变)来转移对象

    // error[E0507]: cannot move out of `*say_hello` as enum variant `Some` which is behind a
    // shared reference
    //
    // 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() {
    // 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);

    let my_struct = MyStruct("abc".to_string());

    // expected `&dyn MyTrait`, found `MyStruct`
    //printf_hello(Some(my_struct));

    // error: borrow expressions cannot be annotated with lifetimes
    // printf_hello(Some(&'static my_struct));

    printf_hello(Some(&my_struct));

    // error[E0308]: mismatched types,expected `&Box<dyn MyTrait + Send>`, found `&Box<MyStruct>`
    // printf_hellov2(Some(&Box::new(my_struct)));

    // 解决办法:先创建一个 Box 对象,然后再借用。
    // 赋值时,通过 unsized type coercion 隐式自动类型转换。
    let st1: Box<dyn MyTrait + Send + 'static> = Box::new(my_struct);
    printf_hellov2(Some(&st1));

    let my_struct2 = MyStruct("def".to_string());
    printf_hellov3(Some(Box::new(my_struct2)));
}

使用 Box 保存 trait object 时, 有三种标准格式, Rust 标准库为这三种格式定义了对应的方法和函数:

  • Box<dyn TraitName + 'static>
  • Box<dyn TraitName + Send + 'static>
  • Box<dyn TraitName + Sync + Send + 'static>

Fn/FnMut/FnOnce 也是 trait,故上面的 TraitName 可以是闭包函数,如: Box<dyn Fn(int32) -> int32>

struct Sheep {}

struct Cow {}

trait Animal {
    fn noise(&self) -> &'static str;
}

impl Animal for Sheep {
    fn noise(&self) -> &'static str {
        "baaaaah!"
    }
}

impl Animal for Cow {
    fn noise(&self) -> &'static str {
        "moooooo!"
    }
}

// 返回一个实现 Animal trait 的 Box 对象,编译期间不感知具体的实现类型
fn random_animal(random_number: f64) -> Box<dyn Animal> {
    if random_number < 0.5 {
        Box::new(Sheep {})
    } else {
        Box::new(Cow {})
    }
}

创建 Box<dyn MyTrait> 时,不能使用 Box::<dyn MyTrait>::new(value) ,因为 Box new() 方法是定义在 impl <T> Box<T> 上的, T 没有任何 trait bound, 所以 T 默认是 Sized, 而 dyn MyTrait 是 un-sized 的, 所以编译报错。

解决办法:在返回值中指定 Box::<dyn MyTrait> 类型, 让 Rust 编译器自动转换。

let val: Box<dyn Any + 'a> = Box::new(*val); // OK

// function or associated item cannot be called on `Box<dyn Any>` due to unsatisfied trait bounds,
// 115 | pub trait Any: 'static {
// | ---------------------- doesn't satisfy `dyn std::any::Any: std::marker::Sized`
// let val: Box<dyn Any + 'a> = Box::<dyn Any + 'a>::new(*val);

// Box::<&dyn Any>::new() 是 OK 的, 因为 &dyn Any 是 Sized 对象.
// let val: Box<&dyn Any> = Box::<&dyn Any>::new(&val);

5.1 Default trait object lifetime
#

dyn Trait 的 lifetime 规则:

  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 长。
  4. &'r Ref<'q, dyn Trait> 等效于 &'r Ref<'q, dyn Trait+'q> ;

理解 Box<dyn Trait> 等效于 Box<dyn Trait + 'static>:

  • 使用 Box::new(value) 来赋值, 其中 value 的类型实现了 Trait。value 所有权被转移到 Box 中, Box 可以随意决定 value 的生命周期, 相当于具有了 ‘static 语义;
  • 当将一个对象被转移给函数时,该对象满足函数的 ‘static 要求(因为转移给函数后,函数可以自由决定对象的生命周期)。如果对象有借用,则不能转移给函数;
let Box<dyn Trait> = Box::new(value);

// Parent 拥有 Box 的所有权,进而拥有 trait object 所有权。
//
// 当 Parent 对象被 drop 时,child 的 Box 的 trait object 也会被 drop
struct Parent {
    child: Box<dyn OtherTrait + 'static>,
}


use std::collections::HashMap;

trait App {}
struct AppRegistry {
    apps: HashMap<String, Box<dyn App>>,
}
impl AppRegistry {
    fn new() -> AppRegistry {
        Self { apps: HashMap::new() }
    }

    // impl App 必须加加 'static lifetime,否则 Box::new(app) 报错。
    fn add(&mut self, name: &str, app: impl App + 'static) {
        self.apps.insert(name.to_string(), Box::new(app));
    }
}
struct MyApp {}
impl App for MyApp {}
fn main() {
    let mut registry = AppRegistry::new();
    // paas by value 传递对象,add() 拥有这个对象,所以满足 'static
    registry.add("thing", MyApp {});
    println!("apps: {}", registry.apps.len());
}

参考: lifetime-elision.html

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

trait Foo { }

// 下面两个类型等效
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;

// 这两个 impl 也是等效
impl dyn Foo {}
impl dyn Foo + 'static {}

// 下面两个类型等效,因为 &'a T 意味着 T: 'a
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);

// 下面两个相同,因为 std::cell::Ref<'a, T> 内部要求 T: 'a
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;

// T 的 lifetime 需要同时满足 'a + 'b, 所以应该比 'a 和 'b 都长
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
    f1: &'a i32,
    f2: &'b i32,
    f3: T,
}
// Error: the lifetime bound for this object type cannot be deduced from context
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;

// trait object 只能指定一个 'lifetime
//
// error[E0226]: only a single explicit lifetime bound is permitted
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo + 'a + 'b>;

// OK
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo + 'a>;
// OK
type T7<'a, 'b, 'c> = TwoBounds<'a, 'b, dyn Foo + 'c>;

// 表示实现 Bar trait 的类型的对象 lifetime 不能长于 'a
trait Bar<'a>: 'a { }

// 下面两种类型相同
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// 下面两种类型相同
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}

https://doc.rust-lang.org/reference/lifetime-elision.html

The assumed lifetime of references held by a trait object is called its default object lifetime bound . These were defined in RFC 599 and amended in RFC 1156.

These default object lifetime bounds are used instead of the lifetime parameter elision rules defined above when the lifetime bound is omitted entirely. If ‘_ is used as the lifetime bound then the bound follows the usual elision rules.

如果 trait object 作为 trait bound 且没有指定 lifetime bound, 则使用这里说明的 default trait object lifetime bound。如果为 trait object 指定 ‘_ liftime bound, 则使用 lifetime elide rule 来自动添加 lifetime。

If the trait object is used as a type argument of a generic type then the containing type is first used to try to infer a bound.

  1. If there is a unique bound from the containing type then that is the default
  2. If there is more than one bound from the containing type then an explicit bound must be specified

If neither of those rules apply, then the bounds on the trait are used:

  1. If the trait is defined with a single lifetime bound then that bound is used.
  2. If ‘static is used for any lifetime bound then 'static is used.
  3. If the trait has no lifetime bounds, then the lifetime is inferred in expressions and is 'static outside of expressions .
trait Foo { }

// 下面这些 T1 到 T7 都是将 trait object 用作 type argument, 所以先检查对应 containing type 的
// lifetime 来推导 trait object 的 lifetime。主要是 3 条规则:
//
// 1. 如果 containing type 没有 lifetime,如 T1/T2 则使用 'static;
// 2. 如果 containing type 只指定了一个 lifetime, 则使用对应的 lifetime,如 T3/T4/T5/T6;
// 3. 如果 containing type 有多个 lifetime,则必须为 trit object 明确指定 liftime,如 T7;

// These two are the same because Box<T> has no lifetime bound on T
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;
// ...and so are these:
impl dyn Foo {}
impl dyn Foo + 'static {}

// ...so are these, because &'a T requires T: 'a
// 首先检查 containing type 是否有 lifetime,如果有的话且唯一,则使用它。
// 这里的 containing type 指的是 &'a dyn Foo 中的 &'a.
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);

// std::cell::Ref<'a, T> also requires T: 'a, so these are the same
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;

// This is an example of an error.
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
    f1: &'a i32,
    f2: &'b i32,
    f3: T,
}
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
//                                  ^^^^^^^
// Error: the lifetime bound for this object type cannot be deduced from context

// 如果 trait object 不是作为 type argument,而是用于赋值,则使用对应目标类型的 trait bound 来决定
// trait object 的 lifetime:
//
// 1. 如果目标 trait 只有一个 lifetime, 则使用它;
// 2. 如果目标没有 lifetime,则使用 'static 或从 expression 推导;

Note that the innermost object sets the bound, so &'a Box<dyn Foo> is still &'a Box<dyn Foo + 'static> .

// For the following trait...
trait Bar<'a>: 'a { }

// ...these two are the same:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;

// ...and so are these:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}

6 impl trait
#

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

impl Traitname 的主要使用场景是简化泛型参数约束的复杂性,如函数返回一个复杂的多种嵌套迭代器时,该类型可能只有编译器才能准确写出来,这时可以用 impl TraitName 来简化返回参数类型。例如将iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> 简化为 impl Iterator<Item=i32>

impl trait 可以作为函数输入参数和返回值类型,但是不能作为泛型参数的 bound,因为 impl trait 被编译时实例化为具体类型而不是 trait,所以不用用于泛型参数限界。

// impl trait 作为函数参数的类型时,不需要引入泛型函数。
fn print_it1(input: impl Debug + 'static ) {
    println!( "'static value passed in is: {:?}", input );
}

// 等效为泛型参数版本,这个版本的优势是实例化时可以指定 T 的类型,但是 impl Debug 不行。
fn print_it<T: Debug + 'static>(input: T) {
    println!( "'static value passed in is: {:?}", input );
}

use std::iter;
use std::vec::IntoIter;
fn combine_vecs_explicit_return_type(v: Vec<i32>, u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
    v.into_iter().chain(u.into_iter()).cycle()
}
// 等效为
fn combine_vecs(v: Vec<i32>, u: Vec<i32>, ) -> impl Iterator<Item=i32> {
    v.into_iter().chain(u.into_iter()).cycle()
}

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

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

由于闭包 Fn 也是 trait,所以函数可以返回 Box<dyn Fn() -> i32> 或 impl Fn() -> i32:

fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
    let closure = move |x: i32| { x + y };
    closure
}

fn main() {
    let plus_one = make_adder_function(1);
    assert_eq!(plus_one(2), 3);
}

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1) // 堆分配空间,性能差些
}

fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1 // 栈分配,性能好
}

7 Sized/?Sized
#

Rust 为所有固定大小类型都自动实现了 std::marker::Sized trait, 用户不能自己为类型实现该 trait,该 trait 没有关联类型和函数,仅仅是一个标记 trait;

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

Sized 是 Rust 的隐式默认值,即如果写 struct S<T> 的 T 未加任何限界, 则 Rust 解释为 struct S<T: Sized>, 如果不想以这种方式约束 T 则必须显示的指出 struct S<T: ?Sized>, 表示 T 可以是无固定大小的类型。

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

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

对于上面的无固定大小的类型,Rust 不能将它们作为变量或函数参数来传递,而是需要转换为固定大小的引用或 Box(它们都是 2 个 usize 的胖指针),如:

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

8 PhantomData
#

PhantomData 是一个 zero-sized 泛型 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,
           phantom: PhantomData<&'a T>, // 表示 T: 'a, 即 T 对象的引用的生命周期要比 'a 长。
       }
    
  2. 隐藏数据:

       use std::marker::PhantomData;
    
       // 由于 PhantomData 类型没有字段,编译器不为其分配内存,所以该类型字段不可访问,可以用来隐藏字段。
    
       // 第二个字段是隐藏的
       #[derive(PartialEq)]
       struct PhantomTuple<A, B>(A, PhantomData<B>);
    
       // 第二个字段是隐藏的
       #[derive(PartialEq)]
       struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }
    
       fn main() {
           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>);
    
       impl<Unit> Add for Length<Unit> {
           type Output = Length<Unit>;
    
           fn add(self, rhs: Length<Unit>) -> Length<Unit> {
               Length(self.0 + rhs.0, PhantomData)
           }
       }
    
       fn main() {
           let one_foot:  Length<Inch> = Length(12.0, PhantomData);
           let one_meter: Length<Mm>   = Length(1000.0, PhantomData);
    
           let two_feet = one_foot + one_foot;
           let two_meters = one_meter + one_meter;
    
           println!("one foot + one_foot = {:?} in", two_feet.0);
           println!("one meter + one_meter = {:?} mm", two_meters.0);
    
           // Nonsensical operations fail as they should:
           // Compile-time Error: type mismatch.
    
           //let one_feter = one_foot + one_meter;
       }
    

9 Copy/Clone
#

Copy 是一个 marker trait, 不需要实现特定的方法。它是 Clone 的 子 trait, 所以在实现 Copy 的同时也必须实现 Clone:

pub trait Copy: Clone { }

Clone 和 Copy 的差别:

  1. Copy 是 Rust 隐式调用的(如变量赋值, 传参等), 不能被重载或控制, 做的是 simple bit-wise copy;
  2. Clone 是需要显式调用的, 如 x.clone(), 在具体实现时可能会拷贝堆内存,而 Copy 的实现一般只会做栈内存的拷贝而共享堆内存;

默认实现 Copy 的情况:

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

没有实现 Copy 的情况:

  1. 自定义的 Struct/Enum;
  2. std::collections 容器类型, 如 Vec/HashMap/HashSet; (但都实现了 Clone)
  3. &mut T 没有实现 Copy, 但是 &T 实现了 Copy.
  4. 任何实现了 Drop 的对象也没有实现 Copy.
  5. 各种智能指针,如 Box/Rc/Arc/Ref 都没有实现 Copy。

实现 Copy 方式: derive 宏或手动实现:

// OK: i32 实现了 Copy
#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

// 错误: Vec<T> 没有实现 Copy
// the trait `Copy` cannot be implemented for this type; field `points` does not implement `Copy`
#[derive(Copy, Clone)]
struct PointList {
    points: Vec<Point>,
}

// OK: 因为 &T 实现了 Copy, 即使 T 没有实现 Copy, &T 也实现了 Copy
#[derive(Copy, Clone)]
struct PointListWrapper<'a> {
    point_list_ref: &'a PointList,
}

// 或者
struct MyStruct;

impl Copy for MyStruct { }

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

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

10 Default
#

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

/// Time in seconds.
///
/// # Example
///
/// ```
/// let s = Second::default();
/// assert_eq!(0, s.value());
/// ```
pub struct Second {
    value: u64,
}

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

impl Default for Second {
    fn default() -> Self {
        Self { value: 0 }
    }
}

Rust 基本类型和 &str, &CStr, &OsStr, Box<str>, Box<CStr>, Box<OsStr>, CString, OsString, Error, PathBuf, String, &[T], Option<T>, [T; N], Rc<T>, Arc<T>, Vec<T>, HashSet<T, S> 等都实现了 Default。

如果自定义类型的成员都实现了 Default,则可以使用 derive macro 来为自定义类型实现 Default:

use std::{path::PathBuf, time::Duration};

#[derive(Default, Debug, PartialEq)]
struct MyConfiguration {
    output: Option<PathBuf>,
    search_path: Vec<PathBuf>,
    timeout: Duration,
    check: bool,
}

impl MyConfiguration {
}

fn main() {

    let mut conf = MyConfiguration::default(); // 调用 Default trait 的 default() 方法来创建对象.
    conf.check = true;
    println!("conf = {conf:#?}");


    let conf1 = MyConfiguration {
        check: true,
        ..Default::default()  // struct 对象部分输出化, 没有指定的 field 都使用 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 出来,然后将原始的 T 的值用他的 Default 填充;
let x: Option<u32> = None;
let y: Option<u32> = Some(12);

assert_eq!(x.unwrap_or_default(), 0);
assert_eq!(y.unwrap_or_default(), 12);

11 Drop
#

Drop trait 为对象提供了自定义析构能力,一般在对象离开 scope 时由编译器自动调用来释放资源:

  • Box, Vec, String, File 和 Process 等都实现了 Drop;

一般不能直接调用对象的 drop() 方法来释放资源,这样会导致对象离开作用域被二次释放。但是可以调用 drop(obj) 或 std::mem::drop/forget 来手动释放对象,这时 Rust 不会再自动释放它。

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

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

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

12 From/Into
#

实现 From/Into trait 时,相应的方法都会转移对象所有权到结果对象中:

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

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

自定义类型一般只需实现 From,Rust 会自动生成相反方向的 Into:

use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num);
}

Into 一般作用在泛型函数的限界场景中:

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

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

  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. 使用 `?` 运算符处理 `Result` 或 `Option` 错误在返回错误时,将错误类型转换为函数返回的错误类型。

       fn from_type() -> Result<IntoType, IntoError> {
           let result: Result<FromType, FromError> = do_something_may_fail();
           let from_type = result?; // 如果出错,自动调用 From<FromError> for IntoError 的实现并返回
       }
    

标准库提供了一个 blanket From trait 的实现,用于将 std:error::Error 转换为 Box<dyn std::error::Error>, ?会自动使用 From trait 来做类似转换:

impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a> {
    fn from(err: E) -> Box<dyn Error + Send + Sync + 'a> {
        Box::new(err)
    }
}

type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
type GenericResult<T> = Result<T, GenericError>;
fn parse_i32_bytes(b: &[u8]) -> GenericResult<i32> {
    Ok(std::str::from_utf8(b)?.parse::<i32>()?)
}
  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>> {
           // Ok(()) 执行这行将会正常返回
           return Err("main will return 1".into());
       }
    
       use std::net::Ipv4Addr;
       // A 是任意能转换为 Ipv4Addr 的类型
       fn ping<A>(address: A) -> std::io::Result<bool> where A: Into<Ipv4Addr>
       {
           let ipv4_address = address.into();
           // ...
       }
    

13 FromStr/ToString
#

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

Rust 基本类型,如整数、浮点数、bool、char、String、PathBuf、IpAddr、 SocketAddr、Ipv4Addr、 Ipv6Addr 都实现了该 trait。

pub trait FromStr: Sized {
    type Err;

    // Required method
    fn from_str(s: &str) -> Result<Self, Self::Err>;
}

str::parse::<T>() 方法使用 FromStr trait 将字符串转换为 T 类型对象:

pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where F: FromStr

在使用 &str.parse() 方法时一般需要指定目标对象类型,否则编译器不知道该调用哪个类型的 FromStr 实现:

let four: u32 = "4".parse().unwrap();
assert_eq!(4, four);

let four = "4".parse::<u32>();
assert_eq!(Ok(4), four);

ToString trait 用于从对象返回一个字符串对象。

Rust 对实现 Display trait 的类型自动实现了 ToString trait,例如实现了 std::error:Error trait 的对象也实现了 ToString trait:

use std::fmt;

struct Circle {
    radius: i32
}

impl fmt::Display for Circle {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Circle of radius {}", self.radius)
    }
}

fn main() {
    let circle = Circle { radius: 6 };
    println!("{}", circle.to_string());
}

14 FromIterator/IntoIterator
#

FromIterator: 从一个 IntoIterator<Item=A> 迭代器创建一个 Self 类型对象:

pub trait FromIterator<A>: Sized {

    // Required method
    fn from_iter<T>(iter: T) -> Self where T: IntoIterator<Item = A>;
}

FromIterator/IntoIterator 的典型使用场景是迭代器方法 collect::<TargetType>() ,用将前序迭代器对象的值创建一个 TargetType:

// 使用 collect 可以构建出 Rust 标准库中的任意集合,只要能迭代生成合适的条目类型即可
let args: Vec<String> = std::env::args().collect();
let args: HashSet<String> = std::env::args().collect();
let args: BTreeSet<String> = std::env::args().collect();
let args: LinkedList<String> = std::env::args().collect();
let args: HashMap<String, usize> = std::env::args().zip(0..).collect();
let args: BTreeMap<String, usize> = std::env::args().zip(0..).collect();

IntoIterator 是 for-in 迭代循环对 type 的要求,即 type 要实现 IntoIterator 才能被 for-in 迭代:

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

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

15 Extend
#

std::iter::Extend trait 用于消费传入的迭代器对象,将元素插入到自身对象中:

pub trait Extend<A> {
    // Required method
    fn extend<T>(&mut self, iter: T) where T: IntoIterator<Item = A>;

    // Provided methods
    fn extend_one(&mut self, item: A) { ... }
    fn extend_reserve(&mut self, additional: usize) { ... }
}

Rust 各种集合类型都实现了 Extend trait:

let mut message = String::from("The first three letters are: ");
message.extend(&['a', 'b', 'c']);
assert_eq!("abc", &message[29..32]);

Extend 和 std::iter::FromIterator 非常类似,但后者会创建新的集合,而 Extend 扩充 Self 自己。

16 AsRef/AsMut
#

AsRef/AsMut 多用于泛型参数限界,例如:open() 的实现依赖于 &Path,通过限定 P 实现了 AsRef<Path>,在 open() 内部就可以通过 P.as_ref() 方法调用返回 &Path 的类型对象:

fn open<P: AsRef<Path>>(path: P) -> Result<file>

标准库各种类型,如 String/str/OsString/OsStr/PathBuf/Path 等实现了 AsRef<Path>, 所以这些类型对象都可以作为 open() 的参数:

// https://doc.rust-lang.org/src/std/path.rs.html#3170
impl AsRef<Path> for String {
    #[inline]
    fn as_ref(&self) -> &Path {
        Path::new(self)
    }
}

String 实现了 AsRef<str>, AsRef<[u8]>, AsRef<Path>。

17 运算符重载/PartialEq/PartialOrd/Eq/Ord
#

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

PartialEq 只包含 eq/ne() 方法,而 PartialOrd 只需要实现 partial_cmp() 方法:

  • Eq 是 PartialEq 的 子 trait;
  • Ord 是 PartialOrd + Eq 的子 trait;
  • PartialOrd 是 PartialEq 的子 trait;

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

pub trait PartialEq<Rhs = Self> where Rhs: ?Sized,
{
    // Required method
    fn eq(&self, other: &Rhs) -> bool;

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

pub trait Eq: PartialEq { }

// PartialOrd 是 PartialEq 的子 trait
pub trait PartialOrd<Rhs = Self>: PartialEq<Rhs> where Rhs: ?Sized,
{
    // Required method
    fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;

    // Provided methods
    fn lt(&self, other: &Rhs) -> bool { ... }
    fn le(&self, other: &Rhs) -> bool { ... }
    fn gt(&self, other: &Rhs) -> bool { ... }
    fn ge(&self, other: &Rhs) -> bool { ... }
}

// 实现 Ord 的同时需要实现:Eq 和 PartialOrd,以及 PartialEq
pub trait Ord: Eq + PartialOrd {
    // Required method
    fn cmp(&self, other: &Self) -> Ordering;

    // Provided methods
    fn max(self, other: Self) -> Self where Self: Sized
    fn min(self, other: Self) -> Self where Self: Sized
    fn clamp(self, min: Self, max: Self) -> Self where Self: Sized + PartialOrd
}

可以使用 derive macro 来自动生成 PartialEq/PartialOrd 的实现:

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

也可以手动实现 PartialEq/PartialOrd trait:

#[derive(PartialEq)]
enum BookFormat {
    Paperback,
    Hardback,
    Ebook,
}

#[derive(PartialEq)]
struct Book {
    isbn: i32,
    format: BookFormat,
}

// PartialEq 是泛型 trait,可以在实现时指定泛型参数的类型
impl PartialEq<BookFormat> for Book {
    fn eq(&self, other: &BookFormat) -> bool {
        self.format == *other
    }
}

impl PartialEq<Book> for BookFormat {
    fn eq(&self, other: &Book) -> bool {
        *self == other.format
    }
}

fn main() {
    let b1 = Book { isbn: 1, format: BookFormat::Paperback };
    let b2 = Book { isbn: 2, format: BookFormat::Paperback };

    assert!(b1 == BookFormat::Paperback);
    assert!(BookFormat::Paperback == b2);
}

std::ops module 下的各 trait 用以支持运算符重载:

use std::ops;

struct Foo;

struct Bar;

#[derive(Debug)]
struct FooBar;

#[derive(Debug)]
struct BarFoo;

impl ops::Add<Bar> for Foo {
    type Output = FooBar;

    fn add(self, _rhs: Bar) -> FooBar {
        println!("> Foo.add(Bar) was called");
        FooBar
    }
}

impl ops::Add<Foo> for Bar {
    type Output = BarFoo;

    fn add(self, _rhs: Foo) -> BarFoo {
        println!("> Bar.add(Foo) was called");

        BarFoo
    }
}

fn main() {
    println!("Foo + Bar = {:?}", Foo + Bar);
    println!("Bar + Foo = {:?}", Bar + Foo);
}

// 实现泛型 trait
impl<L, R> Add<Complex<R>> for Complex<L> where: L: Add<R>,
{
    type Output = Complex<L::Output>;

    fn add(self, rhs: Complex<R>) -> Self::Output {
        Complex{
            re: self.re + rhs.re,
            im: self.im + rhs.im,
        }
    }
}

其它运算符:

  1. i..j, i.., ..j, i..=j 等:是 RangeXX struct 类型语法糖;
  2. ?:适用于 Result 和 Option,而且可以和 From/Into trait 协作,进行自动类型转换;
  3. *val:使用 Deref/DerefMut trait 来进行重载;

对于 * 和 & 运算符,使用 Deref/DerefMut trait 重载。

18 Index/IndexMut
#

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

a[i] 调用a.index(i) 方法后再自动解引用,所以等效于 *a.index(i) 或 *a.index_mut(i) 。返回一个对象的好处是,可以作为左值使用,例如 a[i] = 3。

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

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

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

  • Range: m..n
  • RangeFrom: m..
  • RangeFull: ..
  • RangeInclusive: m..=n
  • RangeTo: ..n
  • RangeToInclusive: ..=n
#[stable(feature = "rust1", since = "1.0.0")]
impl<I> ops::Index<I> for str where I: SliceIndex<str>
{
    type Output = I::Output;

    #[inline]
    fn index(&self, index: I) -> &I::Output {
        index.index(self)
    }
}

// SliceIndex 文档页面的 Implementors 部分可以看到实现 SliceIndex<str> 的类型列表,以及对应的关联类型 Output 的具体类型

impl SliceIndex<str> for (Bound<usize>, Bound<usize>)
type Output = str

impl SliceIndex<str> for Range<usize>
type Output = str

impl SliceIndex<str> for RangeFrom<usize>
type Output = str

impl SliceIndex<str> for RangeFull
type Output = str

impl SliceIndex<str> for RangeInclusive<usize>
type Output = str

impl SliceIndex<str> for RangeTo<usize>
type Output = str

impl SliceIndex<str> for RangeToInclusive<usize>
type Output = str

19 Borrow/ToOwned/Cow
#

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

Borrow 用于解决泛型哈希表和其他关联集合类型的情况:K 和 Q 都是 Eq + Hash 语义,K: Borrow<Q> 表示可以从 K 对象生成 &Q 的引用。所以可以给 HashMap 的 get() 方法传入任何满足上面两个约束的引用对象。

  • 在 get() 方法的内部实现中,会从自身的 K.borrow() 生成 &Q 对象,然后用 K 的 Eq+Hash 值与 Q 的 Eq+Hash 值进行比较;
  • self 一般是 owned 类型, 如 String/PathBuf/OsString/Box/Arc/Rc 等, borrow 的结果是对应的 unsized 类型的借用, 如 &str/&Path/&OsStr/&T;
impl<K,V> HashMap<K, V> where K: Eq + Hash
{
    // key 必须是借用类型 &Q,而且需要 K 实现了 Borrow<Q>
    fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V> where K: Borrow<Q>, Q: Eq + Hash
    // ...
}

String 和 &str 保证做相同的 hash,所以 String 只实现按了 Borrow<str>, 所以 HashMap<String, i32> 的 get() 方法可以传入 &String 或 &str 类型值。

// 从 Ownerd 类型生成 unsized 的类型引用
impl Borrow<str> for String
impl Borrow<CStr> for CString
impl Borrow<OsStr> for OsString
impl Borrow<Path> for PathBuf
impl<'a, B> Borrow<B> for Cow<'a, B> where B: ToOwned + ?Sized,

// 从 &T/&mut T/T 类型值产生 &T
impl<T> Borrow<T> for &T where T: ?Sized,
impl<T> Borrow<T> for &mut T where T: ?Sized,
impl<T> Borrow<T> for T where T: ?Sized,

impl<T, A> Borrow<[T]> for Vec<T, A>  where A: Allocator,
impl<T, A> Borrow<T> for Box<T, A> where A: Allocator, T: ?Sized,
impl<T, A> Borrow<T> for Rc<T, A> where A: Allocator, T: ?Sized,
impl<T, A> Borrow<T> for Arc<T, A> where A: Allocator, T: ?Sized,
impl<T, const N: usize> Borrow<[T]> for [T; N]  // &[T;N] -> &[T]

std::borrow::ToOwned trait 从 &T 生成 Owned 类型:

  • &str -> String
  • &CStr -> CString
  • &OsStr -> OsString
  • &Path -> PathBuf
  • &[T] -> Vec<T>
  • &T -> T
pub trait ToOwned {
    // Owned 是实现了 Borrow<Self> 的任意类型
    type Owned: Borrow<Self>;

    // Required method
    fn to_owned(&self) -> Self::Owned;

    // Provided method
    fn clone_into(&self, target: &mut Self::Owned) { ... }
}

// 实现 ToOwned 的类型都是 unsized 类型, 它们的 Owned 类型都是 sized 版本
impl ToOwned for str type Owned = String
impl ToOwned for CStr type Owned = CString
impl ToOwned for OsStr type Owned = OsString
impl ToOwned for Path type Owned = PathBuf
impl<T> ToOwned for [T] where T: Clone, type Owned = Vec<T>
impl<T> ToOwned for T where T: Clone, type Owned = T

// 示例
let s: &str = "a";
let ss: String = s.to_owned();
let v: &[i32] = &[1, 2];
let vv: Vec<i32> = v.to_owned();

Clone 和 ToOwned 区别:

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

std::borrow::Cow 是可以保存 &B 或 B 的 Owned 类型的枚举类型(cow:clone on write):

// B 必须实现 ToOwned trait
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized,
{
    Borrowed(&'a B), // &B
    Owned(<B as ToOwned>::Owned), // &B 的 Owned 类型
}

Cow 实现了各种 From trait,可以从多种类型对象生成 Cow:

impl<'a, T> From<&'a [T]> for Cow<'a, [T]> where T: Clone
impl<'a, T, const N: usize> From<&'a [T; N]> for Cow<'a, [T]> where T: Clone
impl<'a> From<&'a CStr> for Cow<'a, CStr>
impl<'a> From<&'a CString> for Cow<'a, CStr>
impl<'a> From<&'a OsStr> for Cow<'a, OsStr>
impl<'a> From<&'a OsString> for Cow<'a, OsStr>
impl<'a> From<&'a Path> for Cow<'a, Path>
impl<'a> From<&'a PathBuf> for Cow<'a, Path>
impl<'a> From<&'a String> for Cow<'a, str>
impl<'a, T> From<&'a Vec<T>> for Cow<'a, [T]> where T: Clone
impl<'a> From<&'a str> for Cow<'a, str>
impl<'a> From<CString> for Cow<'a, CStr>

如果 Cow 实际保存的是 Borrowed<&B> 对象, 则在调用 into_owned()/to_mut() 方法时, 会调用 &B 实现的 ToOwned trait 来生成 Owned 对象, 也就是实现了 Clone on Write 的特性。

Cow<'a, B> 实现了 Deref<Target=B>, 所以可以直接调用 B 的方法。

// Cow 方法:
pub fn is_borrowed(&self) -> bool
pub fn is_owned(&self) -> bool
pub fn into_owned(self) -> <B as ToOwned>::Owned   // 消耗 Cow, 生成 Owned 对象
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned // 返回 Owned 对象的 &mut 引用。

// 函数参数是 Cow<'_, [i32> ], 故可以保存 &[i32] 或它的 Owned 类型 Vec<i32>
fn abs_all(input: &mut Cow<'_, [i32]>) {
    // Cow 实现了 Deref<Target=B>, 所以可以调用 B 的方法。
    for i in 0..input.len() {
        let v = input[i];
        if v < 0 {
            // 这里是通过 &[T] 实现的 ToOwned trait 来生成一个 &mut Vec<T> 变量,然后修改它。
            input.to_mut()[i] = -v;
        }
    }
}

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

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

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


// Another example showing how to keep Cow in a struct:
use std::borrow::Cow;

struct Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    values: Cow<'a, [X]>,
}

impl<'a, X: Clone + 'a> Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    fn new(v: Cow<'a, [X]>) -> Self {
        Items { values: v }
    }
}

let readonly = [1, 2];
let borrowed = Items::new((&readonly[..]).into());
match borrowed {
    Items { values: Cow::Borrowed(b) } => println!("borrowed {b:?}"),
    _ => panic!("expect borrowed value"),
}

let mut clone_on_write = borrowed;
clone_on_write.values.to_mut().push(3);
println!("clone_on_write = {:?}", clone_on_write.values);
match clone_on_write {
    Items { values: Cow::Owned(_) } => println!("clone_on_write contains owned data"),
    _ => panic!("expect owned data"),
}

Cow 语义看成『potentially owned』,即可能拥有所有权,可以用来避免一些不必须的拷贝。比如保存函数内临时对象的引用:

fn foo(s: &str, some_condition: bool) -> &str {
    if some_condition {
        // 错误:在 block 结束时临时对象被释放,临时对象的引用无效
        &s.replace("foo", "bar")
    } else {
        s
    }
}

// 如果把返回值改成 String,那么在 else 分支会有一次额外的拷贝,这时 Cow 就可以派上用场了。
fn foo(s: &str, some_condition: bool) -> Cow<str> {
    if some_condition {
        Cow::from(s.replace("foo", "bar")) // 保存 Owned 的 String 对象
    } else {
        Cow::from(s) // 保存 &str
    }
}

20 Deref/DerefMut
#

Deref/DerefMut trait 定义:

pub trait Deref {
    type Target: ?Sized;

    // Required method
    fn deref(&self) -> &Self::Target;
}

pub trait DerefMut: Deref {
    // Required method
    fn deref_mut(&mut self) -> &mut Self::Target;
}

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

  1. *t 返回 U,等效于 *Deref.deref(&t);
  2. &t 返回 &U, 等效于 Deref.deref(&t);

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

对于实现了 Deref<Target=U> 的类型 T 值, &*T 返回 &U:

  • Deref 重载了 * 运算符, 所以 *T == *t.deref(), 返回 U 对象, 为了获得 &U 可以使用表达式 &*T;

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

  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 。

21 smart pointer
#

只要实现了 Deref trait 的类型都可以称为 smart pointer,例如:

// https://doc.rust-lang.org/std/ops/trait.Deref.html

impl Deref for String
  type Target = str

impl<B> Deref for Cow<'_, B> where B: ToOwned + ?Sized, <B as ToOwned>::Owned: Borrow<B>
  type Target = B

impl<P> Deref for Pin<P> where P: Deref
  type Target = <P as Deref>::Target

impl<T> Deref for &T where T: ?Sized
  type Target = T

impl<T> Deref for &mut T where T: ?Sized
  type Target = T

impl<T> Deref for Ref<'_, T> where T: ?Sized
  type Target = T

impl<T> Deref for RefMut<'_, T> where T: ?Sized
  type Target = T

impl<T, A> Deref for Box<T, A> where A: Allocator, T: ?Sized
  type Target = T

impl<T, A> Deref for Rc<T, A> where A: Allocator, T: ?Sized
  type Target = T

impl<T, A> Deref for Arc<T, A> where A: Allocator, T: ?Sized
  type Target = T

impl<T, A> Deref for Vec<T, A> where A: Allocator
  type Target = [T]

Ref<T>/RefMut<T>/Rc<T>/Arc<T>/Box<T> :

  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]);
// 以下两个方法调用等价,但是建议使用第二种方式: 全限定名称的方法或关联函数调用
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<T> 实现了 Deref<Target = [T]>;
fn read_slice(slice: &[usize]) {
    // ...
}

let v = vec![0, 1];
read_slice(&v); // Deref 自动转换

let u: &[usize] = &v; // Deref 自动转换

在函数传参匹配 trait bound 时不会进行协变和自动 Deref trait 调用,例如虽然 &mut i32 可以协变到 &i32, 但传参时不会协变。带来的影响是:如果 Trait 作为函数参数限界,&i32 和 &mut i32 两种类型都需要实现该 Trait。

trait Trait {}

fn foo<X: Trait>(t: X) {}

impl<'a> Trait for &'a i32 {}

fn main() {
    let t: &mut i32 = &mut 0;
    foo(t);
}

// error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
//  --> src/main.rs:9:9
//   |
// 3 | fn foo<X: Trait>(t: X) {}
//   |           ----- required by this bound in `foo`
// ...
// 9 |     foo(t);
//   |         ^ the trait `Trait` is not implemented for `&mut i32`
//   |
//   = help: the following implementations were found:
//             <&'a i32 as Trait>
//   = note: `Trait` is implemented for `&i32`, but not for `&mut i32`

22 Rc/Arc
#

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

  1. Rc::clone(&a) 增加 a 的引用计数,并不会重新分配堆内存;
  2. Rc 和 clone() 返回的 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)))));
    let b = Cons(3, Rc::clone(&a)); // 不能使用 Box<T>, 否则这里会发生所有权转移,后续不能再使用它。
    let c = Cons(4, Rc::clone(&a));
}

打印引用计数:

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));

    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}

// $ cargo run
//    Compiling cons-list v0.1.0 (file:///projects/cons-list)
//     Finished dev [unoptimized + debuginfo] target(s) in 0.45s
//      Running `target/debug/cons-list`
// count after creating a = 1
// count after creating b = 2
// count after creating c = 3
// count after c goes out of scope = 2

Rc 提供了 get_mut()/make_mut()/into_inner() 方法来修改对象,Rc::get_mut() 方法

pub fn get_mut(this: &mut Rc<T, A>) -> Option<&mut T>

当没有其他 Rc 对象引用 this 时, 也就是 this Rc 对象的引用计数为 1 时, 返回 &mut T, 否则返回 None.

  • 而 make_mut() 方法在引用计数 >=1 时, clone 一个新对象来替换 this, 然后返回他的 &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());

Rc::make_mut() 方法:

pub fn make_mut(this: &mut Rc<T, A>) -> &mut T

创建传入 Rc<T> 的 &mutT 可变引用,当调用该方法时, 如果传入的 Rc 对象被 clone 过, 也就是引用计数 >=2, 则该方法会 clone 生成一个新的 Rc 对象并替换 this, 这被称为 clone-on-write;

use std::rc::Rc;

let mut data = Rc::new(5);
*Rc::make_mut(&mut data) += 1;         // Won't clone anything
let mut other_data = Rc::clone(&data); // Won't clone inner data

// 由于 data 被 clone 过, 再次调用 data 的 make_mut 时会 clone 一个新 Rc 对象并替换 data
// 这是  other_data 的引用计数 -1 变为 1, data 的引用计数也为 1
*Rc::make_mut(&mut data) += 1;         // Clones inner data
// data 是刚才  clone 生成的新对象, 计数为 1, 所以这次不会再 clone
*Rc::make_mut(&mut data) += 1;         // Won't clone anything

// other_data 引用计数为 1, 所以也不会 clone
*Rc::make_mut(&mut other_data) *= 2;   // Won't clone anything

// Now `data` and `other_data` point to different allocations.
assert_eq!(*data, 8);
assert_eq!(*other_data, 12);

Rc::into_inner() 方法:

pub fn into_inner(this: Rc<T, A>) -> Option<T>

Returns the inner value, if the Rc has exactly one strong reference. Otherwise, None is returned and the Rc is dropped.

  • 传入的是 Rc 对象本身而非引用, 会消耗传入的 Rc(其他类型的 into_inner() 方法都是类似的语义).

This will succeed even if there are outstanding weak references.

If Rc::into_inner is called on every clone of this Rc, it is guaranteed that exactly one of the calls returns the inner value. This means in particular that the inner value is not dropped.

This is equivalent to Rc::try_unwrap(this).ok(). (Note that these are not equivalent for Arc, due to race conditions that do not apply to Rc.)

另外,into_inner() 是一个常见的类型方法, 都是从包装类型生成对应的 T, 例如:

  • pub fn into_inner(boxed: Box<T, A>) -> T
  • Cell/RefCell
// pub fn into_inner(self) -> T

use std::cell::Cell;
let c = Cell::new(5);
let five = c.into_inner();
assert_eq!(five, 5);

使用 Rc/Arc 可能创建出一些循环引用的数据解构,导致 Rc/Arc 管理的对象引用计数 strong_count() 不为 0,从而发生内存泄露。

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}

impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    println!("b initial rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // Uncomment the next line to see that we have a cycle;
    // it will overflow the stack
    // println!("a next item = {:?}", a.tail());

    // block 结束时, b 被 drop 后 strong_count() 为 1, 所以堆内存不会被释放, a 也是同样的情况.
}

解决办法:使用 Rc::downgrade() 而非 Rc::clone() 来增加对象弱引用计数 weak_count() 而不会增加 strong_count(), 他返回一个 Weak<T> 智能指针。

  • weak_count() 记录 Rc 对象的 Weak<T> 对象数量。
  • Rc<T> 并不会在执行清理时要求 weak_count() 必须为 0;一旦强引用计数为 0,任何由弱引用组成的循环就会被打破,因此弱引用不会造成循环依赖;
  • 由于 Weak<T> 引用的值可能会被释放,可以使用 weak.upgrade() 方法来验证 weak 指向的值是否存在,他返回一个 Option<Rc<T>>, 在 Rc<T> 值存在时返回 Some, 否则返回 None.
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}

23 Pin/UnPin
#

由于 move 机制的存在,导致 Rust 很难去正确表达『自引用』的结构,比如链表、树等。这是由于 move 只会进行值本身的拷贝,指针的指向则不变。如果被 move 的结构有指向其他字段的指针,那么这个指向被 move 后就是非法的,因为原始指向已经换地址了。

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

pub trait Future {
    type Output;

    // Required method
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

#![feature(noop_waker)]
use std::future::Future;
use std::task;
let waker = task::Waker::noop();
let mut cx = task::Context::from_waker(&waker);
let mut future = Box::pin(async { 10 });
assert_eq!(future.as_mut().poll(&mut cx), task::Poll::Ready(10));

Pin 是 struct std::pin::Pin struct 类型,而 Unpin 是标准库提供的 marker trait,标准库为绝大部分类型实现了 Unpin。

使用 Pin::new(&mut T) 函数来创建 Pin struct 类型值。T 需要shxian Unpin trait。

use std::pin::Pin;
let mut unpin_future = std::future::ready(5);
let my_pinned_unpin_future: Pin<&mut _> = Pin::new(&mut unpin_future); // 传入的是 &mut T

// 如果要 Pin<Box<T>>, 则需要使用 Box::pin(value) 函数
use std::pin::Pin;

async fn add_one(x: u32) -> u32 {
    x + 1
}
let fut = add_one(42);
let pinned_fut: Pin<Box<_>> = Box::pin(fut);

// 如果要 Pin<Box<dyn Trait>> ,则需要使用 Box::into_pin(value) 函数
async fn add_one(x: u32) -> u32 {
    x + 1
}
fn boxed_add_one(x: u32) -> Box<dyn Future<Output = u32>> {
    Box::new(add_one(x))
}
let boxed_fut = boxed_add_one(42);
let pinned_fut: Pin<Box<_>> = Box::into_pin(boxed_fut);

如果在 non_std 环境创建 Pin<T> 或则 T 没有实现 Unpin,则需要使用 pin!() 宏:

  • pin!() 宏:Constructs a Pin<&mut T>, by pinning a value: T locally.
use core::pin::{pin, Pin};

fn stuff(foo: Pin<&mut Foo>) {
    // …
}

let pinned_foo = pin!(Foo { /* … */ });
stuff(pinned_foo);

// Manually polling a Future (without Unpin bounds)
use std::{
    future::Future,
    pin::pin,
    task::{Context, Poll},
    thread,
};

fn block_on<Fut: Future>(fut: Fut) -> Fut::Output {
    let waker_that_unparks_thread = {/*.*/};

    let mut cx = Context::from_waker(&waker_that_unparks_thread);

    // Pin the future so it can be polled.
    let mut pinned_fut = pin!(fut);

    loop {
        match pinned_fut.as_mut().poll(&mut cx) {
            Poll::Pending => thread::park(),
            Poll::Ready(res) => return res,
        }
    }
}

24 Send/Sync
#

Send 和 Sync 都是 marker trait:

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

对于 thread 闭包函数,一般使用 move 来转移引用的对象,所以对象类型必须实现 Send。

  • 对于 scope thread 的闭包函数,可以不使用 move,它们可以共享引用父 thread 的 stack 变量,所以要求这些对象必须实现 Sync。

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

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

没有实现 Send/Sync 的类型:

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

相关文章

不安全:unsafe
··824 字
Rust
Rust
借用:refer/borrow
··3120 字
Rust
Rust 引用类型和借用
内置类型:type
··16432 字
Rust
Rust 内置基本类型介绍
函数、方法和闭包:function/method/closure
··6964 字
Rust
Rust 函数、方法和闭包