泛型 #
类型/函数/方法可以使用泛型参数,用 <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)支持:
- 整数常量(usize, u8 等)
- 布尔常量
- 字符常量
- 枚举值(要求实现 ConstParamTy)
- 已有 const 引用
- 数组 / 元组常量
- 编译期常量表达式
- 嵌套 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
包含三种类型:
- PathIdentSegment: 标识符,如:
T: std::ops::Index
- GenericArgs: 进一步使用泛型参数
<xx>
,如T: std::ops::Index<'a, std::ops::Range<usize>>
- PathIdentSegment:
std::ops::Index
- GenericArgs:
<'a, std::ops::Range<usize>>
- TypePathFn:泛型函数,如
T: FnOnce() -> T
:
- PathIdentSegment:
FnOnce
- TypePathFn:
() -> T
而 GenericArgs
是 <xx,yy>
格式,其中的内容分为如下类型:Lifetime | Type | GenericArgsConst | GenericArgsBinding | GenericArgsBounds
- Lifetime:
T: std::ops::Index<'a>
- Lifetime + Type:
T: std::ops::Index<'a, i32>
- GenericArgsConst:
T: m::m2::MyTrait<23, 'x', U>
- GenericArgsBinding,即对关联类型绑定具体类型:
T: Add<U, Output = Option<i32>>
- 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
trait #
trait 可以作为泛型参数的限界约束,也可以作为函数的参数或返回值(如:impl Trait
或 trait 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
来指定:
Add<&int32, Output=int32>
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 的场景:
- 泛型参数限界:
fn f<A: Copy>() {}
- 关联类型限界:
trait A { type B: Copy; }
等效于trait A where Self::B : Copy { type B;}
- 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,就不能调用这些方法。
最佳实践:
- 在为自定义类型实现 Trait 时,一般为三种类型
T, &T, &mut T
都实现同一个 trait。 - 或者,在 T 上实现 trait,这样内部可以使用
self
或&mut self
;
trait Trait {}
fn foo<X: Trait>(t: X) {}
impl<'a> Trait for &'a i32 {}
fn main() {
let t: &mut i32 = &mut 0;
foo(t); // 报错。
}
// error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
// --> src/main.rs:9:9
// |
// 3 | fn foo<X: Trait>(t: X) {}
// | ----- required by this bound in `foo`
// ...
// 9 | foo(t);
// | ^ the trait `Trait` is not implemented for `&mut i32`
// |
// = help: the following implementations were found:
// <&'a i32 as Trait>
// = note: `Trait` is implemented for `&i32`, but not for `&mut i32`
函数传参匹配 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>
的两个场景:
-
为自定义类型的定义泛型的方法或关联函数:
impl<T> MyStruct<T> {}
; -
为自定义类型类型实现泛型 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 Trait
或T: 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 Trait
或 Box<dyn Trait>
胖指针类型 (fat pointer
),包含两部分:
- 数据指针(data pointer):指向实现 Trait 的具体类型对象内存(如 String 对象的为 3 个 usized:data pointer、length、capacity)。
- vtable 指针(vtable pointer,virtual method table,虚方法表):指向该具体类型的 vtable 表。
Rust 每个静态类型都有自己的 vtable,包含如下内容:
- 该类型实现的各 trait 方法的函数指针,即实现方法的入口地址。
- 类型的元信息,如大小
size_of::<T>()
,对齐align_of::<T>()
,析构函数drop_in_place::<T>
等。
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
的函数调用执行过程:
- a 是一个胖指针,包含:1. data_ptr → 指向 Dog 实例; 2. vtable_ptr → 指向 Dog 类型的 vtable。
- 调用 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));
参考:
- https://quinedot.github.io/rust-learning/dyn-trait.html
- 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 取决于:
- TraitName 是否实现 Send、Sync trait;
- 或者,本身有没有加 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 MyTrait
或 Box<dyn MyTrait>
,因为是运行时动态派发类型,在编译期间不能确定具体类型,所以编译器默认没有为它们实现 Send/Sync/Unpin
,而需要显式的来指定:
- 定义
trait
类型时是否实现了Send/Sync/Unpin
,如果实现了,则后续 dyn MyTrait 就也实现了对应的 Send/Sync/Unpin。 - 或则,使用
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 推导规则 #
两个 dyn trait 的 trait、lifetime 如果相同则类型互为别名,如:dyn Trait + Send + UnwindSafe
和 dyn 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 Trait
或 Box<dyn Trait>
时,如果没有显式指定生命周期参数(例如 &(dyn Trait+ 'a)
),编译器需要一个规则来确定这个 trait object
内 部可能 包含的引用(例如上面的 &str)的生命周期。
普通函数参数/返回值的生命周期省略规则(比如 fn foo(x: &i32) -> &i32
等价于 fn foo<'a>(x: &'a i32) -> &'a i32
)和这里的推断规则不同。换句话说,trait object 有自己的一套规则。
-
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 }
-
根据外层容器(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>;
-
根据 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 {}
总结(简化版)
- &dyn Trait → 生命周期来自 &。
- Box
→ 没有显式生命周期,默认 ‘static。 - dyn Trait + ‘_ → 强制走普通省略规则。
- 如果 trait 自身有生命周期参数,则依赖那个规则。
dyn Trait
的 lifetime 规则:
Box<dyn Trait>
等效于Box<dyn Trait + 'static>
,因为 Box 接收 trait object 的所有权,所以是 ‘static;&'x Box<dyn Trait>
等效于&'x Box<dyn Trait + 'static>
;&'a dyn Foo
等效于&'a (dyn Foo + 'a)
,因为&'a T
隐式声明了T: 'a
&'a (dyn Foo+'a)
表示dyn Foo
的 trait object 的 lifetime 要比 ‘a 长,同时它的引用也要比 ‘a 长。
&'r Ref<'q, dyn Trait>
等效于&'r Ref<'q, dyn Trait+'q>
;
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 type
或 opaque 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
。
参考:
- https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits/
- 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()
}
}
总结:
-
普通函数返回
impl Trait
时,编译器在编译阶段为其生成一个具体的匿名类型,而且后续不再变化; -
trait 中函数返回
impl Trait
时,编译器通过为 trait 生成一个关联类型来保存生成的匿名类型,而且编译器为不同实现该 Trait 的类型实现 的匿名类型还不一致。
由于 trait 中的 async fn
函数是返回 impl Future
的语法糖,这会导致两个常见的问题:
- 无法对
async fn
的返回值进行限界,编译器不能在编译期间推导它是否实现Send/Unpin
(所以默认仍未没实现Send/Unpin
),也不能进行限界; - 包含
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<...>
限制条件:
- 仅允许一个
use<..>
,且必须包含 返回值对象实际捕获的所有泛型类型参数、生命周期参数
- 所有作用域内的类型泛型和常量泛型参数
- 所有出现在其他 bounds 中的生命周期参数
- 生命周期顺序
- 在 use<..> 中,生命周期参数必须在所有类型和常量泛型参数之前。
- 可以使用省略生命周期 ‘_’(如果在返回位置允许)。
- 不能用于参数位置的 impl Trait,因为参数位置的 impl Trait 会引入匿名类型参数,无法在 use<..> 中按名称引用。
- Trait 方法中必须包含 Self,在 trait 定义的关联函数中,use<..> 必须包含 trait 的所有泛型参数,包括隐式的 Self。
总结:
- 自动捕获:适合大多数情况,尤其是 2024 edition 之后,省心无痛。
- 精确捕获:用于控制 API 暴露的泛型依赖,确保 impl Trait 只绑定特定的泛型参数。
- 作用:决定匿名返回类型依赖哪些泛型参数,影响类型推导、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)的类型:
str
;[T]
;dyn MyTrait
, 包括函数类型dyn Fn(&str) -> T
;
对于 unsized 类型,Rust 不能将它们作为变量或函数参数,而是需要转换为固定大小的引用或固定大小的 Box,如:
&str, Box<str>
;&[T], Box<[T]>
;&dyn MyTrait, Box<dyn Myrait>, &dyn Fn(&str) -> T
PhantomData #
PhantomData
是一个不占用内存的泛型 struct 类型, 唯一值是 PhantomData
:
pub struct PhantomData<T> where T: ?Sized;
常用在一些特殊场景来避免编译错误:
-
lifetime:
// Error: 没有 field 使用 'a struct Slice<'a, T> { start: *const T, end: *const T, } // OK: 用 0 长字段的 PhantomData 使用 'a use std::marker::PhantomData; struct Slice<'a, T> { start: *const T, end: *const T, // 隐式表示 T: 'a, 即 T 对象的引用的生命周期要比 'a 长。 phantom: PhantomData<&'a T>, }
-
隐藏数据: 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); }
-
编译时类型检查:
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 的差别:
- Copy 是编译器隐式调用的(如变量赋值, 传参等), 不能被用户自定义实现或显式调用, 一般是栈内存的直接拷贝(simple bit-wise copy,共享堆内存);
- Clone 是需要显式调用的, 如
x.clone()
, 在具体实现时可能会拷贝堆内存;
默认实现 Copy 的情况:
- 基本类型, 如整型, 浮点型, bool, char;
- Option
, Result<T,E>, Bound , 共享借用类型 &T
; - 数组
[T;N]
, 元组(T, T)
: 前提: 元素类型 T 实现了 Copy;
没有实现 Copy 的情况:
- 自定义 Struct/Enum/Union 类型;
- 容器类型,如 std::collections::Vec/HashMap/HashSet; (但都实现了 Clone)
- &mut T(但 &T 实现了 Copy)
- 实现了 Drop 的类型(因为编译器认为该类型有自己的资源或状态管理机制)
- 各种智能指针,如 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 作为泛型类型的定界, 在标准库中得到广泛使用, 例如:
Option::unwrap_or_default()
方法;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 在以下场景被自动调用:
-
Rust 为任何实现了
From<T> trait
的类型自动实现Into<U> trait
:let from_type: FromType = FromType::new(); let into_type: IntoType = from_type.into(); // 自动调用 From<FromType> for IntoType 的实现
-
使用
?
运算符处理Result
或Option
错误在返回错误时,将错误类型转换为函数返回的错误类型。fn from_type() -> Result<IntoType, IntoError> { let result: Result<FromType, FromError> = do_something_may_fail(); let from_type = result?; // 如果出错,自动调用 From<FromError> for IntoError 的实现并返回 }
标准库提供了一个
blanket From trait
的实现,用于将std:error::Error
转换为Box<dyn std::error::Error>
。?操作符自动使用From trait
来做类似转换:impl<'a, E: Error + Send + Sync + 'a> From<E> for Box<dyn Error + Send + Sync + 'a> { fn from(err: E) -> Box<dyn Error + Send + Sync + 'a> { Box::new(err) } } type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>; type GenericResult<T> = Result<T, GenericError>; fn parse_i32_bytes(b: &[u8]) -> GenericResult<i32> { Ok(std::str::from_utf8(b)?.parse::<i32>()?) }
-
collect
方法将Iterator<Item=T>
转换为其他容器类型:let vec: Vec<FromType> = get_some_from_types(); // 自动调用 From<FromType> for IntoType 的实现 let result: Result<Vec<IntoType>, _> = vec.into_iter().collect();
-
std::convert::from
函数会显式调用From
trait:let from_type = FromType::new(); // 显式调用 From<FromType> for IntoType 的实现 let into_type:IntoType = std::convert::From::from(from_type);
-
变量赋值和函数返回值: 如
&str
实现了Into Box<dyn std::error::Error>
的 trait, 则可以直接调用&str.into()
方法。如果
impl From<T> for U
, 则可以let u: U = U::from(T)
或let u: U = T.into()
:fn main() { if let Err(e) = run_app() { eprintln!("Application error: {}", e); std::process::exit(1); } } fn run_app() -> Result<(), Box<dyn std::error::Error>> { 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 express
的express
结果对象隐式调用 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,
}
}
}
其它运算符:
i..j, i.., ..j, i..=j
等:是各种struct RangeXX
类型语法糖;?
:适用于 Result 和 Option,和 From/Into trait 协助进行自动类型转换;*val
:使用Deref/DerefMut trait
进行重载;(用于实现智能指针)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 的类型没有限定,可以是 usize
如 a[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
语义。
实现 BorrrowString/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();
Clone
和 ToOwned
trait 区别:
-
相同点: 两者都可以从
&self -> Self
, 也即&T -> T
; -
不同点: Clone 只能实现
&T -> T
的转换, 而 ToOwned trait 可以更灵活, 转换后的对象不局限于 T,只要 Owned 类型实现了Borrow<Self>
即可。例如 String 实现了 Borrow, 则 &str 就可以转换为 Owned 类型为 String 的对象。
std::borrow::Cow
是可以保存 &B
或 B 的 Owned
类型的枚举类型(cow:clone on write
):
// B 必须实现 ToOwned trait
pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B), // &B
Owned(<B as ToOwned>::Owned), // &B 的 Owned 类型
}
Cow 实现了各种 From trait
,可以从多种类型对象生成 Cow:
impl<'a, T> From<&'a [T]> for Cow<'a, [T]> where T: Clone
impl<'a, T, const N: usize> From<&'a [T; N]> for Cow<'a, [T]> where T: Clone
impl<'a> From<&'a CStr> for Cow<'a, CStr>
impl<'a> From<&'a CString> for Cow<'a, CStr>
impl<'a> From<&'a OsStr> for Cow<'a, OsStr>
impl<'a> From<&'a OsString> for Cow<'a, OsStr>
impl<'a> From<&'a Path> for Cow<'a, Path>
impl<'a> From<&'a PathBuf> for Cow<'a, Path>
impl<'a> From<&'a String> for Cow<'a, str>
impl<'a, T> From<&'a Vec<T>> for Cow<'a, [T]> where T: Clone
impl<'a> From<&'a str> for Cow<'a, str>
impl<'a> From<CString> for Cow<'a, CStr>
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
操作:
*t
返回 U,等效于*Deref.deref(&t)
;&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
来解引用
来满足泛型参数限界的要求。两种 解决办法:
- 使用
as
类型转换; - 手动解引用
&*V
;
use std::ops::{Deref, DerefMut};
impl<T> Deref for Selector<T> {
type Target = T;
fn deref(&self) -> &T { &self.elements[self.current] }
}
impl<T> DerefMut for Selector<T> {
fn deref_mut(&mut self) -> &mut T { &mut self.elements[self.current] }
}
// OK: 在函数传参时, Rust 会自动隐式解引用, &Selector -> &str
let s = Selector { elements: vec!["good", "bad", "ugly"], current: 2 };
fn show_it(thing: &str) { println!("{}", thing); }
show_it(&s);
// Error: 当使用 Trait 作为泛型参数的限界时, Rust 不会自动解引用到 &str 类型来满足类型限界的要求
use std::fmt::Display;
fn show_it_generic<T: Display>(thing: T) { println!("{}", thing); }
show_it_generic(&s);
// 两种解决办法:
show_it_generic(&s as &str); // 使用 as 类型转换;
show_it_generic(&*s) // 手动解引用 &*V 来得到 &str 类型:
类型自动转化是 Rust 为了追求语言简洁,使用 Deref/DerefMut
实现的另一种隐式操作,比如下面的赋值都是正确的:
let s: String = "hello".to_string();
let s1: &String = &s;
let s2: &str = s1;
let s3: &str = &&s;
let s4: &str = &&&s;
let s5: &str = &&&&s;
无论有多少个 & ,Rust 都能正确的将其转为 &str 类型, 也就是 Rust 自动进行嵌套的隐式解引用,究其原因,是因为 deref coercions
,它允许在 T: Deref<U>
时,&T
可以自动转为 &U
。
smart pointer #
只要实现了 Deref trait 的类型都可以称为 smart pointer,例如:
// https://doc.rust-lang.org/std/ops/trait.Deref.html
impl Deref for String
type Target = str
impl<B> Deref for Cow<'_, B> where B: ToOwned + ?Sized, <B as ToOwned>::Owned: Borrow<B>
type Target = B
impl<P> Deref for Pin<P> where P: Deref
type Target = <P as Deref>::Target
impl<T> Deref for &T where T: ?Sized
type Target = T
impl<T> Deref for &mut T where T: ?Sized
type Target = T
impl<T> Deref for Ref<'_, T> where T: ?Sized
type Target = T
impl<T> Deref for RefMut<'_, T> where T: ?Sized
type Target = T
impl<T, A> Deref for Box<T, A> where A: Allocator, T: ?Sized
type Target = T
impl<T, A> Deref for Rc<T, A> where A: Allocator, T: ?Sized
type Target = T
impl<T, A> Deref for Arc<T, A> where A: Allocator, T: ?Sized
type Target = T
impl<T, A> Deref for Vec<T, A> where A: Allocator
type Target = [T]
Ref<T>/RefMut<T>/Rc<T>/Arc<T>/Box<T>
:
*
操作符解引用后类型都是 T,实际执行的操作为:*(v.deref())
;- 在需要
&T
的地方都可以传入&Ref/&Rc/&Box
类型; Ref<T>/Box<T>
可以调用 T 定义的所有方法;
由于智能指针都可以调用 T
的方法, 为了避免指针的方法和 T
的方法冲突, 在调用智能指针自己的方法或实现的 trait 时使用全限定名称:
use std::sync::Arc;
let foo = Arc::new(vec![1.0, 2.0, 3.0]);
// 以下两个方法调用等价,但是建议使用第二种方式: 全限定名称的方法或关联函数调用
//
// 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 个机器字栈空间+可变长堆空间:
- String 实现了
Deref<Target = str>
; - 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>
类似,也是在堆上分配内存,并拥有它,但是:
Rc::clone(&a)
增加 a 引用计数,并不会重新分配堆内存;Rc
和clone()
返回的 Rc 对象都被 drop 后,a 对应的堆内存才会被释放;Rc
值是只读的,所以可以和Cell/RefCell
结合使用,如Rc<Cell<T>>
,使用内部可变性机制来修改共享对象 T;
Rc<T>
主要使用场景是需要多 owner 的情况,例如:
- 一个链接表对象被多个其他链接表共享引用;
- 在多线程中共享大的数据,避免内存拷贝;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
// 不能使用 Box<T>, 否则这里会发生所有权转移,后续不能再使用它。
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
打印引用计数:
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
// $ cargo run
// Compiling cons-list v0.1.0 (file:///projects/cons-list)
// Finished dev [unoptimized + debuginfo] target(s) in 0.45s
// Running `target/debug/cons-list`
// count after creating a = 1
// count after creating b = 2
// count after creating c = 3
// count after c goes out of scope = 2
Rc 提供了 get_mut()/make_mut()/into_inner()
方法来修改对象。
-
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
。
- 该方法在弱引用计数 >=1 时,不会 clone,而是打破弱引用计数对象引用的 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,从而发生内存泄露:
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 Trait
和 Marker Trait
,由编译器自动为类型实现。
异步编程的核心是 Future trait
,它的 poll 方法的 self 签名类型是 self: Pin<&mut Self>
,这里将 &mut
Self(Self 为实现 Future 的对象)放到 Pin 的效果是:
- &mut Self 将 Self 借用冻结,只能在 poll() 中修改 Self;
- 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 Trait
和 Marker 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
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>
对象的几种方式:
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;
Box::pin(T)
和pin!(T)
:
- T 可以是实现
Future
类型对象:前者返回的是Pin<Box<impl Future>>
,后者返回的是Pin<&mut dyn Future>
类型对象;
如果 T 实现了 Deref,则 Pin
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 AutoTrait
或 impl !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 object
、async {}/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
:
- 使用不占用任何空间的 PhantomData 类型:
use std::marker::PhantomData;
// 如果只有一个 handle 字段,则 struct X 实现了 Sync 和 Send,
// 如果添加了 _not_sync 字段,则 struct X 没有实现 Sync,但实现了 Send
struct X {
handle: i32,
_not_sync: PhantomData<Cell<()>>,
}
- 或则明确为类型实现这两个标记 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 来修改对象的,所以不能在多个线程中安全地共享借用。
- 常见的不满足 Sync 要求的类型:
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
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
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
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。
-
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;
-
-
没有实现 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 isSync
《– 上面第一条规则; - &T is
Send
if and only if T isSync
《– 上面 Rust 显式定义的规则impl<T> Send for &T where T: Sync + ?Sized
- &mut T is
Send
if and only if T isSend
《— 上面第一条规则
注:trait 和 trait object 由于是运行时动态分发类型,在编译时不能自动推导是否实现 Send/Sync,所以需要显式的来指定它是否实现了 Auto Trait:
- 定义 trait 时,通过 super trait 语法来定义它实现 Send、Sync:
trait MyTrait: Send + Sync
; - 使用 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 = ℞
let rx2 = ℞
// 编译报错: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:?}");
}