Trait 基础
Trait 项(Item)
Self
Self 总是指代实现类型。
trait Trait {// returns emplementing typefn return_self() -> Self;}
函数(Function)
Trait 函数是指第一个参数不是 self 关键字的任意函数。
trait Default {// functionfn default()-> Self;}
Trait 函数可以通过 trait 或者实现类型的命名空间来调用。
fn main(() {let zero: i32 = Default::default();let zero = i32::default();}
方法(Method)
Trait 方法是指,第一个参数使用了 self 关键字并且 self 关键字的类型是 self , &self, &mut self 之一。 self 的类型也可以被 Box , Rc , Arc , Pin 来包装。
trait Trait {fn take_self(self);//...// 去语法糖后为// fn take_self(self: Self);}// example from standard libararytrait ToString {fn to_string(&self) -> String;}
Trait 方法可以通过在实现类型上使用点(.)操作符来调用。
fn main() {let five = 5.to_string();}
此外,trait 方法还可以像函数那样由 trait 或者实现类型通过命名空间来调用。
fn main() {let five = ToString::to_string(&5);let fiv = i32::to_string(&5);}
关联类型(Associated Types)
Trait 可以有关联类型。当我们需要再函数签名中使用 Self 以外的某个类型,但是希望这个类型可以由实现着来选择而不是硬编码到 trait 声明中,这时关联类型就可以发挥作用了。
trait Trait {type AssociatedType;fn func(arg: Self::AssociatedType);}struct SomeType;struct OtherType;// any type implementing Trait can// choose the type of AssociatedTypeimpl Trait for SomeType {type AssociatedType = i8; // chooses i8fn func(arg: Self::AssociatedType) {}}impl Trait for OtherType {type AssociatedType = u8; // chooses u8fn func(arg: Self::AssociatedType) {}}fn main() {SomeType::func(-1_i8); // can only call func with i8 on SomeTypeOtherType::func(1_u8); // can only call func with u8 on OtherType}
泛型参数(Generic Parameters)
“泛型参数”繁殖泛型类型参数(gereric type parameters)、泛型生命周期参数(generic lifetime parameters)、以及泛型常量参数(generic const parameters)。因为这些说起来比较拗口,所以人们通常把它们简称为“泛型类型(generic type)”、“生命周期(lifetime)”和“泛型常量(generic const)”。由于我们将要讨论的所有标准库 trait 中国都没有使用泛型常量,所以它们不再本文的讨论范围之内。
我们可以使用参数来对比一个 triat 生命进行泛化(generalize)。
// trait declaration generalized with lifetime & type parameterstrait Trait<'a, T> {// signature uses generic typefn func1(arg: T);// signature uses lifetimefn func2(arg: &'a i32);// signature uses generic type & lifetimefn func3(arg: &'a T);}struct SomeType;impl<'a> Trait<'a, i8> for SomeType {fn func1(arg: i8) {}fn func2(arg: &'a i32) {}fn func3(arg: &'a i8) {}}impl<'b> Trait<'b, u8> for SomeType {fn func1(arg: u8){}fn func2(arg: &'b i32){}fn func3(arg: &'b u8){}}
泛型可以具有默认值,最常用的默认值是 Self ,但是任何类型都可以作为默认值。
// make T = Self by defaulttrait Trait<T = Self> {fn func(t: T) {}}// any type can be used as the defaulttrait Trait2<T = i32> {fn func2(t: T) {}}struct SomeType;//omitting the generic type will cause the impl to// use the default value, which is Self hereimpl Trait for SomeType {fn func(t: SomeType) {}}// default value here is i32impl Trait2 for SomeType {fn func2(t: i32) {}}// the default is overridable as we'd expectimpl Trait<String> for SomeType {fn func(t: String) {}}
上面是对 trait 进行参数化,还可以对单个函数和方法进行参数化。
trait Trait {fn func<'a, T>(t: &'a T);}
泛型类型 VS 关联类型
泛型类型和关联类型都把在 trait 的函数和方法使用哪种具体类型的决定权交给了实现者。
通常的经验法则是:
- 当每个类型只应该有 trait 的一个实现时,使用关联类型;
- 当每个类型可能会有 trait 的多个实现时,使用泛型类型;
一个普通的函数相加的示例:
trait Add {type Rhs;type Output;fn add(self, rhs: Self::Rhs) -> Self::Output;}struct Point {x: i32,y: i32,}impl Add for Point {type Ths = Point;type Output = Point;'fn add(self, rhs: Point) -> Point{Point {x: self.x + rhs.x,x: self,y + rhs.y,}}}fn main() {let p1 = Point {x: 1, y: 1};let p2 = Point {x: 2, y: 2};let ps = p1.add(p2);assert_eq!(p3.x, 3);assert_eq!(p3.y, 3);}
增加功能:把单个的数分别加到 Point 的 x 和 y。
当再次实现 impl Add for Point 就会出现错误,属于重复声明实现。所以这个时候必须将关联类型重构为泛型类型,这样就可以根据 Rhs 不同的类型参数来为 Point 实现 trait 多次。
trait Add<Rhs> {type Output;fn add(self, rhs: Rhs) -> Self::Output;}struct Point {x: i32,y: i32,}impl Add<Point> for Point {type Output = Self;fn add(self, rhs: Point) -> Self::Output {Self {x: self.x + rhs.x,x: self.y + rhs.y,}}}impl Add<i32> for Point {type Output = Self;fn add(self, rhs: i32) -> Self::Output {Self {x: self.x + rhs,x: self.y + rhs,}}}fn main() {let p1 = Point { x: 1, y: 1 };let p2 = Point { x: 2, y: 2 };let p3 = p1.add(p2);assert_eq!(p3.x, 3);assert_eq!(p3.y, 3);let p1 = Point { x: 1, y: 1 };let int2 = 2;let p3 = p1.add(int2); // ✅assert_eq!(p3.x, 3);assert_eq!(p3.y, 3);}
增加新功能:增加一个 Line 的新类型,两个 Point 相加之后应该产生一个 Line 。需要将 Output 从关联类型重构为泛型类型来实现这个需求。
trait Add<Rhs, Output> {fn add(self, rhs: Rhs) -> Output;}struct Point {x: i32,y: i32,}impl Add<Point, Point> for Point {fn add(self, rhs: Point) -> Point {Self {x: self.x + rhs.x,y: self.y + rhs.y,}}}impl Add<i32, Point> for Point {fn add(self, rhs: i32) -> Point {Point {x: self.x + rhs,y: self.y + rhs,}}}struct Line {start: Point,end: Point,}impl Add<Point, Line> for Point {fn add(self, rhs: Point) -> Line {Line {start: self,end: rhs,}}}
作用域
只有当 trait 在作用域之中时,trait 项才能被使用。Read 和 Write 的 trait 并不在标准库的 prelude 中。
use std::fs::File;use std::io;fn main() -> Result<(), io::Error> {let mut file = File::open("Cargo.toml")?;let mut buffer = String::new();// ❌ read_to_string not found in Filefile.read_to_string(&mut buffer)?;}
read_to_string 声明与 std::io::Read 中并且被 std::fs::File 结构体实现,但是要想调用它, std::io::Read 必须在当前作用域中。
use std::io;use std::fs::File;use std::io::Read;fn main() -> Result<(), io::Error> {let mut file = File::open("Cargo.toml")?;let mut buffer = String::new();file.read_to_string(&mut buffer)?;println!("{}", buffer);Ok(())}
标准库预置(The standard library prelude)是标准库中的一个模块,也就是说,std::prelude::v1,它在每个其他模块的顶部被自动导入,即use std::prelude::v1::*。这样的话,下面这些 trait 就总会在作用域中,我们不需要自己显式地导入它们,因为它们是预置的一部分。
- AsMut
- AsRef
- Clone
- Copy
- Default
- Drop
- Eq
- Fn
- FnMut
- FnOnce
- From
- Into
- ToOwned
- IntoIterator
- Iterator
- PartialEq
- PartialOrd
- Send
- Sized
- Sync
- ToString
-
派生宏(Derive Macros)
标准库导出了一小部分派生宏,这些派生宏可以让我们便捷地在一个类型上实现 trait,前提是该类型的所有成员都实现了这个 trait。派生宏以它们实现的 trait 来命名。
Clone
- Copy
- Debug
- Default
- Eq
- Hash
- Ord
- PartialEq
- PartialOrd
使用示例:
#[derive(Copy, Clone)]struct SomeType;
注意:派生宏也是过程宏(procedural macros),它们可以被用来做任何事情,没有强制规定它们必须要实现一个 trait,或者它们只能在所有成员都实现 trait 的情况下才能工作,这些只是标准库中派生宏所遵循的惯例。
默认实现(Default Impls)
Trait 可以为它们的函数和方法提供默认实现。
impl Trait {fn method(&self) {println!("default impl");}}
如果 trait 中的某些方法是完全通过 trait 的另一些方法来实现的,这就非常方便了。
trait Greet {fn greet(&self, name: &str) -> String;fn greet_loudly(&self, name: &str) -> String {self.greet(name) + "!"}}struct Hello;struct Hola;impl Greet for Hello {fn greet(&self, name: &str) -> String {format!("Hello {}", name)}// use default impl for greet_loudly}impl Greet for Hola {fn greet(&self, name: &str) -> String {format!("Hola {}", name)}// override default implfn greet_loudly(&self, name: &str) -> String {let mut greeting = self.greet(name);greeting.insert_str(0, "¡");greeting + "!"}}fn main() {println!("{}", Hello.greet("John")); // prints "Hello John"println!("{}", Hello.greet_loudly("John")); // prints "Hello John!"println!("{}", Hola.greet("John")); // prints "Hola John"println!("{}", Hola.greet_loudly("John")); // prints "¡Hola John!"}
泛型覆盖实现(Generic Blanket Impls)
泛型覆盖实现是一种在泛型类型而不是具体类型上的实现。为整数类型实现一个 is_even 方法开始。
trait Even {fn is_even(self) -> bool {}}impl Even for i8 {fn is_even(self) -> bool {self % 2_i8 == 0_i8}}impl Even for u8 {fn is_even(self) -> bool {self % 2_u8 == 0_u8}}impl Even for i16 {fn is_even(self) -> bool {self % 2_i16 = 0_i16}}
通过使用泛型来覆盖实现重复逻辑:
use std::fmt::Debug;use std::convert::TryInto;use std::ops::Rem;trait Even {fn is_even(self) -> bool;}// generic blanket implimpl<T> Even for TwhereT: Rem<Output = T> + ParticalEq<T> + Sized,u8: TryInto<T>,<u8 as TryInto<T>>::Error: Debug,{fn is_even(self) -> bool {self % 2.try_into().unwrap() == 0.try_into().unwrap()}}// 不可以再为其他类型实现,下面的实现时错误的// impl Even for u8{}
trait 一致性是指,对于任意给定的类型,最多存在某一 trait 的一个实现。Rust 用来强制执行特质一致性的规则。
Subtraits & Supertraits
subtrait中的sub指的是子集(subset),supertrait中的super指的是超集(superset)。如果我们有下面这个 trait 声明:
trait Subtrait: Supertrait {}
所有实现了Subtrait的类型是所有实现了Supertrait的类型的子集,
上面的代码是一种语法糖,展开为:
trait Substrait where Self: Supertriat {}
这是一个微妙而重要的区别,要明白约束在Self上,也就是实现Subtrait的类型而非Subtrait自身。后者也没有意义,因为 trait 约束只能作用于能够实现 trait 的具体类型,trait 本身不能实现其他的 trait:
trait Supertrait {fn method(&self) {println!("in supertrait");}}trait Subtrait: Supertrait {// this looks like it might impl or// override Supertrait::method but it does notfn method(&self) {println!("in subtrait")}}struct SomeType;impl Supertrait for SomeType {}impl Subtrait for SomeType {}fn main() {SomeType.method(); // ❌ ambiguous method call<SomeType as Supertrait>::method(&st); // ✅ prints "in supertrait"<SomeType as Subtrait>::method(&st); // ✅ prints "in subtrait"}
对于一个类型如何同时实现一个 subtrait 和一个 supertrait,也没有明确的规则。
trait Supertrait {fn super_method(&mut self);}trait Subtrait: Supertrait {fn sub_method(&mut self);}struct CallSuperFromSub;impl Supertrait for CallSuperFromSub {fn super_method(&mut self) {println!("in super");}}impl Subtrait for CallSuperFromSub {fn sub_method(&mut self) {println!("in sub");self.super_method();}}struct CallSubFromSuper;impl Supertrait for CallSubFromSuper {fn super_method(&mut self) {println!("in super");self.sub_method();}}impl Subtrait for CallSubFromSuper {fn sub_method(&mut self) {println!("in sub");}}struct CallEachOther(bool);impl Supertrait for CallEachOther {fn super_method(&mut self) {println!("in super");if self.0 {self.0 = false;self.sub_method();}}}impl Subtrait for CallEachOther {fn sub_method(&mut self) {println!("in sub");if self.0 {self.0 = false;self.super_method();}}}fn main() {CallSuperFromSub.super_method(); // prints "in super"CallSuperFromSub.sub_method(); // prints "in sub", "in super"CallSubFromSuper.super_method(); // prints "in super", "in sub"CallSubFromSuper.sub_method(); // prints "in sub"CallEachOther(true).super_method(); // prints "in super", "in sub"CallEachOther(true).sub_method(); // prints "in sub", "in super"}
subtrait 和 supertrait 之间可以是很复杂的关系。在介绍能够将这些复杂性进行整洁封装的心智模型之前,让我们快速回顾并建立我们用来理解泛型类型上的 trait 约束的心智模型。
fn function<T: Clone>(t: T) {// impl}
合理的猜测, t.clone() 会在某个时候被调用,因为当一个泛型类型被一个 trait 所约束时,意味着它对 trait 有依赖性。泛型与 trait 约束之间关系的心智模型是一个简单而直观的模型:泛型依赖于 trait 约束。
Copy 的trait 声明:
trait Copy: Clone {}
但是 Copy 完全不依赖于 Clone,之前的模型在这里没有帮助。个人认为,理解 subtrait 与 supertrait 最为简洁优雅的心智模型是:subtrait 细化(refine)了他们的supertrait。
“细化(Refinement)”刻意保持一定的模糊性,因为它们在不同的上下文环境中会有不同的含义:
- subtrait 可能会使得 supertrait 的方法实现更为具体,快速,占用更少的内存,例如:
Copy: Clone; - subtrait 可能会对 supertrait 的方法实现增加额外的保证,例如:
Eq: PartialEq,Ord: PartialOrd,ExactSizeIterator: Iterator; - subtrait 可能会使得 supertrait 的方法更为灵活和易于调用,例如:
FnMut: FnOnce,Fn: FnMut。 - subtrait 可能会扩展 supertrait 并添加新的方法,例如:
DoubleEndedInterator: Iterator,ExactSizeIterator: Iterator。Trait 对象
泛型给我提供了编译器多态,而 trait 对象给我提供了运行时多态。我们可以使用 trait 对象来让函数在运行时动态地返回不同的类型。
Trait 对象还允许我们在集合中存储多种类型: ```rust use std::f64::consts::PI;fn example(condition: bool, vec: Vec<i32>) -> Box<dyn Iterator<Item = i32>> {let iter = vec.into_iter();if condition {// Has type:// Box<Map<IntoIter<i32>, Fn(i32) -> i32>>// But is cast to:// Box<dyn Iterator<Item = i32>>Box::new(iter.map(|n| n * 2))} else {// Has type:// Box<Filter<IntoInter<i32>, Fn(&i32) -> bool>>// But is cast to:// Box<dyn Iterator<Item = i32>>Box::new(iter.filter<|&n| n >= 2))}}
struct Circle { radius: f64, }
struct Square { side: f64, }
trait Shape { fn area(&self) -> f64; }
impl Shape for Circle { fn area(&self) -> f64 { PI self.radius self.radius } }
impl Shape for Square { fn area(&self) -> f64 { self.side * self.side } }
fn get_total_area(shapes: Vec
fn example() {
let shapes: Vec
assert_eq!(PI + 1.0, get_total_area(shapes));
}
trait对象是没有大小的,所以它们必须总是在一个指针后面。我们可以根据类型中 `dyn` 关键字的存在来区分具体类型和 trait 对象在类型级别上的区别。```ruststruct Struct;trait Trait {}// regular struct&StructBox<Struct>Rc<Struct>Arc<Struct>// trait objects&dyn TraitBox<dyn Trait>Rc<dyn Trait>Arc<dyn Trait>
不是所有的 trait 都可以被转换成 trait 对象。当且仅当一个 trait 满足下面这些要求时,它才是对象安全的(obect-safe):
- trait 不要求
Self:Sized - trait 的所有方法都是对象安全的
当一个 trait 方法满足下面的要求时,该方法是对象安全的:
- 方法要求
Self:Sized或者 - 方法在其接受者位置仅使用一个
Self类型
理解为什么要求是这样的,与本文的其余部分无关,但如果你仍然好奇,可以阅读Sizeness in Rust
标记 Trait(Market Traits)
标记 trait 是不含 trait 项的 trait。它们的工作把实现类型“标记(mark)”为具有某种属性,否则就没有办法在类型系统中去表示。
// Impling PartialEq for a tye promises// that eqality for the type has these properties:// - symmetry: a == b implies b == a,and// - transitivity: a == b && b == a implies a == c // 传递性// But does not promise this property:// - reflexivity: a == a // 自反性trait PartialEq {fn eq(&self, other: &Self) -> bool;}// Eq has no trait items! The eq method is already// declared by PartialEq, but "impling" Eq// for a type promises this additional equality property:// - reflexivity: a == atrait Eq: PartialEq {}// f64 impls PartialEq but not Eq because NaN != NaN// i32 impls PartialEq & EQ because there's no NaNs :)
自动 Trait(Auto Trait)
自动 Trait 是指如果一个类型的所有成员都实现了该 trait,该类型就会自动实现该 trait。“成员(member)”的含义取决于类型,例如:结构体的字段、枚举的变量、数组的元素、元组的项,等等。
所有的自动 trait 都是标记 trait,但不是所有的标记 trait 都是自动 trait。自动 trait 必须是标记 trait,所以编译器可以为它们提供一个自动的默认实现,如果它们有任何 trait 项,这就不可能实现了。
自动 trait 的例子。
// implemented for types which are safe to send between threadsunsafe auto trait Send {}// implemented for types whose references are safe to send between threadsunsafe auto trait Sync {}
不安全 Trait(Unsafe Trait)
trait 可以被标记为 unsafe,以表明实现该 trait 可能需要 unsafe 代码。Send和Sync都被标记为 unsafe,因为如果它们不是自动实现的类型,就意味着它必须包含一些非Send或非Sync的成员,如果我们想手动标记类型为Send和Sync,作为实现者我们必须格外小心,确保没有数据竞争。
