首页 > 其他分享 >Rust 标准库 Trait 指南

Rust 标准库 Trait 指南

时间:2024-03-14 13:56:17浏览次数:33  
标签:指南 impl Point Trait self let Rust trait fn

部分内容来自 Rust 2021 年期刊

内容目录

  • 引言
  • Trait 基础
  • 自动 Trait
  • 泛型 Trait
  • 格式化 Trait
  • 操作符 Trait
  • 转换 Trait
  • 错误处理
  • 迭代器 Trait
  • I/O Trait
  • 总结

引言

你是否曾想过下面这些 trait 有什么不同?

  • Deref<Traget=T>AsRef<T>,以及Borrow<T>
  • CloneCopy,和ToOwned
  • From<T>Into<T>?
  • TryFrom<&str>FromStr
  • FnOnceFnMutFnfn?

或者你曾问过自己下面这些问题:

  • “我在 trait 中,什么时候使用关联类型(associated type),什么时候使用泛型(generic types)?”

  • “什么是泛型覆盖实现(generic blanket impls)”?

  • “subtrait 和 supertrait 是如何工作的?”

  • “为什么这个 trait 没有任何方法?”

那么这篇文章就是为你而写的!它回答了包括但不限于上述所有的问题。我们将一起对 Rust 标准库中所有最流行和最常用的 trait 进行快速的浏览。

你可以按章节顺序阅读本文,也可以跳到你最感兴趣的 trait,因为每个 trait 章节的开头都有一个指向前置章节的链接列表,你应该阅读这些链接,以便有足够的背景知识来理解当前章节的解释(译注:很抱歉,译文中暂时无法提供链接跳转)。

Trait 基础

我们将会覆盖足够多的基础知识,这样文章的其余部分就可以精简,而不必因为它们在不同的 trait 中反复出现而重复解释相同的概念。

Trait 项(Item)

Trait 项是指包含于 trait 声明中的任意项。

Self

Self总是指代实现类型。

trait Trait {
    // always returns i32
    fn returns_num() -> i32;

    // returns implementing type
    fn returns_self() -> Self;
}

struct SomeType;
struct OtherType;

impl Trait for SomeType {
    fn returns_num() -> i32 {
        5
    }

    // Self == SomeType
    fn returns_self() -> Self {
        SomeType
    }
}

impl Trait for OtherType {
    fn returns_num() -> i32 {
        6
    }

    // Self == OtherType
    fn returns_self() -> Self {
        OtherType
    }
}

函数(Function)

Trait 函数是指第一个参数不是self关键字的任意函数。

trait Default {
    // function
    fn default() -> Self;
}

Trait 函数可以通过 trait 或者实现类型的命名空间来调用。

fn main() {
    let zero: i32 = Default::default();
    let zero = i32::default();
}

方法(Method)

Trait 方法是指,第一个参数使用了self关键字并且self的类型是Self,&Self&mut Self之一。self的类型也可以被BoxRcArcPin来包装。

trait Trait {
    // methods
    fn takes_self(self);
    fn takes_immut_self(&self);
    fn takes_mut_self(&mut self);

    // above methods desugared
    fn takes_self(self: Self);
    fn takes_immut_self(self: &Self);
    fn takes_mut_self(self: &mut Self);
}

// example from standard library
trait 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 five = 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 AssociatedType

impl Trait for SomeType {
    type AssociatedType = i8; // chooses i8
    fn func(arg: Self::AssociatedType) {}
}

impl Trait for OtherType {
    type AssociatedType = u8; // chooses u8
    fn func(arg: Self::AssociatedType) {}
}

fn main() {
    SomeType::func(-1_i8); // can only call func with i8 on SomeType
    OtherType::func(1_u8); // can only call func with u8 on OtherType
}

泛型参数(Generic Parameters)

“泛型参数”泛指泛型类型参数(generic type parameters)、泛型生命周期参数(generic lifetime parameters)、以及泛型常量参数(generic const parameters)。因为这些说起来比较拗口,所以人们通常把它们简称为 “泛型类型(generic type)”、“生命周期(lifetime)”和 “泛型常量(generic const)”。由于我们将要讨论的所有标准库 trait 中都没有使用泛型常量,所以它们不在本文的讨论范围之内。

我们可以使用参数来对一个 trait 声明进行泛化(generalize )。

// trait declaration generalized with lifetime & type parameters
trait Trait<'a, T> {
    // signature uses generic type
    fn func1(arg: T);

    // signature uses lifetime
    fn func2(arg: &'a i32);

    // signature uses generic type & lifetime
    fn 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 default
trait Trait<T = Self> {
    fn func(t: T) {}
}

// any type can be used as the default
trait 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 here
impl Trait for SomeType {
    fn func(t: SomeType) {}
}

// default value here is i32
impl Trait2 for SomeType {
    fn func2(t: i32) {}
}

// the default is overridable as we'd expect
impl Trait<String> for SomeType {
    fn func(t: String) {}
}

// overridable here too
impl Trait2<String> for SomeType {
    fn func2(t: String) {}
}

除了可以对 trait 进行参数化之外,我们还可以对单个函数和方法进行参数化。

trait Trait {
    fn func<'a, T>(t: &'a T);
}

泛型类型 vs 关联类型

泛型类型和关联类型都把在 trait 的函数和方法中使用哪种具体类型的决定权交给了实现者,因此这部分内容要去解释什么时候使用泛型类型,什么时候使用关联类型。

通常的经验法则是:

  • 当每个类型只应该有 trait 的一个实现时,使用关联类型。

  • 当每个类型可能会有 trait 的多个实现时,使用泛型类型。

比如说我们想要定义一个名为Add的 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 Rhs = Point;
    type Output = Point;
    fn add(self, rhs: Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

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

假设现在我们想要添加这样一种功能:把i32加到Point上,其中Point里面的成员xy都会加上i32

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 Rhs = Point;
    type Output = Point;
    fn add(self, rhs: Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

impl Add for Point { // ❌
    type Rhs = i32;
    type Output = Point;
    fn add(self, rhs: i32) -> Point {
        Point {
            x: self.x + rhs,
            y: 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);
}

上面的代码会抛出错误:

error[E0119]: conflicting implementations of trait `Add` for type `Point`:
  --> src/main.rs:23:1
   |
12 | impl Add for Point {
   | ------------------ first implementation here
...
23 | impl Add for Point {
   | ^^^^^^^^^^^^^^^^^^ conflicting implementation for `Point`

因为Add trait 没有被任何的泛型类型参数化,我们只能在每个类型上实现这个 trait 一次,这意味着,我们只能一次把RhsOutput类型都选取好!为了能够使Pointi32类型都能和Point相加,我们必须把Rhs从一个关联类型重构为泛型类型,这样就能够让我们根据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 {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

impl Add<i32> for Point { // ✅
    type Output = Self;
    fn add(self, rhs: i32) -> Self::Output {
        Point {
            x: self.x + rhs,
            y: 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,现在,在我们的程序中存在这样一种上下文环境,即将两个Point相加之后应该产生一个Line而不是另一个Point。这在当我们当前的Add trait 设计中是不可行的,因为Output是一个关联类型,但是我们通过把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 {
        Point {
            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,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 1 };
    let p2 = Point { x: 2, y: 2 };
    let p3: Point = p1.add(p2);
    assert!(p3.x == 3 && p3.y == 3);

    let p1 = Point { x: 1, y: 1 };
    let int2 = 2;
    let p3 = p1.add(int2);
    assert!(p3.x == 3 && p3.y == 3);

    let p1 = Point { x: 1, y: 1 };
    let p2 = Point { x: 2, y: 2 };
    let l: Line = p1.add(p2); // ✅
    assert!(l.start.x == 1 && l.start.y == 1 && l.end.x == 2 && l.end.y == 2)
}

所以,哪个Add trait 是最好的呢?这取决于你程序中的需求!放在合适的场景中,它们都很好。

作用域(Scope)

只有当 trait 在作用域之中时,trait 项才能被使用。大多数 Rustaceans 在第一次尝试写一个 I/O 相关的程序时,都会在吃过一番苦头之后了解到这一点,因为ReadWrite的 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();
    file.read_to_string(&mut buffer)?; // ❌ read_to_string not found in File
    Ok(())
}

read_to_string(buf: &mut String)声明于std::io::Read中并且被std::fs::File结构体实现,但是要想调用它,std::io::Read必须在当前作用域中。

use std::fs::File;
use std::io;
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)?; // ✅
    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
  • Ord

派生宏(Derive Macros)

标准库导出了一小部分派生宏,这么派生宏可以让我们可以便捷地在一个类型上实现 trait,前提是该类型的所有成员都实现了这个 trait。派生宏以它们所实现的 trait 来命名。

  • Clone
  • Copy
  • Debug
  • Default
  • Eq
  • Hash
  • Ord
  • PartialEq
  • PartialOrd

使用示例:

// macro derives Copy & Clone impl for SomeType
#[derive(Copy, Clone)]
struct SomeType;

注意:派生宏也是过程宏(procedural macros),它们可以被用来做任何事情,没有强制规定它们必须要实现一个 trait,或者它们只能在所有成员都实现 trait 的情况下才能工作,这些只是标准库中派生宏所遵循的惯例。

默认实现(Default Impls)

Trait 可以为它们的函数和方法提供默认实现。

trait Trait {
    fn method(&self) {
        println!("default impl");
    }
}

struct SomeType;
struct OtherType;

// use default impl for Trait::method
impl Trait for SomeType {}

impl Trait for OtherType {
    // use our own impl for Trait::method
    fn method(&self) {
        println!("OtherType impl");
    }
}

fn main() {
    SomeType.method(); // prints "default impl"
    OtherType.method(); // prints "OtherType 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 impl
    fn 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!"
}

标准库中的很多 trait 为很多它们的方法提供了默认实现。

泛型覆盖实现(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
    }
}

// etc

#[test] // ✅
fn test_is_even() {
    assert!(2_i8.is_even());
    assert!(4_u8.is_even());
    assert!(6_i16.is_even());
    // etc
}

很明显,上面的实现十分啰嗦。而且,所有我们的实现几乎都是一样的。此外,如果 Rust 决定在未来增加更多的整数类型,我们必须回到这段代码中,用新的整数类型来更新它。我们可以通过使用泛型覆盖实现来解决所有的问题。

use std::fmt::Debug;
use std::convert::TryInto;
use std::ops::Rem;

trait Even {
    fn is_even(self) -> bool;
}

// generic blanket impl
impl<T> Even for T
where
    T: Rem<Output = T> + PartialEq<T> + Sized,
    u8: TryInto<T>,
    <u8 as TryInto<T>>::Error: Debug,
{
    fn is_even(self) -> bool {
        // these unwraps will never panic
        self % 2.try_into().unwrap() == 0.try_into().unwrap()
    }
}

#[test] // ✅
fn test_is_even() {
    assert!(2_i8.is_even());
    assert!(4_u8.is_even());
    assert!(6_i16.is_even());
    // etc
}

不同于默认实现,泛型覆盖实现提供了方法的实现,所以它们不能被重写。

use std::fmt::Debug;
use std::convert::TryInto;
use std::ops::Rem;

trait Even {
    fn is_even(self) -> bool;
}

impl<T> Even for T
where
    T: Rem<Output = T> + PartialEq<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 { // ❌
    fn is_even(self) -> bool {
        self % 2_u8 == 0_u8
    }
}

上面的代码会抛出下面的错误:

error[E0119]: conflicting implementations of trait `Even` for type `u8`:
  --> src/lib.rs:22:1
   |
10 | / impl<T> Even for T
11 | | where
12 | |     T: Rem<Output = T> + PartialEq<T> + Sized,
13 | |     u8: TryInto<T>,
...  |
19 | |     }
20 | | }
   | |_- first implementation here
21 |
22 |   impl Even for u8 {
   |   ^^^^^^^^^^^^^^^^ conflicting implementation for `u8`

这些实现有重叠,因此它们是冲突的,所以 Rust 拒绝编译这段代码以确保 trait 的一致性。trait 一致性是指,对于任意给定的类型,最多存在某一 trait 的一个实现。Rust 用来强制执行特质一致性的规则,这些规则的含义,以及针对这些含义的变通方案都不在本文的讨论范围之内。

Subtraits & Supertraits

subtrait中的sub指的是子集(subset),supertrait中的super指的是超集(superset)。如果我们有下面这个 trait 声明:

trait Subtrait: Supertrait {}

所有实现了Subtrait的类型是所有实现了Supertrait的类型的子集,或者反过来讲:所有实现了Supertrait的类型是所有实现了Subtrait类型的子集。而且,上面的代码是一种语法糖,展开来应该是:

trait Subtrait where Self: Supertrait {}

这是一个微妙而重要的区别,要明白约束在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 not
    fn method(&self) {
        println!("in subtrait")
    }
}

struct SomeType;

// adds Supertrait::method to SomeType
impl Supertrait for SomeType {}

// adds Subtrait::method to SomeType
impl Subtrait for SomeType {}

// both methods exist on SomeType simultaneously
// neither overriding or shadowing the other

fn main() {
    SomeType.method(); // ❌ ambiguous method call
    // must disambiguate using fully-qualified syntax
    <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 {}

上面的语法看起来与在一个泛型类型上应用 trait 约束很相似,但是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 并添加新的方法,例如:DoubleEndedIterator: Iterator,ExactSizeIterator: Iterator

Trait 对象

泛型给我们提供了编译期多态,而 trait 对象给我们提供了运行时多态。我们可以使用 trait 对象来让函数在运行时动态地返回不同的类型。

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<IntoIter<i32>, Fn(&i32) -> bool>>
        // But is cast to:
        // Box<dyn Iterator<Item = i32>>
        Box::new(iter.filter(|&n| n >= 2))
    }
}

Trait 对象还允许我们在集合中存储多种类型:

use std::f64::consts::PI;

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<Box<dyn Shape>>) -> f64 {
    shapes.into_iter().map(|s| s.area()).sum()
}

fn example() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 1.0 }), // Box<Circle> cast to Box<dyn Shape>
        Box::new(Square { side: 1.0 }), // Box<Square> cast to Box<dyn Shape>
    ];
    assert_eq!(PI + 1.0, get_total_area(shapes)); // ✅
}

Trait 对象是没有大小的,所以它们必须总是在一个指针后面。我们可以根据类型中dyn关键字的存在来区分具体类型和 trait 对象在类型级别上的区别。

struct Struct;
trait Trait {}

// regular struct
&Struct
Box<Struct>
Rc<Struct>
Arc<Struct>

// trait objects
&dyn Trait
Box<dyn Trait>
Rc<dyn Trait>
Arc<dyn Trait>

不是所有的 trait 都可以被转成 trait 对象。当且仅当一个 trait 满足下面这些要求时,它才是对象安全的(object-safe):

  • trait 不要求Self:Sized
  • trait 的所有方法都是对象安全的

当一个 trait 方法满足下面的要求时,该方法是对象安全的:

  • 方法要求Self:Sized 或者
  • 方法在其接收者位置仅使用一个Self类型

理解为什么要求是这样的,与本文的其余部分无关,但如果你仍然好奇,可以阅读Sizeness in Rust(译注:Sizedness in Rust 这篇文章已翻译,可在公众号翻阅往期文章)。

标记 Trait(Marker Traits)

标记 trait 是不含 trait 项的 trait。它们的工作把实现类型“标记(mark)”为具有某种属性,否则就没有办法在类型系统中去表示。

// Impling PartialEq for a type promises
// that equality for the type has these properties:
// - symmetry: a == b implies b == a, and
// - transitivity: a == b && b == c 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 == a
trait 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 threads
unsafe auto trait Send {}

// implemented for types whose references are safe to send between threads
unsafe auto trait Sync {}

不安全 Trait(Unsafe Trait)

Trait 可以被标记为 unsafe,以表明实现该 trait 可能需要 unsafe 代码。SendSync都被标记为 unsafe,因为如果它们不是自动实现的类型,就意味着它必须包含一些非Send或非Sync的成员,如果我们想手动标记类型为SendSync,作为实现者我们必须格外小心,确保没有数据竞争。

自动 Trait

Send & Sync

所需预备知识

unsafe auto trait Send {}
unsafe auto trait Sync {}

如果一个类型是Send,这就意味着它可以在线程之间被安全地发送(send)。如果一个类型是Sync,这就意味着它可以在线程间安全地共享引用。说得更准确点就是,当且仅当&TSend时,类型TSync

几乎所有的类型都是SendSync。唯一值得注意的Send例外是RcSync例外中需要注意的是RcCellRefCell。如果我们需要一个满足SendRc,我们可以使用Arc。如果我们需要一个CellRefCellSync版本,我们可以使用MutexRwLock。尽管我们使用MutexRwLock来包装一个原始类型,但通常来讲,使用标准库提供的原子类型会更好一些,比如AtomicBoolAtomicI32AtomicUsize等等。

几乎所有的类型都是Sync这件事,可能会让一些人感到惊讶,但它是真的,即使是对于没有任何内部同步的类型来讲,也是如此。这能够得以实现要归功于 Rust 严格的借用规则。

我们可以传递同一份数据的若干个不可变引用到多个线程中,由于只要有不可变引用存在,Rust 就会静态地保证底层数据不被修改,所以我们可以保证不会发生数据竞争。

use crossbeam::thread;

fn main() {
    let mut greeting = String::from("Hello");
    let greeting_ref = &greeting;

    thread::scope(|scoped_thread| {
        // spawn 3 threads
        for n in 1..=3 {
            // greeting_ref copied into every thread
            scoped_thread.spawn(move |_| {
                println!("{} {}", greeting_ref, n); // prints "Hello {n}"
            });
        }

        // line below could cause UB or data races but compiler rejects it
        greeting += " world"; // ❌ cannot mutate greeting while immutable refs exist
    });

    // can mutate greeting after every thread has joined
    greeting += " world"; // ✅
    println!("{}", greeting); // prints "Hello world"
}

同样地,我们可以把数据的一个可变引用传递给一个单独的线程,由于 Rust 静态地保证不存在可变引用的别名,所以底层数据不会通过另一个可变引用被修改,因此我们也可以保证不会发生数据竞争。

use crossbeam::thread;

fn main() {
    let mut greeting = String::from("Hello");
    let greeting_ref = &mut greeting;

    thread::scope(|scoped_thread| {
        // greeting_ref moved into thread
        scoped_thread.spawn(move |_| {
            *greeting_ref += " world";
            println!("{}", greeting_ref); // prints "Hello world"
        });

        // line below could cause UB or data races but compiler rejects it
        greeting += "!!!"; // ❌ cannot mutate greeting while mutable refs exist
    });

    // can mutate greeting after the thread has joined
    greeting += "!!!"; // ✅
    println!("{}", greeting); // prints "Hello world!!!"
}

这就是为什么大多数类型在不需要任何显式同步的情况下,都满足Sync的原因。当我们需要在多线程中同时修改某个数据T时,除非我们用Arc<Mutex<T>>或者Arc<RwLock<T>>来包装这个数据,否则编译器是不会允许我们进行这种操作,所以编译器会在需要时强制要求进行显式地同步。

Sized

如果一个类型是Sized,这意味着它的类型大小在编译期是可知的,并且可以在栈上创建一个该类型的实例。

类型的大小及其含义是一个微妙而巨大的话题,影响到编程语言的许多方面。因为它十分重要,所以我单独写了一篇文章Sizedness in Rust,如果有人想要更深入地了解 sizedness,我强烈推荐阅读这篇文章。我会把这篇文章的关键内容总结在下面。

  1. 所有的泛型类型都有一个隐含的Sized约束。
fn func<T>(t: &T) {}

// example above desugared
fn func<T: Sized>(t: &T) {}
  1. 因为所有的泛型类型上都有一个隐含的Sized约束,如果我们想要选择退出这个约束,我们需要使用特定的“宽松约束(relaxed bound)”语法——?Sized,该语法目前只为Sized trait 存在。
// now T can be unsized
fn func<T: ?Sized>(t: &T) {}
  1. 所有的 trait 都有一个隐含的?Sized约束。
trait Trait {}

// example above desugared
trait Trait: ?Sized {}

这是为了让 trait 对象能够实现 trait,重申一下,所有的细枝末节都在Sizedness in Rust中。

泛型 traits

Default

trait Default {
    fn default() -> Self;
}

可以为实现了Default的类型构造默认值。

struct Color {
    r: u8,
    g: u8,
    b: u8,
}

impl Default for Color {
    // default color is black
    fn default() -> Self {
        Color {
            r: 0,
            g: 0,
            b: 0,
        }
    }
}

这在快速构建原型的时候十分有用,尤其是在我们没有过多要求而只需要一个类型实例的情况下:

fn main() {
    // just give me some color!
    let color = Color::default();
}

当我们想要显式地把函数暴露给用户时,也可以选择这样做:

struct Canvas;
enum Shape {
    Circle,
    Rectangle,
}

impl Canvas {
    // let user optionally pass a color
    fn paint(&mut self, shape: Shape, color: Option<Color>) {
        // if no color is passed use the default color
        let color = color.unwrap_or_default();
        // etc
    }
}

当我们需要构造泛型类型时,Default在泛型上下文中也是有用的:

fn guarantee_length<T: Default>(mut vec: Vec<T>, min_len: usize) -> Vec<T> {
    for _ in 0..min_len.saturating_sub(vec.len()) {
        vec.push(T::default());
    }
    vec
}

我们还可以利用Default类型结合 Rust 的结构体更新语法(struct update syntax)来对结构体部分初始化。现在,我们有一个Color结构体构造函数new,该函数接收结构体的所有成员作为参数:

impl Color {
    fn new(r: u8, g: u8, b: u8) -> Self {
        Color {
            r,
            g,
            b,
        }
    }
}

但是,我们可以有更为便利的构造函数,这些构造函数分别只接收结构体的一部分成员,结构体剩下的其他成员使用默认值:

impl Color {
    fn red(r: u8) -> Self {
        Color {
            r,
            ..Color::default()
        }
    }
    fn green(g: u8) -> Self {
        Color {
            g,
            ..Color::default()
        }
    }
    fn blue(b: u8) -> Self {
        Color {
            b,
            ..Color::default()
        }
    }
}

还有一个Default派生宏,通过使用它我们可以像下面这样来写Color

// default color is still black
// because u8::default() == 0
#[derive(Default)]
struct Color {
    r: u8,
    g: u8,
    b: u8
}

Clone

trait Clone {
    fn clone(&self) -> Self;

    // provided default impls
    fn clone_from(&mut self, source: &Self);
}

我们能够把Clone类型的不可变引用转换为所拥有的值,即&T->TClone不保证这种转换的效率,所以它会很慢并且成本较高。我们可以使用派生宏在一个类型上快速实现Clone

#[derive(Clone)]
struct SomeType {
    cloneable_member1: CloneableType1,
    cloneable_member2: CloneableType2,
    // etc
}

// macro generates impl below
impl Clone for SomeType {
    fn clone(&self) -> Self {
        SomeType {
            cloneable_member1: self.cloneable_member1.clone(),
            cloneable_member2: self.cloneable_member2.clone(),
            // etc
        }
    }
}

Clone可以用于在泛型上下文中构造一个类型实例。下面是从前面章节拿过来的一个例子,其中的Default被替换为了Clone

fn guarantee_length<T: Clone>(mut vec: Vec<T>, min_len: usize, fill_with: &T) -> Vec<T> {
    for _ in 0..min_len.saturating_sub(vec.len()) {
        vec.push(fill_with.clone());
    }
    vec
}

人们通常把克隆(clone)作为一种避免和借用检查器打交道的逃生出口(escape hatch)。管理带有引用的结构体很具有挑战性,但是我们可以通过克隆把引用变为所拥有的值。

// oof, we gotta worry about lifetimes 

标签:指南,impl,Point,Trait,self,let,Rust,trait,fn
From: https://www.cnblogs.com/RioTian/p/18072690

相关文章

  • 最详Hive入门指南
    本质就是一个hadoop的客户端,将HIveSQL转化成MapReduce程序一、Hive介绍&配置1、hive本质基于Hadoop的⼀个数据仓库⼯具,可以将结构化的数据⽂件映射为⼀张表,并提供类SQL查询功能。本质就是一个hadoop的客户端,将HIveSQL转化成MapReduce程序2、架构原理主要分为三......
  • 洛谷题单指南-二叉树-P4913 【深基16.例3】二叉树深度
    原题链接:https://www.luogu.com.cn/problem/P4913题意解读:计算二叉树的深度解题思路:首先介绍二叉树的存储方式,对于本题,直接用数组模拟,数组的下标即节点号structnode{intl,r;}tree[N];tree[i].l存的是节点i的左子结点,tree[i].r存的是节点i的右子节点。计算深度至......
  • 「Java开发指南」MyEclipse如何支持Spring Scaffolding?(五)
    在上文中(点击这里回顾>>)主要为大家介绍了SpringDSL模型等内容,本文将继续介绍菜单等。MyEclipsev2023.1.2离线版下载MyEclipse技术交流群:742336981欢迎一起进群讨论6.菜单本节主要描述与Spring支持的MyEclipse相关的各种菜单。6.1MyEclipse菜单当您右键单击Eclipse项目......
  • docker系列-报错以及解决指南
    1.windows运行docker报错WindowsHypervisorisnotpresentDockerDesktopisunabletodetectaHypervisor.HardwareassistedvirtualizationanddataexecutionprotectionmustbeenabledintheBIOS.DockerDesktop-WindowsHypervisorisnotpresentDockerDeskto......
  • Rust的Cell、RefCell和OnceCell:灵活且安全的内部可变性
    这一系列文章的创作目的主要是帮助我自己深入学习Rust,同时也为已经具备一定Rust编程经验,但还没有深入研究过语言和标准库的朋友提供参考。对于正在入门Rust的同学,我更建议你们看《Rust圣经》或者《TheBook》,而不是这种晦涩难懂的文章。终于拿到了某量化公司的offer,继续系列文......
  • OpenCASCADE开发指南<二>:OCC 体系结构和基本概念
        OCC是用面向对象方法设计的一个CAD基础平台(软件)。为了能从整体上把握OCC的组织情况,也为了方便后续章节的讨论,下面将介绍OCC体系结构和几个基本概念。1、OCC体系结构1.1面向对象方法和面向对象的软件工程  在介绍OCC体系结构之前,先介绍面向对象方......
  • 【转载】学术科研无从下手?27 条机器学习避坑指南,让你的论文发表少走弯路
    原作者链接:https://blog.csdn.net/HyperAI/article/details/128866164 内容一览:如果你刚接触机器学习不久,并且未来希望在该领域开展学术研究,那么这份为你量身打造的「避坑指南」可千万不要错过了。关键词:机器学习科研规范学术研究机器学习学术小白,如何优雅避坑坑、让自己的......
  • Proxmark3入门指南
    Proxmark3笔记——Proxmark3完全入门指南写在前面这里所有针对扇区、区块的计数都是从0开始算一些需要知道的知识为了能看懂笔记,需要能回答以下问题ID卡和IC卡主要的区别是什么?什么是全加密卡和半加密卡,区别是什么。IC卡分哪几个大类?IC卡的卡号储存在哪个扇区?是前几位......
  • Matplotlib中的子图:规划绘图的指南和工具
    导读我最近从事一个项目,需要在matplotlib中进行一些微调的子图和叠加。虽然我对制作基本的可视化感到很舒服,但我很快发现我对子图系统的理解没有达到标准。于是回到基础知识,并花了一些时间阅读文档并在StackOverflow上搜索相关示例和解释。当我开始了解mateplotlib的......
  • Rust GUI库 egui 的简单应用
    目录简介简单示例创建项目界面设计切换主题自定义字体自定义图标经典布局定义导航变量实现导航界面实现导航逻辑实现主框架布局调试运行参考资料简介egui(发音为“e-gooey”)是一个简单、快速且高度可移植的Rust即时模式GUI库,跨平台、Rust原生,适合一些小工具和游戏引擎GUI:文......