跳过正文

10. 泛型和特性:generic/trait

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

泛型
#

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

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

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

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

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

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

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

为泛型类型实现关联函数或方法时,impl<...> 需要列出泛型类型所需的所有泛型参数:

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

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

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

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

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

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

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

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

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

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

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

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

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

Rust 在编译时进行泛型代码的单态化 (monomorphization),单态化是一个编译时生成具体实现类型(一般是匿名的)的步骤,将泛型代码转换为具体特定类型代码的过程。《== 空间换时间。

泛型参数和限界
#

泛型函数:包含泛型参数的函数,语法为 fn my_func<X, Y, Z> ,其中 <X, Y, Z> 为泛型参数列表(Generic parameters)。

函数名后可以使用泛型参数列表 <x, y:xx, z:xx + yy> 来定义泛型函数,该列表的语法格式如下:

GenericParams :
   < >
   | < (GenericParam ,)* GenericParam ,? >

有三种泛型参数(Generic parameters)类型:lifetime,type,const

GenericParams :
   < >
   | < (GenericParam ,)* GenericParam ,? >

GenericParam :
   OuterAttribute* ( LifetimeParam | TypeParam | ConstParam )

LifetimeParam lifetime 类型
#

指定 lifetime 和它的限界,lifetime 的限界只能是其它 lifetime 或 'static 或 '_

LifetimeParam :
  LIFETIME_OR_LABEL ( : LifetimeBounds )?

LifetimeBounds :
  ( Lifetime + )* Lifetime?

Lifetime :
      LIFETIME_OR_LABEL
  | 'static
  | '_

如:'a, 'a: 'b + 'c, 'a: 'static'a: '_, 其中 '_ 表示让编译器自动推导(如根据 lifetime elision rule);

ConstParam 泛型常量类型
#

ConstParam:
  const IDENTIFIER : Type ( = Block | IDENTIFIER | -?LITERAL )? // = 表示可以为常量指定缺省值

泛型参数也支持常量类型,如 <const N: usize>const N: usize = 4,它可以作为泛型类型/函数/方法中的数组长度(数组长度必须是编译时常量)。

Rust 泛型常量类型(Type)支持:

  1. 整数常量(usize, u8 等)
  2. 布尔常量
  3. 字符常量
  4. 枚举值(要求实现 ConstParamTy)
  5. 已有 const 引用
  6. 数组 / 元组常量
  7. 编译期常量表达式
  8. 嵌套 const 计算

实例化常量参数时,使用字面量或 {XXX} 常量表达式或由编译器推导:

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

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

// 实例化常量参数,可以直接指定常量值或者使用 {xx} 常量表达式
fn foo<const N: usize>() {}

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

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

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

TypeParam 类型限界
#

泛型类型参数,可以用 lifetime 和 trait 进行限界(TypeParamBounds)。

也可以指定缺省类型 Type,如 fn sss<T=i32>fn sss<S: Trait, T=<S as Trait>::Assoc>, 这样后续调用该 函数时可以不用指定该泛型参数的具体类型;

TypeParam :
  IDENTIFIER( : TypeParamBounds? )? ( = Type )? // = Type 表示可以给泛型参数设置缺省类型

TypeParamBounds :
  TypeParamBound ( + TypeParamBound )* +? // 泛型参数可以使用 + 连接多个限界条件,限界可以是 lifetime 或 trait bound

TypeParamBound :
      Lifetime | TraitBound

TraitBound :
      ?? ForLifetimes? TypePath // ?? 表示可选的 ?,表示该 trait boud 是可选。ForLifetimes 表示可以使用 HRTB 定义 TypePaht 中可用的 HRTB liftime。
  | ( ?? ForLifetimes? TypePath )  // 这里的括号为字面量, 不用于分组

TraitBound 使用的 TypePath(Paths in types) 由一个或多个 :: 分割的 TypePathSegment 组成。

TypePathSegment 包含三种类型:

  1. PathIdentSegment: 标识符,如:T: std::ops::Index
  2. GenericArgs: 进一步使用泛型参数 <xx>,如 T: std::ops::Index<'a, std::ops::Range<usize>>
  • PathIdentSegment: std::ops::Index
  • GenericArgs: <'a, std::ops::Range<usize>>
  1. TypePathFn:泛型函数,如 T: FnOnce() -> T :
  • PathIdentSegment: FnOnce
  • TypePathFn: () -> T

GenericArgs<xx,yy> 格式,其中的内容分为如下类型:Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds

  1. Lifetime:T: std::ops::Index<'a>
  2. Lifetime + Type: T: std::ops::Index<'a, i32>
  3. GenericArgsConst: T: m::m2::MyTrait<23, 'x', U>
  4. GenericArgsBinding,即对关联类型绑定具体类型: T: Add<U, Output = Option<i32>>
  5. GenericArgsBounds,即对关联类型进一步限界: T: outer::OuterTrait<Inner<u32>: outer::InnerTrait<u32>>

关于关联类型限界参考:https://rust-lang.github.io/rfcs/2289-associated-type-bounds.html?highlight=associated#

// 如 ::std::ops::Index
TypePath :
  ::? TypePathSegment (:: TypePathSegment)* // 一个或多个 :: 分割的 TypePathSegment

// TypePathSegment 典型的情况是 :: 分割的标识符,如 ::std::ops::Index,但也支持更复杂的情况
TypePathSegment :
  PathIdentSegment (::? (GenericArgs | TypePathFn))?

PathIdentSegment →
    IDENTIFIER | super | self | Self | crate | $crate

// 函数闭包形式限界,如 fn myfunc(F: Fn(i32, &str) -> bool) {} 或 fn myfunc(S, F: Fn(i32, <S as Trait>::Assoc) -> bool)
// pub fn spawn<F, T>(f: F) -> JoinHandle<T> where F: FnOnce() -> T + Send + 'static, T: Send + 'static,
TypePathFn :
( TypePathFnInputs? ) (-> Type)?

// 这里的输入参数 Type 可以是各种类型的格式,如 QualifiedPathInType 对应的 <S as Trait>::Assoc
TypePathFnInputs :
  Type (, Type)* ,?

// 限界的 IDENTIFIER 本省是泛型类型,如 std::ops::Index 时,可以对它的泛型参数进一步限界,如:
// T: std::ops::Index<'a, std::ops::Range<usize>> //  GenericArgs = <'a, std::ops::Range<usize>>, GenericArg = 'a, GenericArg = std::ops::Range<usize>
GenericArgs :
      < >
  | < ( GenericArg , )* GenericArg ,? >

// GenericArgs 是生命期参数、类型参数、const 参数、绑定(binding)、bounds 等的组合。
GenericArg :
  Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds

// 类型参数 Type 的定义如上,它包含 TypePath,所以 <> 内形成一个递归嵌套的例子:
// fn myFunc<I> where I: std::ops::Index<std::ops::Range<usize>> // Type = std::ops::Range<usize>

// 泛型常量和标识符:
// fn myFunc<U, T: m::m2::MyTrait<23, 'x', U>> {} // LiteralExpression = 23,  LiteralExpression='x', SimplePathSegment = U
GenericArgsConst :
      BlockExpression
  | LiteralExpression
  | - LiteralExpression
  | SimplePathSegment

SimplePathSegment →
  IDENTIFIER | super | self | crate | $crate

// 为泛型参数的关联类型指定具体类型,这里的 IDENTIFIER 本身也可能是泛型,它的泛型参数为 GenericArgs(递归嵌套),如:
// fn add_all<T, U>(x: T, y: T) -> <T as Add<U>>::Output where T: Add<U, Output = Option<i32>>, //  IDENTIFIER = Output, Type = Option<i32>
//
//trait Trait { type Item<T>; }
//struct S;
//impl Trait for S { type Item<T> = Vec<T>;}
//fn multi_path<T>() -> Vec<i32> where S: Trait<Item<i32> = Vec<i32>>, // IDENTIFIER = S::Item, GenericArgs = <i32>, Type = Vec<i32>
//
GenericArgsBinding :
  IDENTIFIER GenericArgs? = Type // IDENTIFIER 必须是关联类型标识符

// 为泛型参数的关联类型进一步限界,这里的 IDENTIFIER 本身也可能是泛型,它的泛型参数为 GenericArgs(递归嵌套),如:
// fn f_extended<U>() where D: Trait2<Output: InnerTrait + AnotherTrait>, // IDENTIFIER = Output, TypeParamBounds = InnerTrait + AnotherTrait
// fn f5<T: outer::OuterTrait<Inner<u32>: outer::InnerTrait<u32>>>(x: T)    // IDENTIFIER = Inner, GenericArgs = <Inner<u32>,TypeParamBounds = outer::InnerTrait<u32>
//
GenericArgsBounds :
  IDENTIFIER GenericArgs? : TypeParamBounds

举例:

// 基本的 type 参数 + 单一 trait bound
fn f1<T: Clone>(x: T) -> T {
    x.clone()
}

// 多个 trait bound
fn f2<T: Clone + Default>(x: Option<T>) -> T {
    x.unwrap_or_default()
}

// 带生命周期 bound
fn f3<'a, T: 'a>(x: &'a T) -> &'a T {
    x
}

// lifetime 参数之间的 bound
fn f4<'a: 'b, 'b>(x: &'a &'b str) -> &'b str {
    x
}

// const 参数带 trait bound
fn f5<const N: usize>(x: [u8; N]) -> [u8; N]
where
    [u8; N]: Copy,
{
    x
}

// trait bound 使用 TypePath(简单路径)
fn f6<T: std::fmt::Debug>(x: T) {
    println!("{:?}", x);
}

// trait bound 使用 TypePath + 泛型参数
fn f7<T: std::ops::Add<Output = T>>(x: T, y: T) -> T {
    x + y
}

// trait bound 使用 TypePath + const 泛型
fn f8<const N: usize>(x: [u8; N])
where
    [u8; N]: PartialEq,
{
    let _ = x == x;
}

// trait bound 使用 TypePath + 函数式参数 (TypePathFn)
fn f9<F>(f: F, x: i32) -> bool
where
    F: Fn(i32) -> bool,
{
    f(x)
}

// Qualified Path in bound
trait Trait { type Item; }
struct S;
impl Trait for S {
    type Item = u32;
}

fn f10<T: Trait<Item = u32>>(x: T) -> T::Item {
    42
}

// Higher-Rank Trait Bound (HRTB)
fn f11<F>(f: F)
where
    F: for<'a> Fn(&'a str) -> &'a str,
{
    let _ = f("hi");
}

// 多层路径 + 组合泛型参数
fn f12<T>()
where
    T: ::std::iter::Iterator<Item = std::option::Option<String>>,
{
}

// ==========================
// 1. 基本 GenericArgsBinding
use std::ops::Add;

// 泛型函数,绑定 Add 的关联类型 Output
fn add_all<T, U>(x: T, y: T) -> <T as Add<U>>::Output
where
    T: Add<U, Output = Option<i32>>, // GenericArgsBinding: Output = Option<i32>
{
    x + y
}

fn main() {
    let a: Option<i32> = Some(1);
    let b: Option<i32> = Some(2);
    let c = add_all(a, b);
    println!("{:?}", c);
}

// ==========================
// 2. 带多层路径的 GenericArgsBinding
trait Trait {
    type Item<T>;
}

struct S;

impl Trait for S {
    type Item<T> = Vec<T>;
}

// 泛型函数,绑定 S::Item<T> 到具体类型 Vec<i32>
fn multi_path<T>() -> Vec<i32>
where
    S: Trait<Item<i32> = Vec<i32>>, // IDENTIFIER = S::Item, GenericArgs = <i32>, Type = Vec<i32>
{
    vec![1, 2, 3]
}

// ==========================
// 3. 带 const 泛型的 GenericArgsBinding
struct ArrayHolder<const N: usize>;

// Trait 带 const 泛型关联类型
trait Container {
    type Arr<const M: usize>;
}

struct C;

impl Container for C {
    type Arr<const M: usize> = [u8; M];
}

// 泛型函数,绑定关联类型到具体 const 数组类型
fn const_binding<const N: usize>() -> [u8; N]
where
    C: Container<Arr<N> = [u8; N]>, // IDENTIFIER = Arr, GenericArgs = <N>, Type = [u8; N]
{
    [0; N]
}

// ==========================
// 4. 带泛型 + 多层路径 + const 泛型
trait Trait2 {
    type Output<T, const M: usize>;
}

struct D;

impl Trait2 for D {
    type Output<T, const M: usize> = [T; M];
}

// 泛型函数示例
fn combined<T, const N: usize>() -> [T; N]
where
    D: Trait2<Output<T, N> = [T; N]>, // IDENTIFIER = Output, GenericArgs = <T, N>, Type = [T; N]
{
    // 这里只能用 Default 或 Copy 类型填充
    [T::default(); N]
}

mod outer {
    // Inner 是一个泛型 trait
    pub trait InnerTrait<T> {}

    // OuterTrait 的关联类型 Inner 是一个 trait,而不是具体类型
    pub trait OuterTrait {
        type Inner<U>: InnerTrait<U>;
    }
}

struct A;

// 泛型函数中直接在 fn 泛型参数列表使用 GenericArgsBounds
fn f5<T: outer::OuterTrait<Inner<u32>: outer::InnerTrait<u32>>>(x: T) {
    // IDENTIFIER = Inner, GenericArgs = <Inner<u32>,TypeParamBounds = outer::InnerTrait<u32>
}

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

where 子句
#

where 子句的定义如下:

// https://doc.rust-lang.org/reference/items/generics.html#grammar-WhereClause
WhereClause  where ( WhereClauseItem , )* WhereClauseItem?
WhereClauseItem 
      LifetimeWhereClauseItem
    | TypeBoundWhereClauseItem
LifetimeWhereClauseItem  Lifetime : LifetimeBounds
// 进行限界的 Key 为 Type,而不是标识符
TypeBoundWhereClauseItem  ForLifetimes? Type : TypeParamBounds?

// 其中的 Type 类型定义如下:
Type 
      TypeNoBounds
    | ImplTraitType
    | TraitObjectType

TypeNoBounds 
      ParenthesizedType
    | ImplTraitTypeOneBound
    | TraitObjectTypeOneBound
    | TypePath
    | TupleType
    | NeverType
    | RawPointerType
    | ReferenceType
    | ArrayType
    | SliceType
    | InferredType
    | QualifiedPathInType
    | BareFunctionType
    | MacroInvocation

QualifiedPathInExpression  QualifiedPathType ( :: PathExprSegment )+
QualifiedPathType  < Type ( as TypePath )? > // 完全限定路径类型
QualifiedPathInType  QualifiedPathType ( :: TypePathSegment )+

有些限界类型并不能直接在泛型参数列表 <xx> 中指定,但是却可以在 where 子句中指定,因为它的限界类型是 Type 而非标识符,例如:

  • where U: Trait4, U::Assoc: Trait5;
  • where ::Output: i32
  • where Self: Borrow<&U>
  • where Option: Debug

示例:

// where 子句的 U 和 U::Assoc 均为合法的 Type 类型
trait Trait4 {
    type Assoc;
}
trait Trait5 {}

fn f3<U>()
where
    U: Trait4,      // 泛型参数 U 必须实现 Trait4
    U::Assoc: Trait5, // IDENTIFIER = Assoc, TypeParamBounds = Trait5
{
}

// 其中的 TypeNoBounds 的 QualifiedPathInType 是允许在 TypePath 中使用的格式, 如函数返回值的 Type 类型:
fn add_all<T, U>(x: T, y: T) -> <T as Add<U>>::Output
      where  T: Add<U, Output = Option<i32>>,

HRTB
#

ForLifetimes 用于 HRTB(Higher-ranked trait bounds):

fn call_on_ref_zero<F>(f: F) where for<'a> F: Fn(&'a i32) {
    let zero = 0;
    f(&zero);
}
// 或者:
fn call_on_ref_zero<F>(f: F) where F: for<'a> Fn(&'a i32) {
    let zero = 0;
    f(&zero);
}

泛型参数总结
#

1 泛型类型参数单 trait:<T: Trait1> 2 泛型类型参数多 trait:<T: Trait2 + Trait3> 3 泛型类型参数 + GenericArgs:<T: Add> 4 关联类型 bound: <T: Trait4<Assoc: Trait5» 5 多层路径 + 关联类型 bound: <T: outer::OuterTrait<Inner: outer::InnerTrait» 6 关联类型带 GenericArgs: <T: outer2::OuterTrait<Inner: outer2::InnerTrait» 7 HRTB + 关联类型 bound: <F: for<‘a> Fn(&‘a str) -> &‘a str + FuncTrait<Output: Debug» 8 const 泛型 + 关联类型 bound: <const N: usize, T: ConstTrait<N, Arr: Debug» 9 整数常量泛型: 10 多个泛型参数组合: <U: Trait6, V: Trait6> + 关联类型约束

trait
#

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

  • 匿名函数也可以作为泛型参数的限界约束,例如函数类型 fn() -> u32 或闭包类型 Fn() -> u32
  • 除了对泛型参数 T 直接限界外,也可以使用 where 对使用 T 的其它类型进行限界,如:Option<T>: Debug;

做泛型限界的 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 时,支持泛型参数和关联类型,可以它们指定缺省类型和做限界,trait 的函数或方法使用 Self::Item 来引用关联类型 Item:

// 定义泛型 trait 时,泛型参数和关联类型都可以指定缺省类型,例如各种运算符重载 trait 都指定了默认类型。
pub trait Add<Rhs = Self> {
    // 关联类型也可以指定缺省类型,使用 type alias 语法。
    type Output = Self;

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

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

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

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

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

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

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

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

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

关联常量
#

trait 支持关联常量,在定义 trait 时必须指定类型,可选的指定缺省值。在实现 trait 时指定常量值:

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

trait Greet{
    // 定义关联常量的类型和缺省值
    const GRETING: &'static str = "Hello",

    fn greet(&self) -> String;
}

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

trait 限界传参时不会隐式转换
#

使用 trait bound 的场景:

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

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

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

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

最佳实践:

  1. 在为自定义类型实现 Trait 时,一般为三种类型 T, &T, &mut T 都实现同一个 trait。
  2. 或者,在 T 上实现 trait,这样内部可以使用 self&mut self;
trait Trait {}

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

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

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

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

函数传参匹配 trait 限界时也不会自动通过 Deref trait 解引用来满足泛型参数限界的要求:

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

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

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

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

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

两种解决办法:

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

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

子 trait 继承了父 trait 的关联类型、函数和方法:

// https://medium.com/software-design/understanding-rusts-trait-objects-224b1d8daede
pub trait Shape {
    type T;
    fn area(&self) -> Self::T;
}

pub trait Draw: Shape {
    fn draw(&self);
}

// The function operates with a trait object reference
pub fn draw_dynamic(a: &dyn Draw<T = f64>) {
    a.draw();
}

// Invokes the draw method and then the area method on the trait object
pub fn draw_and_report_area_dynamic(a: &dyn Draw<T = f64>) -> f64 {
    a.draw();
    a.area()
}

impl 定义方法或实现 trait
#

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

  1. 为自定义类型的定义泛型的方法或关联函数:impl<T> MyStruct<T> {}

  2. 为自定义类型类型实现泛型 trait: impl<T> MyTrait<T> for MyStruct<T> {}

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

// 泛型类型
struct GenericVal<T>(T);

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

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

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

在定义 trait 的关联函数或方法,或为自定义类型实现 trait 时,该 trait 或 type 必须是本 crate 定义的类型,这个限制称为 Orphan Rule ,这是为了避免破坏开发者原来的定义。

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

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

通过 blanket implement,可以对已知或未知的类型实现 trait:

// 泛型 trait
trait DoubleDrop<T> {
    fn double_drop(self, _: T);
}

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

// 为所有 &T 或 &mut T 实现 Deref<Target = T> trait:
// https://doc.rust-lang.org/src/core/ops/deref.rs.html#84
impl<T: ?Sized> Deref for &T {
    type Target = T;

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

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

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

crate 的文档专门有一节名为 Blanket Implementations 的内容,展示类型 blanket implement 实现的 trait。 例如:https://docs.rs/hashbrown/0.16.0/hashbrown/struct.HashMap.html#blanket-implementations

完全限定方法调用
#

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

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

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

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

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

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


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

完全限定语法(`Fully Qualified Syntax`) 的几种格式:
1. `<Human as Pilot>::fly(&person)`:调用对象 person 对应的类型 Human 实现的 Pilot trait  fly 方法。
2. `<person as Pilot>::fly()`: 调用 person 对象类型实现的 Pilot trait  fly 方法;

如果类型实现的多个 trait 有同名方法而且这些 trait 都在作用域,则需要使用完全限定语法来指定要调用那个 trait 的方法实现,用于解决命名冲突或混淆的问题:

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

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

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

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

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

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

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

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

    // 使用完全限定方法调用:调用 s1 对象对应的 S1 类型实现的 CalSum trait 的 sum 方法或关联函数。
    println!("Results: {}", <S1 as CalSum>::sum(&s1, 1)); // OK
}

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

let i: i8 = Default::default(); // default() 为 Default trait 的关联函数
let (x, y): (Option<String>, f64) = Default::default();
let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();

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

fn main() {
    // 如果 SomeOptions 没有实现除 Default 外的其它 trait 的 default() 函数或方法, 则 <SomeOptions as
    // Default>::default() 可以简写为 Default::default(),这在 struct 类型赋值表达式中很常用。
    let options: SomeOptions = Default::default();
    let options = SomeOptions { foo: 42, ..Default::default() };
}

trait object
#

trait object 使用 dyn TraitName 创建,可以保存实现 TraitName 的任意类型对象,所以它是在编译时大小不确定的(unsized)类型。

静态派发和动态派发:

  • 静态分发(static dispatch):通过泛型(impl TraitT: Trait)实现,在编译期间就能确定(或动态生成)具体类型和对应的实现函数,编译器会内联并生成专门化的代码,没有运行时开销。

  • 动态分发(dynamic dispatch):通过 trait object 实现,在编译期间不能明确具体类型,而是在运行期间通过查找 vtable 表的方式 来获得实现该 Trait 类型的方法指针,进而调用对应方法。

// https://eventhelix.com/rust/rust-to-assembly-static-vs-dynamic-dispatch/

// The Shape trait requires that the implementor have a method called `area`
// that returns the area as the associated type `Shape::T`.
pub trait Shape {
    type T;
    fn area(&self) -> Self::T;
}

// A generic Point for a specified type T.
pub struct Point<T> {
    x: T,
    y: T,
}

// A generic Rectangle for a specified type T.
pub struct Rectangle<T> {
    top_left: Point<T>,
    bottom_right: Point<T>,
}

// Implement the Shape trait for the Rectangle using a generic type T.
// The `T` is the return type of the `area` method. The where clause specifies
// that `T` must support subtraction, multiplication and the ability to copy.
impl<T> Shape for Rectangle<T>
where
    T: std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + Copy,
{
    type T = T;
    fn area(&self) -> T {
        let width = self.bottom_right.x - self.top_left.x;
        let height = self.top_left.y - self.bottom_right.y;

        width * height
    }
}

// A function that calculates the area of two shapes and returns a tuple containing the areas.
// This function requires that the two shapes implement the Shape trait. The function will call
// the area function via a static dispatch. Code will be generated for the function only if concrete
// types are specified for `a` and `b`.
pub fn area_pair_static(a: impl Shape<T = f64>, b: impl Shape<T = f64>) -> (f64, f64) {
    (a.area(), b.area())
}

pub fn static_dispatch_pair(a: Rectangle<f64>, b: Rectangle<f64>) -> (f64, f64) {
    // The following line will generate code for the function as concrete types are specified for `a` and `b`.
    area_pair_static(a, b)
}

// This function performs the same function as `area_pair_static` but uses a dynamic dispatch. The compiler
// will generate code for this function. The calls to `area` are made through the `vtable`.
pub fn area_pair_dynamic(a: &dyn Shape<T = f64>, b: &dyn Shape<T = f64>) -> (f64, f64) {
    (a.area(), b.area())
}

trait object 是动态分发类型,由于是 unsized 的,故一般使用固定大小(2 usize)的 &dyn TraitBox<dyn Trait> 胖指针类型 (fat pointer),包含两部分:

  1. 数据指针(data pointer):指向实现 Trait 的具体类型对象内存(如 String 对象的为 3 个 usized:data pointer、length、capacity)。
  2. vtable 指针(vtable pointer,virtual method table,虚方法表):指向该具体类型的 vtable 表。

Rust 每个静态类型都有自己的 vtable,包含如下内容:

  1. 该类型实现的各 trait 方法的函数指针,即实现方法的入口地址。
  2. 类型的元信息,如大小 size_of::<T>(),对齐 align_of::<T>(),析构函数 drop_in_place::<T> 等。

https://img.opsnull.com/blog/20250924135711654.png

https://img.opsnull.com/blog/20250921202837759.png

https://img.opsnull.com/blog/20250925173833737.png

vtable 示例如下:

struct VTable {
    fn_ptrs: [*const fn],   // 类型实现的 Trait 方法的函数指针
    drop_in_place: fn(*mut ()), // 类型对应的 Drop 析构函数
    size: usize,
    align: usize,
}

vtable 是 By (Trait, Type) 一个,也即每个静态类型实现的每个 Trait 实现都有一个对应的 vtable。

// https://zhuanlan.zhihu.com/p/407265957
use std::fmt::{Debug, Display};
use std::mem::transmute;


fn main() {
    let s1 = String::from("hello world!");
    let s2 = String::from("goodbye world!");
    // Display / Debug trait object for s
    let w1: &dyn Display = &s1;
    let w2: &dyn Debug = &s1;


    // Display / Debug trait object for s1
    let w3: &dyn Display = &s2;
    let w4: &dyn Debug = &s2;


    // 强行把 triat object 转换成两个地址 (usize, usize)
    // 这是不安全的,所以是 unsafe
    let (addr1, vtable1) = unsafe { transmute::<_, (usize, usize)>(w1 as *const dyn Display) };
    let (addr2, vtable2) = unsafe { transmute::<_, (usize, usize)>(w2 as *const dyn Debug) };
    let (addr3, vtable3) = unsafe { transmute::<_, (usize, usize)>(w3 as *const dyn Display) };
    let (addr4, vtable4) = unsafe { transmute::<_, (usize, usize)>(w4 as *const dyn Debug) };


    // s 和 s1 在栈上的地址,以及 main 在 TEXT 段的地址
    println!(
        "s1: {:p}, s2: {:p}, main(): {:p}",
        &s1, &s2, main as *const ()
    );
    // trait object(s / Display) 的 ptr 地址和 vtable 地址
    println!("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1);
    // trait object(s / Debug) 的 ptr 地址和 vtable 地址
    println!("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2);


    // trait object(s1 / Display) 的 ptr 地址和 vtable 地址
    println!("addr3: 0x{:x}, vtable3: 0x{:x}", addr3, vtable3);


    // trait object(s1 / Display) 的 ptr 地址和 vtable 地址
    println!("addr4: 0x{:x}, vtable4: 0x{:x}", addr4, vtable4);


    // 指向同一个数据的 trait object 其 ptr 地址相同
    assert_eq!(addr1, addr2);
    assert_eq!(addr3, addr4);


    // 指向同一种类型的同一个 trait 的 vtable 地址相同
    // 这里都是 String + Display
    assert_eq!(vtable1, vtable3);
    // 这里都是 String + Debug
    assert_eq!(vtable2, vtable4);
}

vtable 是按需生成的:编译器在遇到 trait object 时,根据赋值的对象类型,生成一个 (Trait, Type) 组合的 vtable,修改 trait object 的 data 指针指向赋值对象内存,修改 vtable 指针指向创建的 vtable。

trait object 的函数调用执行过程:

  1. a 是一个胖指针,包含:1. data_ptr → 指向 Dog 实例; 2. vtable_ptr → 指向 Dog 类型的 vtable。
  2. 调用 a.speak() 时:1. 编译器生成的代码会去 vtable 查找 speak 方法的函数指针;2. 接调用该函数指针,最终执行 Dog::speak()。
trait Animal {
    fn speak(&self);
}

struct Dog;
impl Animal for Dog {
    fn speak(&self) { println!("woof"); }
}

fn main() {
    let a: &dyn Animal = &Dog;
    a.speak();
}

关键特性:

  • vtable 在编译期生成,但在运行时查表调用。
  • 多个 dyn Trait 的实例共享同一个实现的 vtable(节省内存)。
  • 因为通过函数指针调用,无法内联,性能略低于静态分发。
  • Drop 也是通过 vtable 里的析构函数指针调用的,因此 Box<dyn Trait> 在释放时能正确调用底层类型的析构逻辑。

trait object 和泛型参数的差别:

  • trait object 是运行时动态分发: 不和某一类型绑定,如 Vec<&dyn Display> 可以保存各种实现 Display trait 的类 型对象,在运行时根据赋值的对象类型,通过 vtable 查找它实现的 Trait 的方法指针。

  • 泛型参数是编译期静态分发: 编译器推断并绑定一具体类型,如 Vec<T: Display> , 在编译阶段即被编译器实例化为一种实现 Display trait 具体类型;

定义 trait object: dyn BaseTrait + 一个或多个 AutoTrait + 至多一个 lifetime。

用于限界的 AutoTrait 包括:Send, Sync, Unpin, UnwindSafe, RefUnwindSafe,但不包含 ?Sized,Hash,Eq 等 trait。

对比:用于范型参数界限的 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
dyn eviction::EvictionManager + Sync

trait object 的第一个 BaseTrait 必须是对象安全(object-safe/dyn-compatiable)的,需要满足如下要求(具体参考下一节):

  • Trait 不能要求 Self: Sized;
  • 不能包含关联常量;
  • 不能包含带泛型的关联类型(GAT),但允许普通关联类型;

另外,即使满足上面要求,可以创建 trait object,但是使用 where Self: Sized 限制的函数或方法不支持动态派发。

trait object 在编译时类型和大小不确定, 是运行时动态派发类型,两种定义方式:

  • &dyn TraitName: 使用实现 TraitName 的对象引用赋值:let n: &dyn TraitName = &value

  • Box<dyn TraitName>: 使用 Box::new(value) 赋值:let b: Box<dyn TraitName> = Box::new(value) ,但不能直接将 value 赋值给 Box<dyn TraintName>

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

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

// error: expected expression, found keyword `dyn`
let b = &'a dyn MyTrait + Send + 'static;

// 表达式不能指定 'lifetime, 'lifetime 只能用于函数或方法的签名中。
// error: borrow expressions cannot be annotated with lifetimes
// let b = &'a (dyn MyTrait + Send + 'static);

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

参考:

  1. https://quinedot.github.io/rust-learning/dyn-trait.html
  2. https://doc.rust-lang.org/reference/types/trait-object.html

object safe 和 dyn compatible
#

参考:https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility

Rust 1.84.0 开始将 object safe 重命名为 Dyn Compatibility

dyn compatible 指的是编译时可以为其创建 vtable 的 trait 类型。

dyn compatibility(动态兼容性)规则是 Rust 对 trait 检查是否支持创建 trait object,也即该 trait 的方法是否可以被动态 分发(即通过 trait object dyn Trait 来进行调用)。

一个 dyn compatible 的 trait 必须同时满足下列条件

  • 它的 super trait 也必须 dyn compatible: 如 trait Sub: Super {},则要求 Super 也 dyn compatible;

  • 不能要求 Self: Sized,如不能指定 trait T: Sized {},因为 dyn Tratit 是大小未定的;

  • 不能包含任意关联常量(associated consts),如不能有 const C: ...;

  • 不能包含带泛型的关联类型(GATs),如不能有 type Iter<T>;, 但允许非范型的普通关联类型;

  • 所有的关联方法必须可以在 trait object 上安全的动态分发,或显式地指定它不能动态分发(使用 where Self: Sized)。

    可以安全的动态分发的函数需要满足:

    • 无范型类型参数(但可以有生命周期参数)。

    • 方法体中除接收者外不使用 Self(即除了 self 的接收者类型位置,不能在参数/返回值/泛型位置出现 Self)。

    • 接收者必须是以下之一:&Self、&mut Self、Box<Self>、Rc<Self>、Arc<Self>、或上述之一的 Pin<P> 包装(如 Pin<&Self>、Pin<Arc<Self>>)。

    • 返回类型不能是 opaque type,如不能是 async fn(因其等效为返回 impl Future),也不能是 impl Trait 返回。

    • 不能携带 where Self: Sized 之类的限制(对 self 值接收者的约束会隐含该限制)。

如果在方法或函数上限定 where Self: Sized,则表明该方法不能在 trait object 上动态分发调用,而只能在具体类型上调用。(但是不影响该 trait 的 dyn compatible)。

AsyncFn/AsyncFnMut/AsyncFnOnce 这三个 trait 自身不是 dyn compatible。

示例:

use std::{pin::Pin, rc::Rc, sync::Arc};

trait Good {
    // 允许的接收者
    fn by_ref(&self) {}
    fn by_ref_mut(&mut self) {}
    fn by_box(self: Box<Self>) {}
    fn by_rc(self: Rc<Self>) {}
    fn by_arc(self: Arc<Self>) {}
    fn by_pin(self: Pin<&Self>) {}
    fn with_lt<'a>(self: &'a Self) {}

    // 只能在具体类型上调用(不可经 dyn 调用)
    fn helper() where Self: Sized {}
}

struct S;
impl Good for S {}

fn main() {
  let _obj: Box<dyn Good> = Box::new(S);
}

不满足 dyn compatiable 的示例:

use std::{rc::Rc, pin::Pin};

// 1) 关联常量 → 不可 dyn
trait HasConst {
    const C: i32; // ❌
}

// 2) 要求 Self: Sized → 不可 dyn
trait NeedsSized: Sized {} // ❌

// 3) 使用 GAT(带泛型的关联类型) → 不可 dyn。但是允许非泛型的关联类型。
trait WithGat {
    type Iter<T>; // ❌
}

// 4) 不可分发的方法:Self 作返回值/参数 或 泛型方法。该 trait 是 dyn compatiable 的,也即可以 dyn BadMethods,但是部分方法不能动态分发。
trait BadMethods {
    fn returns_self(&self) -> Self;           // ❌ 返回 Self
    fn param_self(&self, other: Self) where Self: Sized; // ❌ 参数含 Self
    fn generic<T>(&self, x: T) where Self: Sized;        // ❌ 泛型方法
    fn nested_receiver(self: Rc<Box<Self>>);  // ❌ 嵌套接收者
    async fn bad_async(&self) {}              // ❌ async 返回 opaque
    fn bad_impl_trait(&self) -> impl Copy { 0 } // ❌ impl Trait 返回
    fn bad_where_sized(&self) where Self: Sized; // ❌ 要求 Sized
}

struct S;
impl BadMethods for S {
    fn returns_self(&self) -> Self { S }
    fn param_self(&self, _other: Self) {}
    fn generic<T>(&self, _x: T) {}
    fn nested_receiver(self: Rc<Box<Self>>) {}
    fn bad_where_sized(&self) {}
}

dyn Trait 的调用机制是 动态分发(vtable 调用),要求所有的方法在类型擦除后(只剩下 dyn Trait)依然能被正确调用。

方法返回 Self、&Self(也即除了 receiver 外,其它函数参数和返回值不能使用 Self)时不可动态分发的原因:

  • 在 dyn Trait 的上下文中,Self 的具体类型已被擦除,在编译时只知道 vtalb 而不能确定 Self 的具体类型。
trait CloneLike {
    fn dup(&self) -> Self;
}
// 对 impl CloneLike for String,dup() 返回 String 没问题。

// 但如果我们有:
fn foo(x: &dyn CloneLike) {
    let y = x.dup(); // fn dup(&self) -> Self 不满足 dyn compatibility,编译错误。
}
// 对于 dyn CloneLike ,在编译时只知道 vtable,而不知道具体是那个类型,所以编译器不能确定 y 的类型。

Box
#

Box<dyn Trait> 是一个智能指针,占两个 usize 空间,它拥有 dyn Trait 对象所有权,并在堆上分配实现该 Trait 对象,在 Drop 时会释放堆上的数据。

Box<T> 的大小取决于 T 的类型,对于 dyn Trait 类型,则是占用 2 个 usize 的胖指针类型:

  • 数据指针:指向堆上分配的对象(比如一个 i32)。
  • vtable 指针:同样指向该具体类型的虚函数表。

特点:

  • 负责释放底层对象。
  • 适合需要返回或长期持有 trait object 的场景。
  • 没有生命周期问题,因为它拥有数据,直到 Box 被 drop。

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

  • Box<dyn TraitName + 'static>Box<dyn TraitName> 的等效形式
  • Box<dyn TraitName + Send + 'static>
  • Box<dyn TraitName + Sync + Send + 'static>

Trait 默认没有实现 Send、Sync trait,但可以在定义 Trait 时将它们指定为 super trait 来告诉编译器实现它们。

dyn TraitName 是否实现 Send、Sync trait 取决于:

  1. TraitName 是否实现 Send、Sync trait;
  2. 或者,本身有没有加 Send’、Sync 限界;

在多线程场景,需要 dyn TraitName 实现 Send+'static。在异步场景,需要跨 await 的 dyn TraitName 实现 Sync/Send/'static,所以通常使用上面三种类型的 Box 来保存 trait object。

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

struct Sheep {}

struct Cow {}

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

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

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

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

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

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

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

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

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

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

trait object 实现 Send/Sync/Unpin
#

对于 trait object,如 &dync MyTraitBox<dyn MyTrait>,因为是运行时动态派发类型,在编译期间不能确定具体类型,所以编译器默认没有为它们实现 Send/Sync/Unpin,而需要显式的来指定:

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

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

错误的情况:

use std::thread;

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

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

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

解决办法:

use std::thread;

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

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

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

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

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

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

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

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

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

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

注意:&dyn MyTrait + Send 不符合语法,编译报错,需要使用 &(dyn MyTrait + Send) 格式:

trait MyTrait {}

impl MyTrait for i32 {}

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

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

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

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

trait object 的缺省 lifetime 推导规则
#

参考: lifetime-elision.html

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

dyn Trait 匿名类型可能包含引用(例如 impl Trait for &str),编译器需要推断出这些引用在 dyn Trait 中的生命周期约 束,这套推断规则就叫 默认对象生命周期绑定(DOLB,default object lifetime bound.)。

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

当你写下 &dyn TraitBox<dyn Trait> 时,如果没有显式指定生命周期参数(例如 &(dyn Trait+ 'a)),编译器需要一个规则来确定这个 trait object 内 部可能 包含的引用(例如上面的 &str)的生命周期。

普通函数参数/返回值的生命周期省略规则(比如 fn foo(x: &i32) -> &i32 等价于 fn foo<'a>(x: &'a i32) -> &'a i32)和这里的推断规则不同。换句话说,trait object 有自己的一套规则。

  1. dyn Trait + '_:指示编译器用普通省略规则(即 lifetime-elision),而不是用 DOLB。

    fn foo<'a>(x: &'a str) -> &'a (dyn ToString + '_) {
        x
    }
    
    // 等价于
    fn foo<'a>(x: &'a str) -> &'a dyn ToString {
        x
    }
    
  2. 根据外层容器(containing type)类型推断: 当 dyn Trait 出现在某个泛型容器里(例如 Box,&dyn Trait,Rc,fn 等),优先看容器的生命周期。

    • &'a dyn Trait : ‘a 来自 &, 即为:&'a(dyn Trait+'a)

    2.1 唯一推断: 如果容器只有一个生命周期,那就直接用它。

      // 这里容器是 &'a,所以 dyn ToString → 'a。
      fn foo<'a>(x: &'a str) -> &'a dyn ToString { x } // 等价于返回 &'a(dyn ToString+'a)
    
      // 下面两个类型等效,因为 &'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,
      }
    

    2.2 多个推断: 如果容器有多个生命周期,则编译器无法推断, 必须明确指定:

    // 错误
    fn foo<'a, 'b>(x: &'a mut &'b dyn Trait)
    // 正确
    fn foo<'a, 'b>(x: &'a mut &'b(dyn Trait+'a))
    
    // 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>;
    
  3. 根据 trait 自身定义的生命周期参数: 如果容器没法提供,编译器会看 trait 定义。

    3.1 唯一生命周期参数:

      trait MyTrait<'a> {}
    
      dyn MyTrait // 等价为 dyn MyTrait + 'a
    

    3.2 ‘static 优先: 如果 trait 里写了 ‘static,那就直接是 ‘static。

    3.3 没有生命周期: 如果 trait 完全没有生命周期参数:

    • 在表达式里,可以根据上下文推断(如 let x: &dyn Trait = &42; → &‘a dyn Trait,‘a 来自 &)。
    // 表示实现 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 {}
    
    • 在类型声明中(比如 struct Foo { f: Box }),没有上下文可推断 : 默认 ‘static。
    
    // T1/T2 都是没有上下文可以推断,默认为 'static
    trait Foo { }
    
    // 下面两个类型等效
    type T1 = Box<dyn Foo>;
    type T2 = Box<dyn Foo + 'static>;
    
    // 这两个 impl 也是等效(dyn 是可选的)
    impl dyn Foo {}
    impl dyn Foo + 'static {}
    

总结(简化版)

  1. &dyn Trait → 生命周期来自 &。
  2. Box → 没有显式生命周期,默认 ‘static。
  3. dyn Trait + ‘_ → 强制走普通省略规则。
  4. 如果 trait 自身有生命周期参数,则依赖那个规则。

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

impl Trait
#

可以在函数的输入参数或返回值位置使用 impl Trait 类型,它是编译器在编译阶段自动生成实现 Trait 的匿名类型(opaque type),属于静态分发类型。

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

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

use std::iter;
use std::vec::IntoIter;

// impl Trait 作为函数返回值时,是由编译器在编译时创建的实现 Trait 的抽象抽象类型(opaque type)
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 作为函数参数, 常简写为 ITIAP(impl Trait in argument position),作用和范型参数类似,如支持限界(如 +'static),调用方 caller 传入的对象必须满足限界的要求。

fn map<U>(self, f: impl FnOnce(T) -> U + Send) -> Option<U>
// 等效为:
fn map<U, F>(self, f: F) -> Option<U> where F: FnOnce(T) -> U + Send

impl Trait 不是 trait,所以和范型参数不同,在调用时和返回时都不能明确指定它的类型:

fn foo<T: Trait>(t: T)
fn bar(t: impl Trait)

foo::<u32>(0) // this is allowed
bar::<u32>(0) // this is not

除了这个差别,可以认为 impl Trait 作为函数参数时,和使用泛型参数是等价的。

impl Trait 作为函数参数时,常添加 'static 限界:

fn print_it1(input: impl Debug + 'static ) {
    println!( "'static value passed in is: {:?}", input );
}

// 对应的泛型参数版本,这个版本的优势是实例化时可以指定 T 的类型,比如 print_it::<usize>(), 但是 impl Debug 是匿名类型,不能指定类
// 型: print_it1(1usize)。
fn print_it<T: Debug + 'static>(input: T) {
    println!( "'static value passed in is: {:?}", input );
}

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 作为函数返回值时也称为 RPIT 或 ITIRP(impl Trait in return position),表示返回的类型为实现这个 Trait 的匿名类型(抽象类型,也称为 opaque type),对应的具体类型(concrete type)是函数实现者定义的,调用方不能对该类型有任何假设(所以对调用方来说是 abstrace return typeopaque type)。

fn foo(n: u32) -> impl Iterator<Item = u32> {
    (0..n).map(|x| x * 100)
}

fn main() {
    for x in foo(10) {
        println!("{}", x);
    }
}

trait T {
    fn make_iter(&self) -> impl Iterator<Item = i32>;
}

和函数输入参数的 impl Trait 类似,函数返回值的 impl Trait 类型,也不是范型类型,所以调用方(caller)不能为它指定实例化类型:

// allows the caller to determine the return type, T, and the function returns that type.
fn foo<T: Trait>() -> T {
    // ...
}

// doesn’t allow the caller to determine the return type. Instead, the function
// chooses the return type, but only promises that it will implement Trait.
fn foo() -> impl Trait {
    // ...
}

在没有 impl Trait 之前,函数返回闭包对象(如 Fn/FnOnce/FnMut trait),需要使用 trait object ,如 Box<dyn Trait>,这会带来 heap 开销和运行时派发开销:

fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
    Box::new(|x| x + 1)
}

有了 impl Trait 后,可以返回 impl Fn,而它是由编译器在编译时生成的匿名抽象类型(opaque type,静态派发),避免了 Box heap 分配和 trait object 运行时派发开销:

fn returns_closure() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

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 // 栈分配,性能好
}

由于 impl Trait 是编译时静态生成的、实现 Trait 的匿名对象类型,在编译时只能被单态化为一种匿名类型,所以函数的所有返回路径必须返回完全相同的类型:

// 编译错误,即使这两个类型都实现了 Bar
fn f(a: bool) -> impl Bar {
    if a {
        Foo { ... }
    } else {
        Baz { ... }
    }
}

impl Trait 作为 trait 函数返回值时的泛型关联类型(GAT)
#

GAT:Generic Associated Types

Rust 1.75 版本开始支持在 Trait 里使用 impl Trait 作为函数返回值,并支持在 trait 中定义 async fn

参考:

  1. https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits/
  2. https://doc.rust-lang.org/reference/types/impl-trait.html#return-position-impl-trait-in-traits-and-trait-implementations

对于自由(普通)函数返回的 impl Trait,编译器会为每个函数实例化一个唯一的匿名类型(opaque type)且后续不会再变化:

// 编译时,编译器生成一个实现 Iterator<Item =u8> 的匿名类型
fn foo() -> impl Iterator<Item = u8> { ... }

但是,在 trait 方法返回值位置使用 impl Trait 时,编译器并不是直接像普通函数那样在编译器期间生成一个匿名类型,而是会反 desugar 成一个 trait 的 匿名关联类型(anonymous associated type, AAT)。

trait MyTrait {
    // 这里的 `impl Iterator<Item = u8>` 不是一个真正的“匿名类型”,而是语法糖,编译器会把它 desugar 成一个隐藏的匿名关联类型。
    fn foo(&self) -> impl Iterator<Item = u8>;
}

这是因为后面各种具体类型实现该 Trait 时该函数的返回值可能是不同的类型,所以为了确保不同实现间保持一致的类型定义方式,因此用匿名关联类型来表示:

  • 作为对比,对于普通函数返回 impl Trait 时,编译器立即生成一个匿名类型,而且后续不能再改变(静态分发)。
trait MyTrait {
    // 编译器内部生成的匿名关联类型(用户不可见)
    type foo$Ret: Iterator<Item = u8>;

    fn foo(&self) -> Self::foo$Ret; //等价于返回这个匿名关联类型。
}

而这个匿名关联类型,是在实现这个 trait 时根据返回的具体类型来确定的:

impl MyTrait for MyType {
    fn foo(&self) -> std::vec::IntoIter<u8> {
        vec![1, 2, 3].into_iter()
    }
}

编译器会把它当成:

impl MyTrait for MyType {
    type foo$Ret = std::vec::IntoIter<u8>;

    fn foo(&self) -> Self::foo$Ret {
        vec![1, 2, 3].into_iter()
    }
}

总结:

  1. 普通函数返回 impl Trait 时,编译器在编译阶段为其生成一个具体的匿名类型,而且后续不再变化;

  2. trait 中函数返回 impl Trait 时,编译器通过为 trait 生成一个关联类型来保存生成的匿名类型,而且编译器为不同实现该 Trait 的类型实现 的匿名类型还不一致。

由于 trait 中的 async fn 函数是返回 impl Future 的语法糖,这会导致两个常见的问题:

  1. 无法对 async fn 的返回值进行限界,编译器不能在编译期间推导它是否实现 Send/Unpin(所以默认仍未没实现 Send/Unpin),也不能进行限界;
  2. 包含 async fn(或返回 impl Trait 的函数)的 trait 本身不再是 object-safe/dyn-compatiable 的,不支持 trait object

trait 函数返回 impl Trait 时,不再是 object-safe/dyn-compatiable 的原因分析:

trait T {
    fn make_iter(&self) -> impl Iterator<Item = i32>;
}

这里函数返回值位置的 impl Trait(RPIT),语义是:编译器为每个 impl T for X 单独生成一个匿名的具体类型,并且在调用点返回它。

impl T for Foo {
    fn make_iter(&self) -> impl Iterator<Item = i32> {
        vec![1, 2, 3].into_iter()
    }
}

编译器会把它变成一个匿名的具体类型 FooMakeIter,但是这个类型是每个 for XX 的类型实现独有的:

  • Foo::make_iter 可能返回 impl Iterator 的某个匿名类型。
  • Bar::make_iter 可能返回另一个完全不同的匿名类型。

在 dyn T 场景下调用时:dyn T 根本没法统一表示这个返回值,因为不同实现可能返回不同的匿名类型,而 dyn T 调用需要一个确定的返 回类型。 所以,trait 函数返回 impl Trait 时不支持通过 trait object 动态派发(但是还是支持静态派发的)。

fn f(x: &dyn T) {
  let it = x.make_iter(); // <-- 问题
}

```rust
trait MyTrait {
    fn iter(&self) -> impl Iterator<Item = u8>;
}

fn use_obj(_: &dyn MyTrait) {} // ❌ 编译失败

/*
error[E0038]: the trait `MyTrait` cannot be made into an object
  --> src/main.rs:5:17
   |
5  | fn use_obj(_: &dyn MyTrait) {}
   |                 ^^^^^^^^^ `MyTrait` cannot be made into an object
   |
   = note: method `iter` references the `impl Iterator<Item = u8>` type, which is not object safe
*/

对 trait 的 async fn 返回值进行限界:实验特性 RTN
#

RTN 是用来解决 trait 中的 async fn 不能对返回的 impl Trait 对象不能进一步限界,从而不能满足一些场景要求的局限。

  • 参考:https://smallcultfollowing.com/babysteps/blog/2023/02/13/return-type-notation-send-bounds-part-2/
// 类型别名
trait HttpService = LocalHttpService<fetch(): Send> + Send;
// trait 中的 async fn 返回值是一个匿名的关联类型
trait HealthCheck {
    // async fn check(&mut self, server: Server);
    type Check<t>: Future<Output = ()> + t;
    fn check<s>(&s mut self, server: Server) -> Self::Check<s>;
}

// RTN 实际是对该匿名关联类型的约束
fn start_health_check<H>(health_check: H, server: Server)
where
    H: HealthCheck + Send + 'static,
    for<a> H::Check<a>: Send, // <— equivalent to `H::check(..): Send`

函数返回 impl Trait 时的泛型参数捕获
#

当函数返回 impl Trait 时,编译器会在背后生成一个匿名的抽象类型,用来代表这个返回值的具体类型。

如果这个具体类型里用到了函数的泛型参数(类型、常量或生命周期),这些泛型参数必须被这个匿名类型捕获,否则无法正确表达依赖关系。

// impl Sized 可能依赖 'a 和 T,所以它捕获了 'a
fn foo<'a, T>(x: &'a T) -> impl Sized {
    (x,)
}

这里返回的元组类型 (&'a T,) 依赖 ‘a 和 T,所以 ‘a 和 T 都要被“捕获”到这个 impl Sized 的匿名类型里。

所以,impl Trait 作为函数返回值时,和 dyn Trait 一样,可能具有捕获的范型参数和 lifetime。

默认自动捕获(Automatic capturing)
#

2024 版本之前,由于不是默认全部捕获,在 trait 函数返回 impl Trait 时, 一般需要通过 + '_ 来让编译器推导和捕获所需泛型参数和 lifetime。

2024 Edition 开始,函数返回位置的 impl Trait 会自动捕获 作用域内的所有泛型参数,所以不再需要加 + '_ 了:

  • 类型参数(T)
  • 常量参数(const N: usize)
  • 生命周期参数(包括 HRTB)
async fn foo(x: &mut i32) -> i32
// 等价于:注意返回的匿名对象的 lifetime 和 x 一致
fn foo<'a>(x: &'a mut i32) -> impl Future<Output = i32> + 'a
// 更精确的,返回值的类型是:impl Future<Output = i32> + '_
// 这里的 '_ 表示 编译器会为其捕获函数的范型参数和 lifetime,具体参考前面的 impl Trait 一节。

// 另一个例子:
trait HealthCheck {
    async fn check(&mut self, server: Server);

    // 等价于:
    // 返回的 impl Future 自动捕获了 's lifetime。
    // fn check<‘s>(&’s mut self, server: Server) -> impl Future<Output = ()> + ‘s;
    //           ^^^^^^^^^^^^                                                ^^
    //         The future captures `self`, so it requires the lifetime bound `'s`
}

由于 check 返回值捕获了 &self 的 lifetime ‘s,所以它不可能是 ‘static。

use<…> 精确捕获
#

Rust 2025.05.15 发布的 1.87.0 版本开始支持 use<...> 作为 impl Trait 的约束,使用它可以精确指定要捕获的类型(如不想捕获所有范型参数和lifetime):

// 只捕获 'a 和 T, 不捕获 'b
fn capture<'a, 'b, T>(x: &'a (), y: T) -> impl Sized + use<'a, T> {
    (x, y)
}

use<...> 限制条件:

  1. 仅允许一个 use<..>,且必须包含 返回值对象实际捕获的所有泛型类型参数、生命周期参数
  • 所有作用域内的类型泛型和常量泛型参数
  • 所有出现在其他 bounds 中的生命周期参数
  1. 生命周期顺序
  • 在 use<..> 中,生命周期参数必须在所有类型和常量泛型参数之前。
  • 可以使用省略生命周期 ‘_’(如果在返回位置允许)。
  1. 不能用于参数位置的 impl Trait,因为参数位置的 impl Trait 会引入匿名类型参数,无法在 use<..> 中按名称引用。
  2. Trait 方法中必须包含 Self,在 trait 定义的关联函数中,use<..> 必须包含 trait 的所有泛型参数,包括隐式的 Self。

总结:

  1. 自动捕获:适合大多数情况,尤其是 2024 edition 之后,省心无痛。
  2. 精确捕获:用于控制 API 暴露的泛型依赖,确保 impl Trait 只绑定特定的泛型参数。
  3. 作用:决定匿名返回类型依赖哪些泛型参数,影响类型推导、trait 实现、object-safety/dyn-compatibility 等。

常见 Trait 解析
#

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 可以是无固定大小(unsized)的类型:

  1. str
  2. [T];
  3. dyn MyTrait, 包括函数类型 dyn Fn(&str) -> T;

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

  1. &str, Box<str>;
  2. &[T], Box<[T]>;
  3. &dyn MyTrait, Box<dyn Myrait>, &dyn Fn(&str) -> T

PhantomData
#

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

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

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

  1. lifetime:

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

       use std::marker::PhantomData;
    
       // 第二个字段是隐藏的
       #[derive(PartialEq)]
       struct PhantomTuple<A, B>(A, PhantomData<B>);
    
       // 第二个字段是隐藏的
       #[derive(PartialEq)]
       struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }
    
       fn main() {
           // PhantomData 只有 PhantomData 一个类型值。
           let _tuple1: PhantomTuple<char, f32> = PhantomTuple('Q', PhantomData);
           let _tuple2: PhantomTuple<char, f64> = PhantomTuple('Q', PhantomData);
    
           let _struct1: PhantomStruct<char, f32> = PhantomStruct { first: 'Q', phantom: PhantomData, };
           let _struct2: PhantomStruct<char, f64> = PhantomStruct { first: 'Q', phantom: PhantomData, };
    
           // Compile-time Error! Type mismatch so these cannot be compared:
           // println!("_tuple1 == _tuple2 yields: {}",
           //           _tuple1 == _tuple2);
    
           // Compile-time Error! Type mismatch so these cannot be compared:
           // println!("_struct1 == _struct2 yields: {}",
           //           _struct1 == _struct2);
       }
    
  3. 编译时类型检查:

       use std::ops::Add;
       use std::marker::PhantomData;
    
       /// Create void enumerations to define unit types.
       #[derive(Debug, Clone, Copy)]
       enum Inch {}
    
       #[derive(Debug, Clone, Copy)]
       enum Mm {}
    
       #[derive(Debug, Clone, Copy)]
       struct Length<Unit>(f64, PhantomData<Unit>);  // 使用泛型参数 Unit
    
       impl<Unit> Add for Length<Unit> {
           type Output = Length<Unit>;
    
           fn add(self, rhs: Length<Unit>) -> Length<Unit> {
               Length(self.0 + rhs.0, PhantomData)
           }
       }
    
       fn main() {
           let one_foot:  Length<Inch> = Length(12.0, PhantomData);
           let one_meter: Length<Mm>   = Length(1000.0, PhantomData);
    
           let two_feet = one_foot + one_foot;
           let two_meters = one_meter + one_meter;
    
           println!("one foot + one_foot = {:?} in", two_feet.0);
           println!("one meter + one_meter = {:?} mm", two_meters.0);
    
           // Nonsensical operations fail as they should:
           // Compile-time Error: type mismatch.
    
           //let one_feter = one_foot + one_meter;
       }
    

Copy/Clone
#

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

pub trait Copy: Clone { }

Copy 和 Clone 的差别:

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

默认实现 Copy 的情况:

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

没有实现 Copy 的情况:

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

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

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

// 错误: Vec<T> 没有实现 Copy
#[derive(Copy, Clone)]
struct PointList {
    points: Vec<Point>,
}

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

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

impl Copy for MyStruct { }

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

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

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

Default
#

按惯例, 自定义类型使用 new() 关联函数来作为类型的构造函数。

Rust 并没有强制所有类型都实现该方法, 但它提供了 Default trait, Rust 基 本类型和常用类型都实现了它。

pub struct Second {
    value: u64,
}

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

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

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

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

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

impl MyConfiguration {
}

fn main() {
    // 调用 Default trait 的 default() 关联函数来创建对象.
    let mut conf = MyConfiguration::default();
    conf.check = true;
    println!("conf = {conf:#?}");

    // struct 对象部分输出化:没有指定的 field 都使用 default() 方法的值来填充:
    let conf1 = MyConfiguration {
        check: true,
        ..Default::default() // 返回一个缺省的 MyConfiguration 类型对象,然后用它来填充其它字段
    };
    assert_eq!(conf, conf1);
}

// 另一个例子
#[derive(Default)]
struct SomeOptions {
    foo: i32,
    bar: f32,
}
fn main() {
    let options: SomeOptions = Default::default();
    // 自动根据目标类型调用对应实现的方法, 等效于 <SomeOptions as Default>::default()
}

对于 enum 类型, 需要明确定义哪一个 enum variant 是 default:

#[derive(Default)]
enum Kind {
    #[default]
    A,
    B,
    C,
}

// 或者
enum Kind {
    A,
    B,
    C,
}

impl Default for Kind {
    fn default() -> Self { Kind::A }
}

Default trait 作为泛型类型的定界, 在标准库中得到广泛使用, 例如:

  1. Option::unwrap_or_default() 方法;
  2. std::mem::take(&mut T) 将 T 的值 move 出来,然后将原始内存值用 Default 填充;
let x: Option<u32> = None;
let y: Option<u32> = Some(12);

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

Drop
#

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

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

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

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

    struct Foo;

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

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

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

From/Into
#

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

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

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

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

use std::convert::From;

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

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

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

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

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

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

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

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

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

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

    impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a> {
        fn from(err: E) -> Box<dyn Error + Send + Sync + 'a> {
            Box::new(err)
        }
    }
    
    type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>;
    type GenericResult<T> = Result<T, GenericError>;
    fn parse_i32_bytes(b: &[u8]) -> GenericResult<i32> {
        Ok(std::str::from_utf8(b)?.parse::<i32>()?)
    }
    
  3. collect 方法将 Iterator<Item=T> 转换为其他容器类型:

       let vec: Vec<FromType> = get_some_from_types();
       // 自动调用 From<FromType> for IntoType 的实现
       let result: Result<Vec<IntoType>, _> = vec.into_iter().collect();
    
  4. std::convert::from 函数会显式调用 From trait:

       let from_type = FromType::new();
       // 显式调用 From<FromType> for IntoType 的实现
       let into_type:IntoType = std::convert::From::from(from_type);
    
  5. 变量赋值和函数返回值: 如 &str 实现了 Into Box<dyn std::error::Error> 的 trait, 则可以直接调用 &str.into() 方法。

    如果 impl From<T> for U, 则可以 let u: U = U::from(T)let u: U = T.into()

       fn main() {
           if let Err(e) = run_app() {
               eprintln!("Application error: {}", e);
               std::process::exit(1);
           }
       }
       fn run_app() -> Result<(), Box<dyn std::error::Error>> {
           return Err("main will return 1".into());
       }
    
       use std::net::Ipv4Addr;
       // A 是任意能转换为 Ipv4Addr 的类型
       fn ping<A>(address: A) -> std::io::Result<bool> where A: Into<Ipv4Addr>
       {
           let ipv4_address = address.into();
           // ...
       }
    

FromStr/ToString
#

FromStr trait 从字符串来生成类型值。Rust 基本类型,如整数、浮点数、bool、char、String、PathBuf、IpAddr、 SocketAddr、Ipv4Addr、Ipv6Addr 都实现了该 trait。

pub trait FromStr: Sized {
    type Err;

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

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

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

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

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

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

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

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

use std::fmt;

struct Circle {
    radius: i32
}

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

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

FromIterator/IntoIterator
#

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

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

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

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

IntoIterator 是类型支持 for-in 迭代的要求,即类型要实现 IntoIterator 才能被 for-in 迭代

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

编译为任意实现了 Iterator trait 的类型自动实现 IntoIterator trait,故可以直接 for-in 实现 Iterator 的对象:

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

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

Extend
#

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

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

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

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

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

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

AsRef/AsMut
#

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

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

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

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

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

PartialEq/PartialOrd/Eq/Ord
#

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

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

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

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

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

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

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

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

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

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

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

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

也可以手动实现这些 trait:

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

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

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

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

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

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

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

use std::ops;

struct Foo;

struct Bar;

#[derive(Debug)]
struct FooBar;

#[derive(Debug)]
struct BarFoo;

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

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

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

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

        BarFoo
    }
}

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

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

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

其它运算符:

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

Index/IndexMut
#

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

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

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

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

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

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

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

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

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

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

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

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

impl SliceIndex<str> for RangeFull
type Output = str

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

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

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

Borrow/ToOwned/Cow
#

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

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

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

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

  • get() 方法的实现从自身 K.borrow() 生成 &Q 对象,然后用 K 的 Eq+Hash 值与 Q 的 Eq+Hash 值进行比较;
impl<K,V> HashMap<K, V> where K: Eq + Hash
{
    // key 必须是借用类型 &Q,而且需要 K 实现了 Borrow<Q>
    fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V> where K: Borrow<Q>, Q: Eq + Hash
    // ...
}

String 实现了 Borrow<str>, 它们具有相同的 Hash 语义,所以 HashMap<String, i32> 的 get() 方法可以传入 &String&str 类型值。

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

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

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

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

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

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

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

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

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

CloneToOwned trait 区别:

  1. 相同点: 两者都可以从 &self -> Self, 也即 &T -> T;

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

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

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

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

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

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

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

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

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

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

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


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

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

// 在 struct 中使用 Cow 的例子
struct Items<'a, X> where [X]: ToOwned<Owned = Vec<X>> {
    values: Cow<'a, [X]>,
}

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

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

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

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

fn foo(s: &str, some_condition: bool) -> &str {
    if some_condition {
        // 错误:在 block 结束时临时对象被释放,临时对象的引用无效
        &s.replace("foo", "bar")
    } else {
        // 如果把返回值改成 String,那么在 else 分支会有一次额外的拷贝。
        s
    }
}


// 可以使用 Cow,避免内存拷贝。
fn foo(s: &str, some_condition: bool) -> Cow<str> {
    if some_condition {
        Cow::from(s.replace("foo", "bar")) // 保存 Owned 的 String 对象
    } else {
        Cow::from(s) // 保存 &str
    }
}

Deref/DerefMut
#

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

  1. *t 返回 U,等效于 *Deref.deref(&t);
  2. &t 返回 &U, 等效于 Deref.deref(&t);
pub trait Deref {
    type Target: ?Sized;

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

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

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

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

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

  1. 使用 as 类型转换;
  2. 手动解引用 &*V
use std::ops::{Deref, DerefMut};

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

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

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

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

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

类型自动转化是 Rust 为了追求语言简洁,使用 Deref/DerefMut 实现的另一种隐式操作,比如下面的赋值都是正确的:

let s: String = "hello".to_string();
let s1: &String = &s;
let s2: &str = s1;
let s3: &str = &&s;
let s4: &str = &&&s;
let s5: &str = &&&&s;

无论有多少个 & ,Rust 都能正确的将其转为 &str 类型, 也就是 Rust 自动进行嵌套的隐式解引用,究其原因,是因为 deref coercions ,它允许在 T: Deref<U> 时,&T 可以自动转为 &U

smart pointer
#

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

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

impl Deref for String
  type Target = str

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

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

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

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

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

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

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

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

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

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

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

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

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

use std::sync::Arc;

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

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

  1. String 实现了 Deref<Target = str>;
  2. Vec 实现了 Deref<Target = [T]>;
fn read_slice(slice: &[usize]) {
    // ...
}

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

在函数传参匹配 trait bound 时不会进行协变和自动 Deref trait 调用,例如虽然 &mut i32 可以协变到 &i32, 但传参时不会协变。

带来的影响是:如果 Trait 作为函数参数限界,&i32&mut i32 两种类型都需要实现该 Trait,最佳实践是 T 实现 Trait。

trait Trait {}

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

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

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

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

Rc/Arc
#

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

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

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

  1. 一个链接表对象被多个其他链接表共享引用;
  2. 在多线程中共享大的数据,避免内存拷贝;
enum List {
    Cons(i32, Rc<List>),
    Nil,
}

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

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    // 不能使用 Box<T>, 否则这里会发生所有权转移,后续不能再使用它。
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
}

打印引用计数:

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

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

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

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

  • pub fn get_mut(this: &mut Rc<T, A>) -> Option<&mut T>: 当没有其他 Rc 对象引用 this 时, 也就是 this Rc 对象的引用计数 为 1 时, 返回 &mut T, 否则返回 None。

  • pub fn make_mut(this: &mut Rc<T, A>) -> &mut T : 要求 T 对象实现 Clone trait,该方法在强引用计数 >=1 时, clone 产生 一个新的 Rc 对象并替换 this,这被称为 clone-on-write, 然后返回它的 &mut T

    • 该方法在弱引用计数 >=1 时,不会 clone,而是打破弱引用计数对象引用的 T 对象,然后返回 &mut T
use std::rc::Rc;

let mut x = Rc::new(3);
*Rc::get_mut(&mut x).unwrap() = 4;
assert_eq!(*x, 4);

let _y = Rc::clone(&x);
assert!(Rc::get_mut(&mut x).is_none());

let mut data = Rc::new(5);
*Rc::make_mut(&mut data) += 1;
let mut other_data = Rc::clone(&data);
// 由于 data 被 clone 过, 再次调用 make_mut() 时会 clone。
// 一个新 Rc 对象并替换 data,这时 other_data 的引用计数 2 变为 1, data 的引用计数也为 1
*Rc::make_mut(&mut data) += 1;         // Clones inner data

// data 是刚才 clone 生成的新对象, 计数为 1, 所以这次不会再 clone
*Rc::make_mut(&mut data) += 1;         // Won't clone anything

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

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

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

  • 各种包装器类型,如 Box<T>,Cell<T>,RefCell<T>, 都提供了 into_inner() 方法,如:
// pub fn into_inner(self) -> T

use std::cell::Cell;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Weak<T> 对 T 进行弱引用计数,在它 >=1 时对象也可能因为强引用计数为 0 而被回收,所以 Weak 引用的对象可能是 None。

Weak 对象的 upgrade() 方法返回 Rc,但是如果 Weak 引用的对象不存在(为 None)则该方法返回 None。

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

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

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

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

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

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

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

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

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

Rc 由于多个 Clone 后的对象都共享相同的引用计数, 所以它没有实现 Send/Sync,从而不能在跨线程场景使用

Arc<T> 是线程安全的引用计数类型,它是实现了 Deref<Target=T> trait 的智能指针,但是未实现 DerefMut,所以和 Rc 一样,是只读的。

pub type LockResult<T> = Result<T, PoisonError<T>>;

pub struct MutexGuard<'a, T: ?Sized + 'a> { /* private fields */ }

// MutexGuard 是实现了 Deref/DerefMut<Iterm=T> 的智能指针,所以可以像 &mut T 一样使用受保护的数据,如 *data = 3;
//
impl<T: ?Sized> Mutex<T>
    // 这里指定 MutexGuard 的 lifetime 参数为 '_ 表示由编译器根据 lifetime-elision-rule 自动推导, 所以
    // MutexGuard 的 lifetime 和 &self 的 Mutex 对象一致。
    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>>

// 例子
use std::sync::Arc;
use tokio::{sync::Mutex, task};

async fn foo(x: Arc<Mutex<i32>>) -> i32 {
    {
        // v 是 MutexGuard<'a, T: ?Sized + 'a> 类型,‘a 和 x 的 lifetime 一致。
        // v 对应的 MutexGuard 是实现 Deref/DerefMut 的智能指针,所以可以像 &mut T 一样使用受保护的数据。
        let mut v = x.lock().await;
        *v += 1;
    }
    task::yield_now().await;

    {
        let mut v = x.lock().await;
        *v *= 2;
    }
    task::yield_now().await;

    let v = x.lock().await;
    *v + 3
}

#[tokio::main]
async fn main() {
    let v = Arc::new(Mutex::new(10));
    let v2 = v.clone(); // 或者: let v2 = Arc::clone(&v);
    // v 和 v2 都是指向相同的内存地址
    println!("v: {v:p}, v2: {v2:p}"); // v: 0x6326f4f7a7a0, v2: 0x6326f4f7a7a0

    let fut = foo(v2);
    let res = task::spawn(fut).await.unwrap();

    println!("result={res}, v={}", *v.lock().await);
}

Pin/Unpin
#

Pin<T> 是一个 struct std::pin::Pin 具体类型,将对象包装到 Pin 中后,可以确保该对象不会被移动(move)。

Unpin 是一个 Auto TraitMarker Trait,由编译器自动为类型实现。

异步编程的核心是 Future trait,它的 poll 方法的 self 签名类型是 self: Pin<&mut Self>,这里将 &mut Self(Self 为实现 Future 的对象)放到 Pin 的效果是:

  1. &mut Self 将 Self 借用冻结,只能在 poll() 中修改 Self;
  2. Pin 将 Self 进一步固定到内存中,不允许移动。

之所以在 poll future 对象要做这个操作,是因为 Feature 所在的异步上下文中各处 await 都会返回新的 Future 对象,它们可能持有上下文栈变量 地址,也即 Future 可能是自引用对象,而 Future 下一次被 wake 执行的时机是不定的,所以为了避免 Future 对象上保存的 stack 变量的地址发生 变化导致引用出错,需要将 Future 对象 Pin 在内存中,不允许 move。

如果 T 实现了 Unpin,则 Pin::new(&mut T) 将 T 对象固定到栈上,进而可以被安全的 poll。

如果 T 未实现 Unpin(如 async fn/async block/async closure 返回的未实现 Unpin 的 impl Future 对象), 则需要 使用 Box<Pin<T>> 将 T 固定到堆上,或使用 pin!() 将它固定在栈上,然后才能被安全的 poll。

Unpin
#

Unpin 是一个 Auto TraitMarker Trait,由编译器自动为类型实现,默认所有类型都是 Unpin,除非:

  • 类型显式实现了 !Unpin;
  • 或类型内部包含了某个 !Unpin 的字段, 如 std::marker::PhantomPinned

类型实现 Unpin trait 意味着该类型对象可以被安全的移动(move)。

在标准库(std/core)里显式定义实现 !Unpin 类型有三个: PhantomPinned、UnsafePinned<T> 和 std::error::Request

对于 slice/Arrry/HashMap/Pin<T>/struct/enum 等容器类型,需要元素 T 或 field 类型实现 Unpin 时,对应类型才实现 Unpin。否则,如果 T 实现了 !Unpin,则 Pin 等也实现了 !Unpin。

impl<Ptr> Unpin for Pin<Ptr> where Ptr: Unpin,
impl<F> Unpin for PollFn<F> where F: Unpin,
impl<'a, B> Unpin for Cow<'a, B> where <B as ToOwned>::Owned: Unpin, B: ?Sized,
impl<'a, F> Unpin for CharPredicateSearcher<'a, F> where F: Unpin,
impl<'a, I> Unpin for ByRefSized<'a, I>
impl<'a, I, A> Unpin for Splice<'a, I, A> where I: Unpin,
impl<'a, K, F> Unpin for std::collections::hash_set::ExtractIf<'a, K, F> where F: Unpin,
impl<'a, K, V> Unpin for std::collections::hash_map::Entry<'a, K, V> where K: Unpin,
impl<B> Unpin for std::io::Lines<B> where B: Unpin,
impl<I, F> Unpin for Map<I, F> where I: Unpin, F: Unpin,
impl<K, V, A> Unpin for BTreeMap<K, V, A> where A: Unpin,
impl<K, V, S> Unpin for HashMap<K, V, S> where S: Unpin, K: Unpin, V: Unpin,
impl<T> Unpin for [T] where T: Unpin,
impl<T> Unpin for (T, T, , Tₙ) where T: Unpin,

借用、裸指针,和智能指针类型 Box<T>/Rc<T>/Arc<T> 都无条件实现了 Unpin:

impl<T> Unpin for *const T where T: ?Sized,
impl<T> Unpin for *mut T where T: ?Sized,
impl<T> Unpin for &T where T: ?Sized,
impl<T> Unpin for &mut T where T: ?Sized,
impl<T, A> Unpin for Box<T, A> where A: Allocator, T: ?Sized,
impl<T, A> Unpin for Rc<T, A> where A: Allocator, T: ?Sized,
impl<T, A> Unpin for Arc<T, A> where A: Allocator, T: ?Sized,

编译器为 async fn/异步 block async {}/同步和异步闭包 生成的实现 Future 的匿名类型对象默认没有实现 Unpin,所以不能用于 Pin::new(&mut T)。为了对这些返回的 future 对象进行 poll,需要使用 pin!(future)Box::pin(future) 来创建一个 Pin<&mut impl Future>Pin<Box<impl Future>> 类型对象。

关于 pin!() 和 Box::pin() 参考:[14-rust-lang-async.md(14-rust-lang-async.md)

trait object 默认也没有实现 Unpin(即实现了 !Unpin),所以不能用于 Pin::new(&mut T)。 但可以在定义 trait 或 trait object 时显示定义 Unpin 约束,这样来标记对应的 trait object 实现 Unpin(类似的 trait object 的 Send/Sync 也需要显示标记)。

// 1. 默认情况:dyn Trait 未实现 Unpin
trait MyTrait {}
let _: &mut dyn MyTrait;  // OK
// 但 dyn MyTrait: !Unpin

// 2. 如果 trait 实现了 Unpin,则 trait object 也实现了 Unpin
trait MyTrait: Unpin {}
let _: &mut dyn MyTrait;   // dyn MyTrait: Unpin

// 3. 或者,在定义 trait object 时指定 Unpin 限界
trait MyTrait {}
let _: &mut (dyn MyTrait + Unpin); // dyn MyTrait + Unpin

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

fn example<T: Unpin>(x: Pin<&mut T>) {
    let mut_ref: &mut T = Pin::into_inner(x);
}

Pin
#

Pin<T> 是一个 struct std::pin::Pin 类型,将对象包装到 Pin 中后,可以确保该对象不会被移动(move)。

创建 Pin<T> 对象的几种方式:

  1. Pin::new(T): new() 方法对传入的 T 要求必须实现 Deref trait,而且 Target 类型必须是 Unpin
  • 传入的一般是 &T、&mut T、Box<T> 类型,它们都实现了 Deref trait,对应的 Target 是 T 类型,所以 T 也需要实现 Unpin

  • 不能传入 Box<dyn Future> ,因为 Target 对应的实现 Future 的对象类型可能没有实现 Unpin;

  1. Box::pin(T)pin!(T)
  • T 可以是实现 Future 类型对象:前者返回的是 Pin<Box<impl Future>> ,后者返回的是 Pin<&mut dyn Future> 类型对象;

如果 T 实现了 Deref,则 Pin 也实现了 Deref,所以 Pin::new() 能否传入 Pin、&Pin、&mut Pin,&T, &mut T, 取决于 T 是 否实现了 Unpin:如果 T 实现了 Unpin,则可以传入,否则不能传入,如:Pin<Box>、&Pin<&mut dyn Future>、&mut Pin<&mut dyn Future>、&dyn Future、&mut dyn Future 都不行。

Pin<&mut impl Future>,即包含了传入 impl Future 对象的 &mut 借用,这样确保只能通过返回的 Pin 对象来操作 Future 对象(&mut 是借 用冻结 ),而 Pin 对象又可以确保不会转移内部封装的对象,所以满足 Future 的 poll() 方法的签名要求。

impl<Ptr> Pin<Ptr> where Ptr: Deref, <Ptr as Deref>::Target: Unpin // Target 必须实现 Unpin
  pub fn new(pointer: Ptr) -> Pin<Ptr>

// 示例:
let mut data=43;
let pinned: Pin<&mut i32> = Pin::new(&mut data); // &mut data Deref 的 Target 是 i32 类型,它实现了 Unpin

let boxed = Box::new(42);
let pinned: Pin<Box<i32>> = Pin::new(boxed); // boxed Deref 的 Target 是 i32 类型,它实现了 Unpin

// 返回 struct std::future::Ready<i32> 类型, 它实现了 Unpin 和 Future
let mut unpin_future = std::future::ready(5);
// &mut unpin_future 是 &mut Ready<i32> 类型,它实现了 Deref 且 Target 是 i32,实现了 Unpin,故满足 Pin::new() 的前面要求
let my_pinned_unpin_future: Pin<&mut _> = Pin::new(&mut unpin_future);

// 返回 Pin<Box<i32>>
let pinned = Box::pin(42);

Pin<Ptr> 类型的方法:

  • as_ref()/as_mut()/info_ref() 都返回新的 Pin 对象。as_mut() 返回 Pin<&mut T> 类型,满足 Future::poll() 方法 签名要求;
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> // 返回新的 Pin 对象

impl<Ptr> Pin<Ptr> where Ptr: DerefMut
  pub fn as_mut(&mut self) -> Pin<&mut <Ptr as Deref>::Target> // 返回新的 Pin 对象
  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 // 返回 &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 // 返回 &mut T

Pin<Ptr> 实现了 Deref/DerefMut trait,故可以调用 Ptr 指向的类型的方法,同时也可以作为 Pin::new() 的参数:

  • 如果 T 实现了 Unpin,则 Pin<&mut T> 效果和 &mut 类似,mem::replace() 或 mem::take() 可以 move 该值。
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,但要求 Deref 的 Target 也实现了 Future,如 pin!(future_obj) 返回的对象:

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>

Future 未实现 Unpin
#

编译器为 async fn/async block/async closure 生成的实现匿名 Future 对象没有实现 Unpin,所以不能用于 Pin::new(&mut T)。(但不是所有 Future 对象都未实现 Unpin)。

这个问题的解法参考:[14-rust-lang-async.md(14-rust-lang-async.md) 中的 关于 pin!() 和 Box::pin() 部分。

Auto Trait
#

auto trait 参考:https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits

Auto Trait 是编译器为类型自动实现的 Trait,也即除非类型显式的使用 impl AutoTraitimpl !AutoTrait 定义 (negative implementations),则编译器自动为它实现 Auto Trait

常见的 Auto Trait 类型: Send, Sync, Unpin, UnwindSafe, RefUnwindSafe

编译器为类型实现 Auto Trait 的规则如下:

  • &T, &mut T, *const T, *mut T, [T; n], and [T] 实现 Auto Trait:如果 T 实现对应的 Auto Trait;
  • 函数和函数指针自动实现 Auto Trait;
  • Structs, enums, unions, and tuples 实现 Auto Trait:如果它所有成员类型实现对应的 Auto Trait;
  • Closure 闭包实现 Auto Trait:如果它捕获的所有对象实现对应的 Auto Trait,即借用捕获 &T/&mut T 或转移捕获的 U,是否都实现了对应的 Auto Trait;

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

trait objectasync {}/async fn 默认都没有实现 Send/Sync/Unpin。

Auto Trait 常用于 trait object 的 Trait 限界,如: dyn BaseTrait + Send + Sync + Unpin

  • trait object 的除了第一个 dyn compatible 的 BaseTrait 外,只能使用一个或多个 Auto Trait 来进行限界。

为自定义类型不自动实现 Auto Trait
#

如为 struct、enum、union 等自定义类型不自动实现 Auto Trait:

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

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

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

Send/Sync
#

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

  • Send:对象(的所有权)可以在多个线程中安全转移,即对象所有权转移具有原子性;

    • 常见的不满足 Send 要求的类型:Rc

    • 对于 &T,Rust 明确定义了实现 Send 的规则:impl<T> Send for &T where T: Sync + ?Sized,所以 T 需要实 现 Sync,所以 Rc/Cell/RefCell 等没有实现 Sync 的 &T 是没有实现 Send 的。

    • 对于 &mut T,由于是唯一访问对象的方式,所以自动实现了 Send。

  • Sync:对象的共享借用 &T 可以被在多个线程共享,也即如果类型 T 实现了 Sync,则 &T 可以多个线程间安全共享,也即 &T 类型实现了 Send。

    • 常见的不满足 Sync 要求的类型:
      • Rc:因为 Rc 的多个 Clone 对象都共享相同的引用计数,所以它们的 &T 不能在多个线程移动;
      • 内部可变性类型如 Cell/RefCell 等:,因为内部可变性类型是通过共享借用 &T 来修改对象的,所以不能在多个线程中安全地共享借用。

https://img.opsnull.com/blog/20250925174733408.png

use std::cell::Cell;

let cell = Cell::new(42);
let cell_ref = &cell; // Just a shared reference, no &mut
cell_ref.set(100);    // But we can still modify the value inside!
println!("{}", cell_ref.get());

内部可变性类型,如 Cell/RefCell 由于允许通过 &T 共享借用修改对象,所以打破了 &T 是只读借用的惯例,所以不能在多线程间共享这些借用,所以这些类型没有实 现 Sync,也即它们实现了 !Sync。

// This WILL NOT compile
let cell = Cell::new(0);
thread::scope(|scope| {
    // error[E0277]: `Cell<i32>` cannot be shared between threads safely
    scope.spawn(|| cell.set(1)); // Thread 1 writing
    scope.spawn(|| cell.set(2)); // Thread 2 writing simultaneously
});

但是不是所有的内部可变性类型都是 !Sync 的,AtomicU64/Mutex 等内部可变性类型通过特殊实现来支持多线程共享借用,它们实现了 Sync:

use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Mutex;

// AtomicU64: Uses hardware-level atomic operations
let atomic = AtomicU64::new(0);
thread::scope(|scope| {
    scope.spawn(|| atomic.store(1, Ordering::Relaxed)); // Safe
    scope.spawn(|| atomic.store(2, Ordering::Relaxed)); // Safe
});

// Mutex: Uses locking to serialize access
let mutex = Mutex::new(0);
thread::scope(|scope| {
    scope.spawn(|| *mutex.lock().unwrap() = 1); // Safe
    scope.spawn(|| *mutex.lock().unwrap() = 2); // Safe
});

对于 Rc 等引用计数类型,clone 生成的多个对象实际都共享同一个引用计数,所以不能再多个线程中安全转移,即 Rc 没有实现 Send,也即实现了 !Send:

The classic example is Rc<T> (reference-counted pointer):

use std::rc::Rc;

let rc1 = Rc::new(42);
let rc2 = rc1.clone(); // Both point to the same data

// If we could send rc1 to another thread (we can't!)...
thread::spawn(move || {
  drop(rc1); // Decrements reference count
});

// ...while rc2 exists on the original thread
drop(rc2); // Also decrements the same reference count

解决办法是使用 Arc 类型,它是线程安全的引用计数类型,即它 clone() 生成的多个对象实现了 Send:

use std::sync::Arc;

let arc1 = Arc::new(42);
let arc2 = arc1.clone();

// This works because Arc makes the shared state thread-safe
thread::spawn(move || {
    drop(arc1); // Atomically decrements counter
});

drop(arc2); // Also atomically decrements counter

但是只有当 T 实现了 Send+Sync 后 Arc 才实现 Send+Sync。所以 Arc<Cell> 并没有实现 Sync,因为 Cell 没有实现 Sync。

impl<T, A> Send for Arc<T, A> where T: Sync + Send + ?Sized, A: Allocator + Send,
impl<T, A> Sync for Arc<T, A> where T: Sync + Send + ?Sized, A: Allocator + Sync,
impl<T, A> Send for Box<T, A> where A: Send, T: Send + ?Sized,
impl<T, A> Sync for Box<T, A> where A: Sync, T: Sync + ?Sized,

Send 示例:多线程间可以转移对象 T:

// Send: Safe to move to another thread
let s = String::from("hello");
thread::spawn(move || {
    println!("{}", s); // s is now owned by this thread
});
// s is no longer accessible in the original thread

Sync 示例:多线程可以共享借用 &T(因为是只读的,所以一般没有数据竞争,但例外的情况:T 是 Rc、 或通过共享借用修改对象的内部可变性对象 Cell、RefCell 等没有实现 Sync 的类型):

// Sync: Safe to share references across threads
let s = String::from("hello");
let s_ref = &s;

thread::scope(|scope| {
    scope.spawn(|| {
        println!("{}", s_ref); // Reading via shared reference
    });
    scope.spawn(|| {
        println!("{}", s_ref); // Multiple threads can read simultaneously
    });
});

Rust 明确定义了如下实现规则,则 Rust 不再自动为对应类型实现 Auto Trait。

  1. Rust 显式定义的没有实现 Send (显式的使用impl !Send for XX 来定义)的类型:

    // 引用计数类型,如 Rc/Ref/RefMut, 没有实现 Send(但是 Arc 实现了 Send)
    impl<T, A> !Send for Rc<T, A> where A: Allocator T: ?Sized
    impl<T, A> !Send for UniqueRc<T, A> where A: Allocator T: ?Sized
    impl<T, A> !Send for std::rc::Weak<T, A> where A: Allocator T: ?Sized
    impl<'b, T> !Send for Ref<'b, T>
    impl<'b, T> !Send for RefMut<'b, T>
    
    // 裸指针类型都没有实现 Send
    impl<T> !Send for *const T where T: ?Sized
    impl<T> !Send for *mut T where T: ?Sized
    
    • Rc<T>: 没有实现 Send 和 Sync(多线程转移 Rc 对象时,不能保证原子性),不能被转移到 thread closure 中。

    • 多线程环境中需要使用 Arc<T> 和它的 Arc::clone() 后的新对象。

    • 对于 Arc 和 Box, 需要 T 要实现 Send、Sync trait 后,Arc/Box 才实现 Send、Sync。所以 Arc<Rc> 没有实现 Send,Arc<Cell> 没有实现 Sync。

    • std::syn 下的 MutexGurad,RwLockReadGuard,RwLockWriteGuard :由于通过 thread-local 机制 实现,所以也没有实现 Send,所以只能在创建它的线程中使用;

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

    • Rust 显式的为 &T 定义了实现 Send 的规则: impl<T> Send for &T where T: Sync + ?Sized 规则,即 &T 是否实现 Send 取决于 T 是否实现 Sync

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

    • Rc<T>

    • Cell/RefCell/OnceCell/std::sync::mpsc:Receiver :没有实现 Sync,不能在多个线程中共享引用,需要使用 Mutex、RwLock、AtomicXX 类型代替;

    • 裸指针 *const/*mut T 没有实现 Sync

由于编译器没有向上面为其它任意类型 T 显式定义 Sync/!Sync 规则,所以除了上面明确的使用 impl !Sync for T 明确定义的类型(一般是 Rc 和 Cell 等内部可变性类型 )和 trait object 外,其它类型都自动实现了 Sync。

对于借用 &T 和 &mut T 是否实现 Sync、Send 也遵从 Auto Trait 的自动实现限制:

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

注:trait 和 trait object 由于是运行时动态分发类型,在编译时不能自动推导是否实现 Send/Sync,所以需要显式的来指定它是否实现了 Auto Trait:

  1. 定义 trait 时,通过 super trait 语法来定义它实现 Send、Sync: trait MyTrait: Send + Sync;
  2. 使用 trait object 时,显式进行限界: Box<dyn Debug+Send+UnwindSafe>;

参考:https://blog.cuongle.dev/p/this-sendsync-secret-separates-professional-and-amateur

闭包和 impl Future 的 Send+‘static 问题
#

参考:9-rust-lang-function-closure.md

std::sync::mpsc:Receiver 没有实现 Sync
#

std::sync::mpsc:Receiver 没有实现 Sync,意味着不能能把同一个 Receiver 的借用(&Receiver<T>&mut Receiver<T>)在多个线程间共享,而只能由一个 线程来消费它。但 Receiver<T> 实现了 Send,所以你可以把所有权转移到另一个线程,让一个线程独占地消费消息。

根据前面介绍的规则: &T is Send if and only if T is Sync 《– 上面 Rust 显式定义的规则 impl<T> Send for &T where T: Sync + ?Sized

所以 T 为 Receiver 时,&T 没有实现 Send,如下 rx1 和 rx2 不能在多线程间共享:

use std::sync::mpsc::channel;
use std::thread;

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

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


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

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

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

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

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

&T 实现 Send/Sync
#

编译器为类型实现 Auto Trait 的规则如下:

  • &T, &mut T, *const T, *mut T, [T; n], and [T] 实现 Auto Trait,如果 T 实现对应的 Auto Trait;

  • 除了对象,借用 &T 是否满足 Send 有特殊要求:T 需要实现 Sync,这是 Rust 显式的为 &T 定义了实现 Send 的规则: impl<T> Send for &T where T: Sync + ?Sized 规则,即 &T 是否实现 Send 取决于 T 是否实现 Sync

所以 Rc、Cell、RefCell 等没有实现 Sync 的 &T、&mut T 是不能 Send 的。

注意:&mut T 是否实现 Send、Sync,取决于 T 是否实现了 Send、Sync。

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

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

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

解决办法:为 T 添加 ?Sized 限界(或则使用 Box 类型):

trait MyTrait {}

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

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

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

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

解决办法:为 trait MyTrait 添加 Send 限界:

trait MyTrait: Send {}

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

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

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

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

解决办法:为 trait MyTrait 添加 Sync 限界:

trait MyTrait: Send + Sync {}

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

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

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

或者为 dyn MyTrait 直接添加 Send + Sync 限界:


trait MyTrait {}

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

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

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

参考
#

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

相关文章

11. 类型协变:type coercion
·
Rust
Rust 高级话题:子类型和类型协变
14. 异步:async
·
Rust
Rust 异步编程
18. 测试:testing
·
Rust
Rust 测试
6. 生命周期:lifetime
·
Rust
Rust 对象生命周期管理和检查