泛型参数 #
类型/函数/方法可以使用泛型参数,用 <CamelCase, ...>
表示。泛型参数可以使用一个或多个 lifetime 和 trait
进行限界:
// 泛型类型:T 为泛型类型名称,名称位于类型名后的 <xx> 中。
// T 没有任何限界,默认为 Sized。
struct Point<T> { x: T, y: T, }
// 使用时,如果未指定泛型参数值,编译器自动推导
let p = Point{
x: "5".to_string(),
y : "hello".to_string()
};
// 使用时,为泛型参数指定具体类型,相当于实例化出一个匿名的具体类型
fn gen_spec_t(_s: Point<A>) {}
fn gen_spec_i32(_s: Point<i32>) {}
struct Val<T> {
val: T,
}
// x 是 Val<f64> 类型
let x = Val{ val: 3.0 };
// y 是 Val<String> 类型
let y = Val{ val: "hello".to_string()};
// 3.0, hello
println!("{}, {}", x.value(), y.value());
// 泛型函数:类型参数位于函数名后的 <xx> 中。
fn generic<T>(_s: SGen<T>) {}
// 调用泛型函数时,使用比目鱼语法:method::<type>() 来指定泛型参数的具体类型。
generic::<char>(__);
// 为泛型类型实现关联函数或方法时,impl 后面需要列出泛型类型所需的所有泛型参数
//
// 函数和方法也可以定义自己的泛型参数, 它们不需要在 impl 后列出,而是在调用时推导或指定。
impl<T> Val<T> {
// 方法可以使用 impl 的泛型参数
fn value(&self) -> &T {
&self.val
}
// 方法可以有自己的泛型参数
fn add<O: Debug>(&self, other: O) {
println!("{:?}", other);
}
}
let x = Val { val: 3.0 };
let y = Val { val: "hello".to_string(), };
println!("{}, {}", x.value(), y.value());
x.add(123); // 推断
x.add::<i32>(123); // 为泛型方法手动指定类型,使用比目鱼语法。
为泛型类型定义关联函数或方法时,可以对 T 添加额外的限制:
// 对 T 无限制,等效于 Sized 限界
struct Pair<T> { x: T, y: T,}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y, }
}
}
impl<T: std::fmt::Debug + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {:?}", self.x);
} else {
println!("The largest member is y = {:?}", self.y);
}
}
}
泛型 trait #
trait 可以作为泛型参数的限界约束,也可以作为函数的参数或返回值(impl Trait
或 trait objecct
):
- 匿名函数也可以作为泛型参数的限界约束,例如函数指针
fn() -> u32
或闭包Fn() -> u32
,但后续只能传入 fn 而不能是闭包; - 除了对泛型参数 T 直接限界外,也可以使用
where
对使用 T 的其它类型进行限界,如:Option<T>: Debug;
- 做泛型限界的 trait 类型和数量没有限制。但是定义 trait object 的 trait 必须是
safe trait 列表
,一般是一个自定义 trait 类型,再加上:Send, Sync, Unpin, UnwindSafe 和 RefUnwindSafe 的一个或多个组合,不包含 ?Sized,Hash,Eq 等 trait。
// 泛型 trait,泛型参数 A、B 没有定义限界,默认为 Sized。
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
// 实现泛型 trait 时,可以指定具体类型或者继续使用泛型参数。
impl Contains<i32, i32> for Container {
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
fn first(&self) -> i32 { self.0 }
fn last(&self) -> i32 { self.1 }
}
// 泛型参数可以使用 + 号连接的多个 trait 或 lifetime 作为限界。
fn compare_prints<T: Debug + Display>(t: &T) {
println!("Debug: `{:?}`", t);
println!("Display: `{}`", t);
}
fn compare_types<T: Debug, U: Debug>(t: &T, u: &U) {
println!("t: `{:?}`", t);
println!("u: `{:?}`", u);
}
// 对于复杂的、较长的限界, 可以使用 where 子句来定义
impl <A: TraitB + TraitC, D: TraitE + TraitF> MyTrait<A, D> for YourType {}
// 等效于
impl <A, D> MyTrait<A, D> for YourType
where
A: TraitB + TraitC,
D: TraitE + TraitF
{
//..
}
// 有些场景必须使用 where 来限界,比如这里限界的是 Option<T>
trait PrintInOption {
fn print_in_option(self);
}
impl<T> PrintInOption for T where Option<T>: Debug {
fn print_in_option(self) {
println!("{:?}", Some(self));
}
}
// 定义泛型 trait 时,可以为泛型参数指定缺省类型
pub trait Add<Rhs = Self> {
// 关联类型也可以指定缺省类型
type Output = Self;
// Required method
fn add(self, rhs: Rhs) -> Self::Output;
}
泛型常量 #
trait 也支持泛型常量参数,如 <const N: usize>
,这样可以在泛型类型/函数/方法中来作为 Array 的长度。
实例化常量参数时,使用字面量或 {XXX}
常量表达式或由编译器推导:
struct ArrayPair<T, const N: usize> {
left: [T; N],
right: [T; N],
}
impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> {
// ...
}
// 实例化常量参数
fn foo<const N: usize>() {}
fn bar<T, const M: usize>() {
foo::<M>();
foo::<2021>();
// OK:使用 <{xx}> 常量表达式
foo::<{20 * 100 + 20 * 10 + 1}>();
// Error: 常量表达式不支持泛型常量运算
foo::<{ M + 1 }>();
// Error: 常量表达式不支持泛型参数
foo::<{ std::mem::size_of::<T>() }>();
let _: [u8; M]; // OK
// Error: const expression contains the generic parameter `T`
let _: [u8; std::mem::size_of::<T>()];
}
关联类型 #
trait 支持关联类型(type alias 语法), 这些类型在定义 trait 时一般是未知的,在实现时指定具体类型,trait 函数或方法可以使用 Self::Item 来引用关联类型 Item。
在定义 trait 时,可以为关联类型指定缺省类型或用其它 trait 做限界:
trait Iterator {
// 声明关联类型,同时对类型进行限界。使用 trait 限界语法。
type Item: std::fmt::Display;
fn next(&mut self) -> Option<Self::Item>;
}
pub trait Add<Rhs = Self> {
// 关联类型也可以指定缺省类型。使用 type alias 语法。
type Output = Self;
// Required method
fn add(self, rhs: Rhs) -> Self::Output;
}
在定义泛型 trait 时,泛型参数和关联类型都可以指定缺省类型, 例如各种运算符重载 trait 都指定了默认类型。
使用泛型 trait 时,可以同时指定泛型参数和关联类型, 泛型参数只能按顺序来指定,而关联类型可以使用 K=V
来指定:
Add<&int32, Output=int32>
Add<Output=int32>
, 有缺省值的泛型参数可以忽略。
但不能使用 Add<Rhs=&T, Output=T>
, 因为 Rhs
是泛型类型参数而不是关联类型参数,K=V 只能用于关联类型参数:
// 定义泛型 trait 时,泛型参数和关联类型都可以指定缺省类型
pub trait Add<Rhs = Self> {
type Output = Self;
// Required method
fn add(self, rhs: Rhs) -> Self::Output;
}
// 实现 trait 时,需要为关联类型指定具体类型
impl Add<Bar> for Foo {
type Output = FooBar;
fn add(self, _rhs: Bar) -> FooBar {
FooBar
}
}
// 定义一个泛型函数,使用 Add trait 作为泛型参数的限界,同时指定 Rhs 类型和关联类型。
// 不能使用 Rhs = Rhs, 否则 Rust 会报错没有 Rhs 关联类型。
fn add_values<T, Rhs>(a: T, b: Rhs) -> T
where T: Add<Rhs, Output = T>
{ a + b }
使用带关联类型的 trait 做限界时,使用 Trait<Item=Type>
为关联类型指定一个具体类型,结果还是一个 trait,可以用它做限界或定义 trait object:
// 使用带关联类型的 trait 做限界:
// 1. 可以不指定关联类型,在使用时由编译器推导;
// 2. 可以为关联类型做进一步限界
// 3. 可以使用关联类型
fn collect_into_vec<I: Iterator>(iter: I) -> Vec<I::Item>
where I::Iterm: Debug {} // 对关联类型做进一步限界
// Iterator<Item=u8> 还是一个 trait,故:
// 1. 用作限界:
fn my_func<Iter: Iterator<Item=u8>>(iter: Iter) {}
// 2. 用作 trait object(需要将所有关联类型明确定义出来,否则编译时不能正确检查而报错)
fn dump(iter: &mut dyn Iterator<Item=String>) {}
struct EvenNumbers { count: usize, limit: usize, }
impl Iterator for EvenNumbers {
// 实现 trait 时必须为关联类型指定 `具体类型` ,该具体类型需要满足该关联类型的限界要求。
type Item = usize;
// trait 的函数/方法,通过 Self::Item 引用关联类型
fn next(&mut self) -> Option<Self::Item> {
if self.count > self.limit {
return None;
}
let ret = self.count * 2;
self.count += 1;
Some(ret)
}
}
关联常量 #
trait 支持关联常量,在定义 trait 时必须指定类型,可选的指定缺省值。在实现 trait 时指定常量值:
trait Float {
// 定义常量时必须指定类型(编译器不会自动推导 const 值类型),但是可以不指定缺省值
const ZERO: Self;
const ONE: Self;
}
trait Greet{
// 定义关联常量的类型和缺省值
const GRETING: &'static str = "Hello",
fn greet(&self) -> String;
}
impl Float for f32 {
const ZERO: f32 = 0.0; // 实现 trait 时,为关联常量设置常量值
const ONE: f32 = 1.0;
}
trait 限界 #
使用 trait bound 的场景:
- 泛型参数限界:
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 类型
实现泛型 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> 中指定 GenericVal 类型的所有泛型参数
impl<T> GenericVal<T> {}
fn main() {
// 创建泛型类型的值时,可以不指定泛型参数 T,由编译器自动推导
let y = GenVal { gen_val: 3i32 };
println!("{}, {}", x.value(), y.value());
}
struct Container(i32, i32);
trait Contains<A, B> {
fn contains(&self, _: &A, _: &B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
// 不需要使用 impl<i32, i32>
impl Contains<i32, i32> for Container {
fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
fn first(&self) -> i32 { self.0 }
fn last(&self) -> i32 { self.1 }
}
为 trait 定义关联函数或方法,或为自定义类型实现 trait 时,该 trait 或 type 必须是本 crate 定义的类型,这个限制称为 Orphan Rule
,这是为了避免破坏开发者原来的定义。
impl<T>
指定 trait 或自定义类型使用的所有泛型参数,其中自定义类型也可以是泛型参数类型(blanket implement),如 impl<T, U> MyTrait<T> for U {};
。所以,Rust 为泛型 trait 放宽了 Orphan Rule 条件限制 :可以为其它 crate package 中定义的 trait 添加方法,或为自定义类型实现 trait ,这种实现称为 blanket impl
。这样可以批量对已知或未知的类型实现 trait。
// 泛型 trait
trait DoubleDrop<T> {
fn double_drop(self, _: T);
}
// 为 U 实现泛型 trait,U 本身也是泛型参数,所以相当于为所有 Sized 类型实现了 DoubleDrop trait。
impl<T, U> DoubleDrop<T> for U {
fn double_drop(self, _: T) {}
}
// 为所有 &T 或 &mut T 实现 Deref<Target = T> trait:https://doc.rust-lang.org/src/core/ops/deref.rs.html#84
impl<T: ?Sized> Deref for &T {
type Target = T;
#[rustc_diagnostic_item = "noop_method_deref"]
fn deref(&self) -> &T {
*self
}
}
impl<T: ?Sized> Deref for &mut T {
type Target = T;
fn deref(&self) -> &T {
*self
}
}
super trait #
trait 可以有多个父 trait,在实现该 trait 的同时也必须实现这些父 trait,父 trait 也支持 lifetime 限界。
trait Person {
fn name(&self) -> String;
}
// 等效于 trait Student where Self: Person
trait Student: Person {
fn university(&self) -> String;
}
trait Programmer {
fn fav_language(&self) -> String;
}
// 使用 + 运算符指定多个父 trait:
trait CompSciStudent: Programmer + Student {
fn git_username(&self) -> String;
}
// 为父 trait 指定 lifetime 限界
trait Circle<'a> : Shape + 'a {
fn radius(&self) -> f64;
}
完全限定方法调用 #
类型可以实现不同 crate package 中的 trait,它们可以有相同或不同的方法,所以在该类型对象上调用 trait 方法时,必须先将该 trait 引入作用域,否则 Rust 不知道该方法是哪一个 trait 的实现。
std::prelude::v1 moudle
中的类型和 trait 会被自动导入到所有 std Rust 程序中,如 std::convert::{AsRef, AsMut, Into, From}
等,所以在调用 into()/from()/as_ref()/as_mut()
方法前不需要手动导入这些 trait。
// write_all() 方法是 std::io::Write trait 定义的,调用它前需要引入 Write trait。
use std::io::Write
let mut buf: Vec<u8> = vec![];
buf.write_all(b"hello")?;
trait Pilot {
fn fly(&self);
}
trait Wizard {
fn fly(&self);
}
struct Human;
impl Pilot for Human {
fn fly(&self) {
println!("This is your captain speaking.");
}
}
impl Wizard for Human {
fn fly(&self) {
println!("Up!");
}
}
impl Human {
fn fly(&self) {
println!("*waving arms furiously*");
}
}
fn main() {
let person = Human;
// 调用 Human 自身实现的 fly() 方法。
person.fly();
// 调用实现的 trait 的方法,这里为了避免歧义,使用的是 TraitName::method(object).
Pilot::fly(&person); // 等效于完全限定方法调用:<Human as Pilot>::fly(&person)
Wizard::fly(&person);
}
TraitName::method(object)
调用方式只适合于方法而非关联函数:它们没有 self 参数,Rust 不能推导出调用哪一个类型实现的该关联函数,这时需要使用完全限定方法调用。
完全限定语法(Fully Qualified Syntax),如 <Human as Pilot>::fly(&person)
,明确指定对象 person 所属的类型 Human 要调用的 Pilot trait 的 fly 方法。
如果类型实现的多个 trait 有同名方法而且这些 trait 都在作用域,则需要使用完全限定语法来指定要调用那个 trait 的方法实现,用于解决命名冲突或混淆的问题:
trait CalSum {
fn sum(&self, a: i32) -> i32;
}
trait CalSumV2 {
fn sum(&self, a: i32) -> i32;
}
#[derive(Clone, Copy, Debug)]
struct S1(i32);
impl CalSum for S1 {
fn sum(&self, a: i32) -> i32 {
self.0 + a
}
}
impl CalSumV2 for S1 {
fn sum(&self, a: i32) -> i32 {
self.0 + a
}
}
fn main() {
let s1 = S1(0);
// 调用指定 trait 的方法
println!("Results: {}", CalSum::sum(&s1, 1));
println!("Results: {}", CalSumV2::sum(&s1, 1));
// ERROR: S1 同时实现了 CalSum 和 CalSumV2, 它们都提供了 sum() 方法, Rust 不确定该调用哪一个。
//println!("Results: {}", s1.sum(1)); // Error: multiple `sum` found
// 使用完全限定方法调用:调用 s1 对象对应的 S1 类型实现的 CalSum trait 的 sum 方法或关联函数。
println!("Results: {}", <S1 as CalSum>::sum(&s1, 1)); // OK
}
在没有歧义的情况下, 可以直接调用对象实现的 trait 方法, 而不需要使用完全限定调用方式:
let i: i8 = Default::default();
let (x, y): (Option<String>, f64) = Default::default();
let (a, b, (c, d)): (i32, u32, (bool, bool)) = Default::default();
#[derive(Default)]
struct SomeOptions {
foo: i32,
bar: f32,
}
fn main() {
// 如果 SomeOptions 没有实现除 Default 外的其它 trait 的 default() 方法,
// 则 <SomeOptions as Default>::default() 可以简写为 Default::default().
let options: SomeOptions = Default::default();
let options = SomeOptions { foo: 42, ..Default::default() };
}
trait object #
参考:https://quinedot.github.io/rust-learning/dyn-trait.html
dyn TraitName
称为 trait object
,它是一个类型,表示实现 TraitName 的任意类型对象。
trait object 类型和泛型参数的差别:
- 对于泛型参数,编译器推断并绑定一具体类型,如 Vec<T: Display> , 只能保存一种实现 Display trait 的类型元素;
- 对于 trait object,不和某一类型绑定,如
Vec<&dyn Display>
可以保存各种实现Display trait
的类型对象;
trait object 可以指定 trait 或 lifetime 做限界,但有如下限制:
- 除了第一个 trait 外,剩余用作限界的 trait 只能是 auto trait 类型,包括:Send, Sync, Unpin, UnwindSafe 和 RefUnwindSafe,但不包含 ?Sized,Hash,Eq 等 trait。
- 最多指定一个 lifetime;
// Rust 2021 前版本 dyn 关键字是可选的,2021 版本开始 dyn 关键字是必选的。
dyn Trait
dyn Trait + Send
dyn Trait + Send + Sync
dyn Trait + 'static
dyn Trait + Send + 'static
dyn Trait + Send + Sync + 'static
dyn (Trait)
dyn Trait + 'a
dyn eviction::EvictionManager + Sync
trait object 的第一个 trait 必须是对象安全(object safe)的,即满足如下要求:
- Trait 方法不返回 Self;
- Trait 方法没有任何泛型参数;
这是由于 trait object 在编译时是不知道自己具体类型的,而是在运行时动态派发。所以编译时,如果方法返回 Self,则编译器不能正确检查返回值合法性。同理,由于在运行时才能确定 trait object 的具体对象类型,所以在编译阶段是不能确认泛型参数的对应的实现类型,所以编译器也不能正确检查。
解决办法:修正 Self 类型,如 fn splice(&self, other: &Self) -> Self
修正为 fn splice(&self, other: &dyn MyTrait) -> Box<dyn MyTrait>
trait object 在编译时类型和大小不确定, 需要使用 &dyn TraitName
或 Box<dyn TraitName>
表示, 它们都是胖指针,包含两个字段:指向动态对象内存的指针 + 该对象实现 trait 定义的各种方法的虚表(vtable) 的指针。
&dyn TraitName
使用实现 TraitName 的对象引用赋值:let n: &dyn TraitName = &value
Box<dyn TraitName>
使用 Box::new(value) 赋值:let b: Box<dyn TraitName> = Box::new(value)
- 不能直接将 value 赋值给 Box
- 不能直接将 value 赋值给 Box
使用 &dyn Trait
时,如果要指定 Send/lifetime 等,则需要使用 &mut (dyn Trait + Send + 'a)
格式,否则 Rust 报错加号有歧义:
// 使用 & 时必须将 dyn 和后续的限制用括号括起来,否则会因歧义而报错。
&(dyn MyTrait + Send)
// error: expected expression, found keyword `dyn`
let b = &'a dyn MyTrait + Send + 'static;
// 表达式不能指定 'lifetime, 'lifetime 只能用于函数或方法的签名中。
// error: borrow expressions cannot be annotated with lifetimes
// let b = &'a (dyn MyTrait + Send + 'static);
// error: borrow expressions cannot be annotated with lifetimes
// printf_hello(Some(&'static my_struct));
当参数类型是 &Box<dyn Trait>
时,不能传入 &Box::new(my_struct)
,Rust 报错类型不匹配。两个解决办法:
- 将参数类型修改为
Box<dyn Trait>
, 后续可以直接传入Box::new(my_struct)
; - 先创建一个
Box<dyn Triat>
的对象 b,然后再通过&b
赋值;
trait MyTrait {
fn say_hello(&self);
}
struct MyStruct(String);
impl MyTrait for MyStruct {
fn say_hello(&self) {
println!("hello {}", self.0);
}
}
fn printf_hello(say_hello: Option<&(dyn MyTrait + Send)>) {
// my_trait 是 &(dyn MyTrait + Send) 类型
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
fn printf_hellov2(say_hello: Option<&Box<dyn MyTrait + Send + 'static>>) {
// 错误:不能通过借用(共享或可变)来转移对象,my_trait 是 Box<dyn MyTrait + Send + 'static> 类型,转移了 Box 对象所有权
// error[E0507]: cannot move out of `*say_hello` as enum variant `Some` which is behind a shared reference
// if let Some(&my_trait) = say_hello {
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
fn printf_hellov3(say_hello: Option<Box<dyn MyTrait + Send + 'static>>) {
if let Some(my_trait) = say_hello {
my_trait.say_hello();
}
}
fn main() {
let my_struct = MyStruct("abc".to_string());
// expected `&dyn MyTrait`, found `MyStruct`
// printf_hello(Some(my_struct));
// OK
printf_hello(Some(&my_struct));
// error[E0308]: mismatched types,expected `&Box<dyn MyTrait + Send>`,
// found `&Box<MyStruct>`
// printf_hellov2(Some(&Box::new(my_struct)));
// 解决办法:先创建一个 Box 对象,然后再借用,赋值时,通过 unsized type coercion 隐式自动类型转换。
let st1: Box<dyn MyTrait + Send + 'static> = Box::new(my_struct);
printf_hellov2(Some(&st1));
let my_struct2 = MyStruct("def".to_string());
printf_hellov3(Some(Box::new(my_struct2)));
}
使用 Box 保存 trait object 时, 有三种标准格式, Rust 标准库为这三种格式定义了对应的方法和函数:
Box<dyn TraitName + 'static>
Box<dyn TraitName + Send + 'static>
Box<dyn TraitName + Sync + Send + 'static>
Fn/FnMut/FnOnce
也是 trait,故上面的 TraitName 或 trait object 也可以是闭包,如: Box<dyn Fn(int32) -> int32>
struct Sheep {}
struct Cow {}
trait Animal {
fn noise(&self) -> &'static str;
}
impl Animal for Sheep {
fn noise(&self) -> &'static str {
"baaaaah!"
}
}
impl Animal for Cow {
fn noise(&self) -> &'static str {
"moooooo!"
}
}
// 返回一个实现 Animal trait 的 Box 对象,编译期间不感知具体的实现类型
fn random_animal(random_number: f64) -> Box<dyn Animal> {
if random_number < 0.5 {
Box::new(Sheep {})
} else {
Box::new(Cow {})
}
}
创建 Box<dyn MyTrait>
时,不能使用 Box::<dyn MyTrait>::new(value)
,因为 Box new()
方法是定义在 impl <T> Box<T>
上的, T 没有任何 trait bound, 所以 T 默认是 Sized, 而 dyn MyTrait 是 unsized 的, 所以编译报错。
解决办法:在返回值中指定 Box::<dyn MyTrait>
类型, 让 Rust 编译器自动转换。
let val: Box<dyn Any + 'a> = Box::new(*val); // OK
// function or associated item cannot be called on `Box<dyn Any>` due to unsatisfied trait bounds,
// 115 | pub trait Any: 'static {
// | ---------------------- doesn't satisfy `dyn std::any::Any: std::marker::Sized`
// let val: Box<dyn Any + 'a> = Box::<dyn Any + 'a>::new(*val);
// Box::<&dyn Any>::new() 是 OK 的, 因为 &dyn Any 是 Sized 对象.
// let val: Box<&dyn Any> = Box::<&dyn Any>::new(&val);
trait object 缺省 lifetime #
dyn Trait
的 lifetime 规则:
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());
}
两个 dyn trait 的 trait、lifetime 如果相同则类型互为别名,如:dyn Trait + Send + UnwindSafe
和 dyn Trait + UnwindSafe + Send
相同。
trait object 的 lifetime 消除规则:
下面这些 T1 到 T7 都是将 trait object 用作 type argument, 所以先检查对应 containing type 的 lifetime 来推导 trait object 的 lifetime。主要是 3 条规则:
- 如果 containing type 没有 lifetime,如 T1/T2,则使用 ‘static;
- 如果 containing type 只指定了一个 lifetime, 则使用对应的 lifetime,如 T3/T4/T5/T6;
- 如果 containing type 有多个 lifetime,则必须为 trait object 明确指定 liftime,如 T7;
trait Foo { }
// 下面两个类型等效
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;
// 这两个 impl 也是等效(dyn 是可选的)
impl dyn Foo {}
impl dyn Foo + 'static {}
// 下面两个类型等效,因为 &'a T 意味着 T: 'a
// 首先检查 containing type 是否有 lifetime,如果有的话且唯一,则使用它。
// 这里的 containing type 指的是 &'a dyn Foo 中的 &'a.
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);
// 下面两个类型等效,因为 std::cell::Ref<'a, T> 内部要求 T: 'a
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
// T 的 lifetime 需要同时满足 'a + 'b, 所以应该比 'a 和 'b 都长
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
f1: &'a i32,
f2: &'b i32,
f3: T,
}
// Error: the lifetime bound for this object type cannot be deduced from context
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
// trait object 只能指定一个 'lifetime
//
// error[E0226]: only a single explicit lifetime bound is permitted
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo + 'a + 'b>;
// OK
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo + 'a>;
// OK
type T7<'a, 'b, 'c> = TwoBounds<'a, 'b, dyn Foo + 'c>;
// 表示实现 Bar trait 类型的对象 lifetime 不能长于 'a
trait Bar<'a>: 'a { }
// 下面两种类型相同
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// 下面两种类型相同
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
// For the following trait...
trait Bar<'a>: 'a { }
// ...these two are the same:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;
// ...and so are these:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}
如果 trait object 不是作为 type argument,而用于赋值,则使用对应目标类型的 trait bound 来决定 trait object 的 lifetime:
- 如果目标 trait 只有一个 lifetime, 则使用它;
- 如果目标没有 lifetime,则使用 ‘static 或从 expression 推导;
impl trait #
impl TraitName 或 &impl TraintName
和泛型类型参数类似, 但在编译时被实例化为一种特定类型, 不支持运行时动态派发。
它的主要使用场景是简化泛型参数约束的复杂性,如函数返回一个复杂的多种嵌套迭代器时,该类型可能只有编译器才能准确写出来,这时可以用 impl TraitName
来简化返回参数类型。如,将 iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>>
简化为
impl Iterator<Item=i32>
。
它可以作为函数输入参数和返回值类型,但是不能作为泛型参数的限界(因为 impl trait 被编译器实例化为具体类型而不是 trait 类型):
// impl trait 作为函数参数的类型时,不需要引入泛型函数。
fn print_it1(input: impl Debug + 'static ) {
println!( "'static value passed in is: {:?}", input );
}
// 等效为泛型参数版本,这个版本的优势是实例化时可以指定 T 的类型,但是 impl Debug 不行。
fn print_it<T: Debug + 'static>(input: T) {
println!( "'static value passed in is: {:?}", input );
}
use std::iter;
use std::vec::IntoIter;
fn combine_vecs_explicit_return_type(v: Vec<i32>, u: Vec<i32>,
) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
v.into_iter().chain(u.into_iter()).cycle()
}
// 等效为
fn combine_vecs(v: Vec<i32>, u: Vec<i32>, ) -> impl Iterator<Item=i32> {
v.into_iter().chain(u.into_iter()).cycle()
}
作为函数参数或返回值时,一般需要指定 ‘static lifetime:
trait App {}
struct AppRegistry {
apps: HashMap<String, Box<dyn App>>,
}
impl AppRegistry {
fn new() -> AppRegistry {
Self { apps: HashMap::new() }
}
// impl App 必须加加 'static,否则 Box::new(app) 报错:
// the parameter type `impl App` may not live long enough,the parameter type `impl App` must be valid for the static lifetime...
// 这是因为 Box::new(app) 会转移 app 的所有权,所以 app 必须是 'static
fn add(&mut self, name: &str, app: impl App + 'static) {
self.apps.insert(name.to_string(), Box::new(app));
}
}
由于 impl Trait 是一种编译时静态派发形式,在编译时只能被单态化为一种类型,否则报错:
// 错误:不兼容的类型
fn make_shape(shape: &str) -> impl Shape{
match shape {
"circle" => Circle::new(),
"tiangle" => Triangle::new(),
"shape" => Retangle::new(),
}
}
由于 Fn/FnOnce/FnMut 也是 trait,所以函数可以返回 impl Fn() -> i32
或 Box<dyn Fn() -> i32>
:
fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
let closure = move |x: i32| { x + y };
closure
}
fn main() {
let plus_one = make_adder_function(1);
assert_eq!(plus_one(2), 3);
}
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1) // 堆分配空间,性能差些
}
fn returns_closure() -> impl Fn(i32) -> i32 {
|x| x + 1 // 栈分配,性能好
}
Sized/?Sized #
Rust 为所有固定大小类型都自动实现了 std::marker::Sized trait
, 用户不能自己为类型实现该 trait,它是一个标记 trait。它的唯一用途是作为泛型类型的限界,如 T: Sized
表示 T 类型在编译时必须为大小已知(固定)的类型。
Sized 是 Rust 的隐式默认值,即 struct S<T>
的 T 未加任何限界, 则 Rust 解释为 struct S<T: Sized>
。
struct S<T: ?Sized>
, 表示 T 可以是无固定大小的类型。
Rust 的动态大小(unsized)类型:
- str;
- [T];
- dyn trait;
对于动态大小类型,Rust 不能将它们作为变量或函数参数,而是需要转换为固定大小的引用或 Box(它们都是 2 个 usize 的胖指针),如:
- &str, Box
; - &[T], Box<[T]>;
- &dyn trait, Box
;
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 长字段来使用 '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(marker 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, 如 &str; - 数组
[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
实现 Copy 方式: derive 宏自动实现(bit-copy)或手动实现:
// OK: i32 实现了 Copy
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
// 错误: Vec<T> 没有实现 Copy
#[derive(Copy, Clone)]
struct PointList {
points: Vec<Point>,
}
// OK: 因为 &T 实现了 Copy, 即使 T 没有实现 Copy, &T 也实现了 Copy
#[derive(Copy, Clone)]
struct PointListWrapper<'a> {
point_list_ref: &'a PointList,
}
// 或者
struct MyStruct;
impl Copy for MyStruct { }
impl Clone for MyStruct {
fn clone(&self) -> MyStruct {
*self
}
}
Default #
按惯例, 自定义类型使用 new() 关联函数来作为类型的 constructor。Rust 并没有强制所有类型都实现该方法, 但它提供了 Default trait, 各类型可以实现该 trait 提供的 default() -> Self 方法:
pub struct Second {
value: u64,
}
impl Second {
pub fn value(&self) -> u64 {
self.value
}
}
impl Default for Second {
fn default() -> Self {
Self { value: 0 }
}
}
Rust 基本类型和 &str, &CStr, &OsStr, Box
如果各成员类型都实现了 Default,则可以使用 derive macro 来为自定义类型实现 Default:
use std::{path::PathBuf, time::Duration};
#[derive(Default, Debug, PartialEq)]
struct MyConfiguration {
output: Option<PathBuf>,
search_path: Vec<PathBuf>,
timeout: Duration,
check: bool,
}
impl MyConfiguration {
}
fn main() {
// 调用 Default trait 的 default() 方法来创建对象.
let mut conf = MyConfiguration::default();
conf.check = true;
println!("conf = {conf:#?}");
// struct 对象部分输出化, 没有指定的 field 都使用 default() 方法的值来填充.
let conf1 = MyConfiguration {
check: true,
..Default::default()
};
assert_eq!(conf, conf1);
}
// 另一个例子
#[derive(Default)]
struct SomeOptions {
foo: i32,
bar: f32,
}
fn main() {
let options: SomeOptions = Default::default();
// 自动根据目标类型调用对应实现的方法, 等效于 <SomeOptions as Default>::default()
}
对于 enum 类型, 需要明确定义哪一个 enum variant 是 default:
#[derive(Default)]
enum Kind {
#[default]
A,
B,
C,
}
// 或者
enum Kind {
A,
B,
C,
}
impl Default for Kind {
fn default() -> Self { Kind::A }
}
Default trait 作为泛型类型的定界, 在标准库中得到广泛使用, 例如:
- 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()
来手动释放对象,后续 Rust 不会再自动释放它。
fn bar() -> Result<(), ()> {
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
println!("exit");
}
}
let _exit = Foo;
baz()?;
Ok(())
}
如果类型实现了 Drop,则不能再实现 Copy。
From/Into #
From/Into trait 的方法都会转移对象所有权到结果对象中:
trait Into<t>: Sized {
fn into(self) -> T;
}
trait From<T>: Sized {
fn from(other: T) -> Self;
}
自定义类型一般只需实现 From trait,Rust 会自动生成 Into trait:
use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
fn main() {
let num = Number::from(30);
println!("My number is {:?}", num);
}
Into trait 一般作用在泛型函数的限界场景中:
pub fn stdout<T: Into<Stdio>>(&mut self, cfg: T) -> &mut Command
From trait 在以下场景自动调用:
-
Into trait
为任何实现了From<T>
的类型,自动实现Into<U>
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 迭代:
pub trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
// Required method
fn into_iter(self) -> Self::IntoIter;
}
Extend #
std::iter::Extend trait
用于消费传入的迭代器对象,将元素插入到自身:
pub trait Extend<A> {
// Required method
fn extend<T>(&mut self, iter: T) where T: IntoIterator<Item = A>;
// Provided methods
fn extend_one(&mut self, item: A) { ... }
fn extend_reserve(&mut self, additional: usize) { ... }
}
Rust 各种集合类型都实现了 Extend trait
:
let mut message = String::from("The first three letters are: ");
message.extend(&['a', 'b', 'c']);
assert_eq!("abc", &message[29..32]);
Extend 和 std::iter::FromIterator 类似,但后者会创建新的集合,而 Extend 扩充自己。
AsRef/AsMut #
AsRef/AsMut
多用于泛型参数限界,如:open()
的实现依赖于 &Path
,通过限定 P: AsRef<Path>
,在 open()
内部就可以通过 P.as_ref()
方法调用返回 &Path
类型对象:
fn open<P: AsRef<Path>>(path: P) -> Result<file>
标准库类型,如 String/str/OsString/OsStr/PathBuf/Path
等实现了 AsRef<Path>
, 所以这些类型对象都可以作为 open()
的参数:
// https://doc.rust-lang.org/src/std/path.rs.html#3170
impl AsRef<Path> for String {
#[inline]
fn as_ref(&self) -> &Path {
Path::new(self)
}
}
String 实现了 AsRef<str>, AsRef<[u8]>, AsRef<Path>
。
运算符重载/PartialEq/PartialOrd/Eq/Ord #
Operator | Trait |
---|---|
+ | Add |
- | Sub |
* | Mul |
% | Rem |
== and != | PartialEq |
<, >, <=, and >= | PartialOrd |
PartialEq
包含 eq/ne()
方法,PartialOrd
只需要实现 partial_cmp()
方法:
- Eq 是 PartialEq 的子 trait;
- Ord 是 PartialOrd + Eq 的子 trait;
- PartialOrd 是 PartialEq 的子 trait;
Rust 为绝大部分基本类型实现了这些 trait:
pub trait PartialEq<Rhs = Self> where Rhs: ?Sized,
{
// Required method
fn eq(&self, other: &Rhs) -> bool;
// Provided method
fn ne(&self, other: &Rhs) -> bool { ... }
}
pub trait Eq: PartialEq { }
// PartialOrd 是 PartialEq 的子 trait
pub trait PartialOrd<Rhs = Self>: PartialEq<Rhs> where Rhs: ?Sized,
{
// Required method
fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
// Provided methods
fn lt(&self, other: &Rhs) -> bool { ... }
fn le(&self, other: &Rhs) -> bool { ... }
fn gt(&self, other: &Rhs) -> bool { ... }
fn ge(&self, other: &Rhs) -> bool { ... }
}
// 实现 Ord 的同时需要实现:Eq 和 PartialOrd 以及 PartialEq
pub trait Ord: Eq + PartialOrd {
// Required method
fn cmp(&self, other: &Self) -> Ordering;
// Provided methods
fn max(self, other: Self) -> Self where Self: Sized
fn min(self, other: Self) -> Self where Self: Sized
fn clamp(self, min: Self, max: Self) -> Self where Self: Sized + PartialOrd
}
可以使用 derive macro 来自动生成 PartialEq/PartialOrd 的实现:
#[derive(PartialEq)]
struct Ticket {
title: String,
description: String,
status: String
}
也可以手动实现 PartialEq/PartialOrd trait:
#[derive(PartialEq)]
enum BookFormat {
Paperback,
Hardback,
Ebook,
}
#[derive(PartialEq)]
struct Book {
isbn: i32,
format: BookFormat,
}
// PartialEq 是泛型 trait,可以在实现时指定泛型参数的类型
impl PartialEq<BookFormat> for Book {
fn eq(&self, other: &BookFormat) -> bool {
self.format == *other
}
}
impl PartialEq<Book> for BookFormat {
fn eq(&self, other: &Book) -> bool {
*self == other.format
}
}
fn main() {
let b1 = Book { isbn: 1, format: BookFormat::Paperback };
let b2 = Book { isbn: 2, format: BookFormat::Paperback };
assert!(b1 == BookFormat::Paperback);
assert!(BookFormat::Paperback == b2);
}
std::ops module
下的各 trait 用以支持运算符重载:
use std::ops;
struct Foo;
struct Bar;
#[derive(Debug)]
struct FooBar;
#[derive(Debug)]
struct BarFoo;
impl ops::Add<Bar> for Foo {
type Output = FooBar;
fn add(self, _rhs: Bar) -> FooBar {
println!("> Foo.add(Bar) was called");
FooBar
}
}
impl ops::Add<Foo> for Bar {
type Output = BarFoo;
fn add(self, _rhs: Foo) -> BarFoo {
println!("> Bar.add(Foo) was called");
BarFoo
}
}
fn main() {
println!("Foo + Bar = {:?}", Foo + Bar);
println!("Bar + Foo = {:?}", Bar + Foo);
}
// 实现泛型 trait
impl<L, R> Add<Complex<R>> for Complex<L> where: L: Add<R>,
{
type Output = Complex<L::Output>;
fn add(self, rhs: Complex<R>) -> Self::Output {
Complex{
re: self.re + rhs.re,
im: self.im + rhs.im,
}
}
}
其它运算符:
i..j, i.., ..j, i..=j
等:是RangeXX struct
类型语法糖;?
:适用于 Result 和 Option,和 From/Into trait 协作进行自动类型转换;*val
:使用 Deref/DerefMut trait 进行重载;
Index/IndexMut #
类型实现了 std::ops::Index/IndexMut trait
后,可以使用 a[i]
操作来获取和设置值。
a[i]
调用 a.index(i)
方法后再自动解引用,所以等效于 *a.index(i) 或 *a.index_mut(i)
。返回一个对象的好处是可以作为左值使用,例如 a[i] = 3
。
Rust 编译器根据 a[i]
索引表达式所在的上下文自动选择调用 index() 或 index_mut()
方法。
Index/IndexMut trait
对 i 的类型没有限定,可以是 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 #
Borrow<T>
可以从自身创建一个 &T
,但要求 &T
类型必须和自身类型以相同的方式进行哈希和比较。Rust 编译器并不会强制检查该限制,但是 Borrow 有这种约定的意图。
Borrow 用于解决泛型哈希表和其它关联集合类型的情况:K: Borrow<Q>
表示可以从 K 对象生成 &Q
,K 和 Q 都是使用相同的 Eq + Hash
语义,所以可以给 HashMap 的 get() 方法传入任何满足上面两个约束的引用对象。
- get() 方法的实现从自身 K.borrow() 生成 &Q 对象,然后用 K 的 Eq+Hash 值与 Q 的 Eq+Hash 值进行比较;
- self 一般是 owned 类型, 如 String/PathBuf/OsString/Box/Arc/Rc 等, borrow 的结果是对应的 unsized 类型的借用, 如 &str/&Path/&OsStr/&T;
impl<K,V> HashMap<K, V> where K: Eq + Hash
{
// key 必须是借用类型 &Q,而且需要 K 实现了 Borrow<Q>
fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V> where K: Borrow<Q>, Q: Eq + Hash
// ...
}
String 实现了 Borrow
// 从 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
即可。例如 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;
}
}
}
// slice 元素都大于 0,所以不会 clone
let slice = [0, 1, 2];
let mut input = Cow::from(&slice[..]); // 保存 &[i32]
abs_all(&mut input);
// slice 有小于 0 的元素,所以会被 clone
let slice = [-1, 0, 1];
let mut input = Cow::from(&slice[..]);
abs_all(&mut input);
// 不会 clone,因为 input 保存的就是 owned 类型 Vec<i32>
let mut input = Cow::from(vec![-1, 0, 1]);
abs_all(&mut input);
// 在 struct 中使用 Cow 的例子
struct Items<'a, X>
where [X]: ToOwned<Owned = Vec<X>>
{
values: Cow<'a, [X]>,
}
impl<'a, X: Clone + 'a> Items<'a, X>
where [X]: ToOwned<Owned = Vec<X>>
{
fn new(v: Cow<'a, [X]>) -> Self {
Items { values: v }
}
}
let readonly = [1, 2];
let borrowed = Items::new((&readonly[..]).into());
match borrowed {
Items { values: Cow::Borrowed(b) } => println!("borrowed {b:?}"),
_ => panic!("expect borrowed value"),
}
let mut clone_on_write = borrowed;
clone_on_write.values.to_mut().push(3);
println!("clone_on_write = {:?}", clone_on_write.values);
match clone_on_write {
Items { values: Cow::Owned(_) } => println!("clone_on_write contains owned data"),
_ => panic!("expect owned data"),
}
Cow 的语义是 potentially owned
,即可能拥有所有权,可以用来避免一些不必要的拷贝。比如保存函数内临时对象的引用:
fn foo(s: &str, some_condition: bool) -> &str {
if some_condition {
// 错误:在 block 结束时临时对象被释放,临时对象的引用无效
&s.replace("foo", "bar")
} else {
s
}
}
// 如果把返回值改成 String,那么在 else 分支会有一次额外的拷贝。
//
// 这时可以使用 Cow,避免内存拷贝。
fn foo(s: &str, some_condition: bool) -> Cow<str> {
if some_condition {
Cow::from(s.replace("foo", "bar")) // 保存 Owned 的 String 对象
} else {
Cow::from(s) // 保存 &str
}
}
Deref/DerefMut #
Defer 主要使用场景是智能指针(smart pointer),例如 t=Box<U>
,可以对 t 进行 *t
和 &t
操作:
*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;
}
在需要对象的 &mut 引用时,如方法调用或变量赋值时,Rust 检查对象是否实现了 DerefMut trait
,如果实现了,则自动调用(mutable deref coercion) 它来实现转换。 所以 *v
作为左值的场景,Rust 使用 DerefMut<Target=U>
来对 * 操作符
进行重载,相当于用生成 U 类型对象来进行赋值。
对于实现了 Deref<Target=U>
的类型 T 值, &*T 返回 &U
,也就是 Deref 重载了 * 运算符
, 所以 *T == *t.deref()
, 返回 U 对象, 为了获得 &U 可以使用表达式 &*T
;
Rust 自动调用 Deref trait
来满足赋值或传参类型的隐式转换,但是如果函数的参数是泛型限界,则 Rust 不会通过 Deref trait
来解引用来满足泛型参数限界的要求。两种解决办法:
- 使用
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,实际执行的操作为:
- 在需要
&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]);
// 以下两个方法调用等价,但是建议使用第二种方式: 全限定名称的方法或关联函数调用
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<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
。
use std::rc::Rc;
let mut x = Rc::new(3);
*Rc::get_mut(&mut x).unwrap() = 4;
assert_eq!(*x, 4);
let _y = Rc::clone(&x);
assert!(Rc::get_mut(&mut x).is_none());
let mut data = Rc::new(5);
*Rc::make_mut(&mut data) += 1;
let mut other_data = Rc::clone(&data);
// 由于 data 被 clone 过, 再次调用 data 的 make_mut 时会 clone。
// 一个新 Rc 对象并替换 data,这时 other_data 的引用计数 -1 变为 1, data 的引用计数也为 1
*Rc::make_mut(&mut data) += 1; // Clones inner data
// data 是刚才 clone 生成的新对象, 计数为 1, 所以这次不会再 clone
*Rc::make_mut(&mut data) += 1; // Won't clone anything
// other_data 引用计数为 1, 所以也不会 clone
*Rc::make_mut(&mut other_data) *= 2; // Won't clone anything
// Now `data` and `other_data` point to different allocations.
assert_eq!(*data, 8);
assert_eq!(*other_data, 12);
pub fn into_inner(this: Rc<T, A>) -> Option<T>
: 消耗传入的 Rc(其他类型的 into_inner() 方法都是类似的语义), 如果 Rc 只有一个强引用, 则返回 inner value, 否则返回 None。
各种包装器类型,如 Box<T>,Cell<T>,RefCell<T>
, 都提供了 into_inner() 方法,如:
// pub fn into_inner(self) -> T
use std::cell::Cell;
let c = Cell::new(5);
let five = c.into_inner();
assert_eq!(five, 5);
使用 Rc/Arc
可创建出一些循环引用的数据结构,导致 Rc/Arc 管理的对象引用计数 strong_count() 不为 0,从而发生内存泄露:
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match self {
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
// Uncomment the next line to see that we have a cycle;
// it will overflow the stack
// println!("a next item = {:?}", a.tail());
// block 结束时, b 被 drop 后 strong_count() 为 1,
// 所以堆内存不会被释放, a 也是同样的情况.
}
解决办法:使用 Rc::downgrade() 而非 Rc::clone() 来增加对象弱引用计数 weak_count(), 而不会增加 strong_count(), 返回一个 Weak
Weak
- weak_count() 记录 Rc 对象的 Weak
对象数量。 - Rc
并不会在执行清理时要求 weak_count() 为 0; - 一旦强引用计数为 0,任何由弱引用组成的循环就会被打破,因此
弱引用不会造成循环依赖
; - 由于 Weak
引用的值可能会被释放,可以使用 weak.upgrade() 方法来验证 weak 指向的值是否存在,返回一个 Option<Rc >, 在 Rc 值存在时返回 Some, 否则返回 None.
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
{
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
);
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
Arc 是线程安全的 Rc:
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
let val = Arc::new(AtomicUsize::new(5));
for _ in 0..10 {
let val = Arc::clone(&val);
thread::spawn(move || {
let v = val.fetch_add(1, Ordering::Relaxed);
println!("{v:?}");
});
}
Pin/Unpin/pin!()/.await #
Pin 常用场景是 std::future::Feature trait
的 poll
方法:由于 Future trait object
必须保存执行上下文中 stack 上的变量,而 Future 下一次被 wake 执行的时机是不定的,所以为了避免 Future 对象上保存的 stack 变量的地址发生变化导致引用出错,需要将 Future 对象设置为 Pin<&mut Self>
类型,这样该对象将不能被转移,从而确保内部保存的栈地址有效。
pub trait Future {
type Output;
// Required method
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
#![feature(noop_waker)]
use std::future::Future;
use std::task;
let waker = task::Waker::noop();
let mut cx = task::Context::from_waker(&waker);
let mut future = Box::pin(async { 10 });
assert_eq!(future.as_mut().poll(&mut cx), task::Poll::Ready(10));
Unpin 是标准库提供的 marker trait
, 它是一个标记类型。类型实现 Unpin trait 意味着该类型不提供任何 Pin 语义,即该类型对象可以安全的移动(move),即不需要 Pin 提供的保护语义。
如果类型实现了 Unpin,则可以从 Pin 中安全地获取可变引用:
fn example<T: Unpin>(x: Pin<&mut T>) {
let mut_ref: &mut T = Pin::into_inner(x); // 安全, 因为 T: Unpin
}
Rust 为几乎所有类型都实现了 Unpin。但例外的情况如下:
- Future:因为实现 Future trait object 可能包含对自身引用或保存指针类型的栈变量, 移动它可能会使这些地址指向失效。
- 包括 async/await 语法糖展开后的 Future 类型;
- Pin
:Pin 本身就是为了处理不能移动的数据而设计的,Pin
对于大多数 P 类型都不会实现 Unpin;
- PhantomPinned:标记 trait,用来显式标记类型不应该实现 Unpin:
struct MyType { _pin: PhantomPinned }
Pin<T>
是一个 struct std::pin::Pin
struct 类型,用于确保被包装的对象不能被移动。
创建 Pin 对象的几种方式:
- Pin 到栈上:
let mut data=43; let pinned = Pin::new(&mut data);
- Pin 到堆上:
let boxed = Box::new(42); let pinned = Pin::new(boxed);
- 直接在堆上创建 Pin:
let pinned = Box::pin(42);
使用 Pin::new(T)
函数来为 T 创建 Pin 类型值,要求 T 必须实现 Unpin trait
, 参数可以是 future 对象或者 &mut T
:
- 不能对
!Unpin
类型,如Future
对象调用Pin:new()
,而需要使用Box::pin()
或pin!()
; - 如果在 non_std 环境创建 Pin 对象或则 T 没有实现 Unpin,则需要使用
pin!()
宏;
Pin<Ptr>
类型的方法:
- pinned.as_ref():返回新的 Pin 对象
Pin<& impl Future<Output=xx>>
- pinned.as_mut(): 返回新的 Pin 对象
Pin<&mut impl Future<Output=xx>>
- pinned.get_ref(): 返回
&T
- pinned.get_mut(): 返回
&mut T
//impl<Ptr> Pin<Ptr> where Ptr: Deref, <Ptr as Deref>::Target: Unpin,
pub fn new(pointer: Ptr) -> Pin<Ptr>
pub fn into_inner(pin: Pin<Ptr>) -> Ptr
//impl<Ptr> Pin<Ptr> where Ptr: Deref,
pub unsafe fn new_unchecked(pointer: Ptr) -> Pin<Ptr>
pub fn as_ref(&self) -> Pin<&<Ptr as Deref>::Target>
//impl<Ptr> Pin<Ptr> where Ptr: DerefMut,
pub fn as_mut(&mut self) -> Pin<&mut <Ptr as Deref>::Target>
pub fn set(&mut self, value: <Ptr as Deref>::Target) where <Ptr as Deref>::Target: Sized,
//impl<'a, T> Pin<&'a T> where T: ?Sized,
pub fn get_ref(self) -> &'a T
//impl<'a, T> Pin<&'a mut T> where T: ?Sized,
pub fn into_ref(self) -> Pin<&'a T>
pub fn get_mut(self) -> &'a mut T where T: Unpin,
示例:
use std::pin::Pin;
let mut unpin_future = std::future::ready(5);
// 传入的是 &mut T,T 需要实现 Unpin
let my_pinned_unpin_future: Pin<&mut _> = Pin::new(&mut unpin_future);
// 如果要 Pin<Box<T>>, 则需要使用 Box::pin(value) 函数
async fn add_one(x: u32) -> u32 {
x + 1
}
let fut = add_one(42);
let pinned_fut: Pin<Box<_>> = Box::pin(fut);
// 如果要 Pin<Box<dyn Trait>> ,则需要使用 Box::into_pin(value) 函数
async fn add_one(x: u32) -> u32 {
x + 1
}
fn boxed_add_one(x: u32) -> Box<dyn Future<Output = u32>> {
Box::new(add_one(x))
}
let boxed_fut = boxed_add_one(42);
let pinned_fut: Pin<Box<_>> = Box::into_pin(boxed_fut);
Pin<Ptr>
实现了 Deref/DerefMut trait
,所以可以调用 Ptr 指向的类型的方法:
impl<Ptr> Deref for Pin<Ptr> where Ptr: Deref,
type Target = <Ptr as Deref>::Target
fn deref(&self) -> &<Ptr as Deref>::Target
impl<Ptr> DerefMut for Pin<Ptr> where Ptr: DerefMut, <Ptr as Deref>::Target: Unpin,
fn deref_mut(&mut self) -> &mut <Ptr as Deref>::Target
Pin<Ptr>
实现了 Future
,但要求 Ptr 是 &mut impl Future
类型,例如 pin!(future)
返回的对象:
impl<P> Future for Pin<P> where P: DerefMut, <P as Deref>::Target: Future
type Output = <<P as Deref>::Target as Future>::Output
fn poll( self: Pin<&mut Pin<P>>, cx: &mut Context<'_>, ) -> Poll<<Pin<P> as Future>::Output>
Pin 在异步编程中得到广泛应用:
- Future 没有实现 Unpin,因为 async/await 生成的 Future 可能包含自引用结构;
- 编译器将 async 函数转换为状态机时, 会产生包含内部引用的结构;
调用 async fn
返回一个 Future
类型值,它没有实现 Unpin(也就是实现了 !Unpin
), 在对它进行 poll 前,需要显式将值 Pin。
对 Future 对象调用 .await
表达式,用于对 Future 对象进行 poll,直到获得 Ready 值。编译器自动对 future 对象进行 Pin(通过获得 future 对象的 &mut 引用),然后调用它的 poll() 方法(同时也会消耗 future 对象):
类似的,tokio 等异步运行时也会自动对 Future 对象进行 Pin 操作:
use futures::executor::block_on;
async fn hello_world() {
println!("hello, world!");
}
fn main() {
let future = hello_world();
// block_on() 自动对 future 进行 Pin 操作。
block_on(future);
}
// block_on() 的一种简化实现方式:
use waker_fn::waker_fn; // Cargo.toml: waker-fn = "1.1"
use futures_lite::pin; // Cargo.toml: futures-lite = "1.11"
use crossbeam::sync::Parker; // Cargo.toml: crossbeam = "0.8"
use std::future::Future;
use std::task::{Context, Poll};
fn block_on<F: Future>(future: F) -> F::Output {
let parker = Parker::new();
let unparker = parker.unparker().clone();
let waker = waker_fn(move || unparker.unpark());
let mut context = Context::from_waker(&waker);
// 为 future 创建一个 Pin 对象
pin!(future);
loop {
// future.as_mut() 返回
match future.as_mut().poll(&mut context) {
Poll::Ready(value) => return value,
Poll::Pending => parker.park(),
}
} }
注意:.await 不支持对 Future 对象的 &mut 借用进行 poll,这是因为 Future 的 poll() 方法的 self 签名是 self: Pin<&mut Self>
async fn my_async_fn() {
// async logic here
}
#[tokio::main]
async fn main() {
let mut future = my_async_fn();
// .await 不支持 &mut Future 的自动 Pin
(&mut future).await;
}
需要显式使用 core::pin::pin!(T)
将 Future 对象 T Pin 住,返回 Pin<&mut T>
对象,然后对 &mut pinned_future
进行 .await
:
- 不能使用 pin::new(T),因为要求 T 必须实现 Unpin,而 Future 对象没有实现 Unpin;
- 如果 Pin
的 Ptr 实现了 Future,则 Pin 也实现了 Future, 所以可以 .await;
如果 T 没有实现 Unpin(如 Future 对象),则该宏将 T 值 pin 到内存中(通过 &mut T 可变借用的方式),不允许 move。
如果 T 实现了 Unpin,则 Pin<&mut T>
效果和 &mut
类似,mem::replace() 或 mem::take() 可以 move 该值。
pin!(future)
宏的实现方式等效于 std::pin::Pin::new(&mut future)
, 它存入 future 对象的 &mut 借用
(所以 future 类型需要是 mut 类型),返回一个 Pin<Ptr>
类型的同名对象,其中 Ptr 是 &mut impl Future<Output=xx>
类型:
- pinned.as_ref() 方法返回
Pin<&<Ptr as Deref>::Target>
即Pin<& impl Future<Output=xx>>
类型; - pinned.as_mut() 方法返回
Pin<&mut <Ptr as Deref>::Target>
即Pin<&mut impl Future<Output=xx>>
类型;
// 下面代码 Ok
#[tokio::main]
async fn main() {
let future = my_async_fn();
pin!(future);
// future 类型是 Pin<Ptr> 其中的 Ptr 是 &mut future 的结果,即 &mut impl Future.
//
// &mut Pin<Ptr>
(&mut future).await;
}
-
pin!() 参数必须是 Future 对象,而不是能是表达式:
async fn my_async_fn() { // async logic here } #[tokio::main] async fn main() { // 错误 // let mut future = pin!(my_async_fn()); // OK let mut future = my_async_fn() pin!(future); (&mut future).await; }
-
pin!{} 支持同时创建多个 Future 对象并 Pin 住:
use tokio::{pin, select}; async fn my_async_fn() { // async logic here } #[tokio::main] async fn main() { pin! { let future1 = my_async_fn(); let future2 = my_async_fn(); } select! { _ = &mut future1 => {} _ = &mut future2 => {} } }
在 loop+select!{}
语句中,要对 Future 对象进行重复 poll,而 select!{}
在并发 .await 时,如果有返回值,则会 drop 其它 branch 的 Future 对象,所以在 select branch 中不能直接使用 future 对象,以避免它被 drop 后下一次 loop 失败 select 失败。
解决办法是:
- 创建一个 mut 类型的 Future 对象;
- 使用
pin!(future)
来创建一个同名,但类型是Pin<&mut impl Future<Output=xx>>
的 Pin 对象, 它也实现了 Future; - 在 select branch 中使用
&mut future
来进行轮询。
async fn action() {
// Some asynchronous logic
}
#[tokio::main]
async fn main() {
let (mut tx, mut rx) = tokio::sync::mpsc::channel(128);
let operation = action(); // 返回一个 Future 对象, 没有对它 .await
// 必须先 Pin 该 Future 对象,创建一个同名的类型为 Pin<&mut impl Future<Output=i32> 类型对象 ;
tokio::pin!(operation);
loop {
tokio::select! {
// &mut operation 类型是 &mut Pin<&mut impl Future<Output=i32> 被 .await 表达式所支持, 所以可以被 poll 轮询。
//
// 当该 branch 因不匹配或无返回值而被 drop/cancel 时,operation 任然有效,下一次 select!{} 可以接续使用 &mut operation
_ = &mut operation => break,
Some(v) = rx.recv() => {
if v % 2 == 0 {
break;
}
}
}
}
}
除了 &mut pinned_future
外,还可以调用 pinned_future.as_mut()
方法来创建一个 Pin<&mut impl Future<Output=xx>>
类型对象(注意不是 &mut XX 借用对象),满足 impl<P> Future for Pin<P> where P: DerefMut, <P as Deref>::Target: Future
中对 P 的要求,所以它也实现了 Future,可以被 poll:
use std::{
future::Future,
pin::pin,
task::{Context, Poll},
thread,
};
fn block_on<Fut: Future>(fut: Fut) -> Fut::Output {
let waker_that_unparks_thread = {/*.*/};
let mut cx = Context::from_waker(&waker_that_unparks_thread);
// Pin the future so it can be polled.
// 为 fut 显式的创建 Pin 对象, 类型为 Pin<&mut impl Future<Output=xx>>
let mut pinned_fut = pin!(fut);
loop {
// as_mut() 返回实现 Future 的 Pin<&mut impl Future<Output=xx>> 对象, 可以被 poll
match pinned_fut.as_mut().poll(&mut cx) {
Poll::Pending => thread::park(),
Poll::Ready(res) => return res,
}
}
}
Send/Sync #
Send 和 Sync 都是标记 trait:
- Send:对象可以在多个线程中转移(也就是对象在多线程间转移具有原子性);
- Sync:可以在多个线程中共享引用对象;
thread 一般使用 move 闭包,这样该闭包才能实现 Send+'static
。对于 scope thread 使用的闭包函数,可以不使用 move,它们可以共享引用父 thread 的 stack 变量,所以要求这些对象必须实现 Sync。
绝大部分 Rust 类型,如 Vec/Map/Array 等实现了 Send/Sync,可以被 move 到 thread closure 中。
自定义类型的各成员如果实现了 Send/Sync,则该类型也自动实现了 Send/Sync。
没有实现 Send/Sync 的类型:
Rc<T>
:没有实现 Send 和 Sync(多线程转移 Rc 对象时,不能保证原子性),不能被转移到 thread closure 中。
- 多线程环境中需要使用 Arc
和它的 clone() 后的新对象;
Cell/RefCell/mpsc:Receiver
:没有实现 Sync,不能在多个线程中共享引用;std::sync::MutexGurad
:没实现 Send;