rus学习笔记之基础:泛型和trait

泛型

泛型(generic)是用于泛化类型和函数功能,以扩大其适用范围。泛型极大地减少了代码的重复,但它自身的语法很要求细心。也就是说,采用泛型意味着仔细地指定泛型类型具体化时,什么样的具体类型是合法的。泛型最简单和常用的用法是用于类型参数。

定义泛型类型或泛型函数之类的东西时,我们会用 <A> 或者 <T> 这类标记作为类型的代号,就像函数的形参一样。在使用时,为把 <A><T> 具体化,我们会把类型说明像实参一样使用,像是 <i32> 这样。这两种把(泛型的或具体的)类型当作参数的用法就是类型参数。

泛型的类型参数是使用尖括号和大驼峰命名的名称:<Aaa, Bbb, ...> 来指定的。泛型类型参数一般用 <T> 来表示。

在函数定义中使用泛型

当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。

当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。类型参数声明位于函数名称与参数列表中间的尖括号 <> 中,像这样:

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

可以这样理解这个定义:函数 largest 有泛型类型 T。它有个参数 list,其类型是元素为 T 的 slice。largest 函数的返回值类型也是 T。

不过 largest 的函数体不能适用于 T 的所有可能的类型,因此上面会编译报错。因为在函数体需要比较 T 类型的值,不过它只能用于我们知道如何排序的类型。

结构体定义中的泛型

同样也可以用 <> 语法来定义结构体,它包含一个或多个泛型参数类型字段。其语法类似于函数定义中使用泛型。首先,必须在结构体名称后面的尖括号中声明泛型参数的名称。接着在结构体定义中可以指定具体数据类型的位置使用泛型类型。

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

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

注意 Point<T> 的定义中只使用了一个泛型类型,这个定义表明结构体 Point<T> 对于一些类型 T 是泛型的,而且字段 x 和 y 都是 相同类型的,无论它具体是何类型。

如果想要定义一个 x 和 y 可以有不同类型且仍然是泛型的 Point 结构体,我们可以使用多个泛型类型参数。

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

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

枚举定义中的泛型

和结构体类似,枚举也可以在成员中存放泛型数据类型。 Option<T> 枚举:

enum Option<T> {
    Some(T),
    None,
}

枚举也可以拥有多个泛型类型。Result 枚举:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

方法定义中的泛型

在为结构体和枚举实现方法时,一样也可以用泛型。

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

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };
    println!("p.x = {}", p.x());
}

注意必须在 impl 后面声明 T,这样就可以在 Point<T> 上实现的方法中使用它了。在 impl 之后声明泛型 T ,这样 Rust 就知道 Point 的尖括号中的类型是泛型而不是具体类型。

结构体定义中的泛型类型参数并不总是与结构体方法签名中使用的泛型是同一类型。

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

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c'};
    let p3 = p1.mixup(p2);
    println!("p3.x = {}, p3.y = {}", p3.x, p3.y); //  p3.x = 5, p3.y = c
}

这个例子展示了一些泛型通过 impl 声明而另一些通过方法定义声明的情况。这里泛型参数 T 和 U 声明于 impl 之后,因为他们与结构体定义相对应。而泛型参数 V 和 W 声明于 fn mixup 之后,因为他们只是相对于方法本身的。

泛型代码的性能

Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失。Rust 通过在编译时进行泛型代码的单态化来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。我们可以使用泛型来编写不重复的代码,而 Rust 将会为每一个实例编译其特定类型的代码。这意味着在使用泛型时没有运行时开销;当代码运行,它的执行效率就跟好像手写每个具体定义的重复代码一样。这个单态化过程正是 Rust 泛型在运行时极其高效的原因。

trait

trait 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。

trait 类似于其他语言中常被称为接口(interfaces)的功能,虽然有一些不同。

定义 trait

一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。

pub trait Summary {
    fn summarize(&self) -> String;
}

使用 trait 关键字来声明一个 trait,后面是 trait 的名字。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名。在方法签名后跟分号,而不是在大括号中提供其实现。trait 体中可以有多个方法:一行一个方法签名且都以分号结尾。

每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现 Summary trait 的类型都拥有与这个签名的定义完全一致的 summarize 方法。

为类型实现 trait

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

实现 trait 时需要注意的一个限制是,只有当 trait 或者要实现 trait 的类型位于 crate 的本地作用域时,才能为该类型实现 trait。不能为外部类型实现外部 trait。

默认实现

有时为 trait 中的某些或全部方法提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的。这样当为某个特定类型实现 trait 时,可以选择保留或重载每个方法的默认行为。

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

如果想要对 NewsArticle 实例使用这个默认实现,而不是定义一个自己的实现,则可以通过 impl Summary for NewsArticle {} 指定一个空的 impl 块。

默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。如此,trait 可以提供很多有用的功能而只需要实现指定一小部分内容。

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

为了使用这个版本的 Summary,只需在实现 trait 时定义 summarize_author 即可:

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

请注意,无法从相同方法的重载实现中调用默认方法。

trait 作为参数

pub fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

对于 item 参数,我们指定了 impl 关键字和 trait 名称,而不是具体的类型。该参数支持任何实现了指定 trait 的类型。

Trait Bound 语法

在使用泛型时,类型参数常常必须使用 trait 作为约束(bound)来明确规定类型应实现哪些功能。例如下面的例子用 Summary 来约束 T,也就是说 T 必须实现 Summary

impl Trait 语法适用于直观的例子,它实际上是一种较长形式语法的语法糖。我们称为 trait bound,它看起来像:

pub fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}

impl Trait 很方便,适用于短小的例子。trait bound 则适用于更复杂的场景。泛型 T 被指定为 item1 和 item2 的参数限制,如此传递给参数 item1 和 item2 值的具体类型必须一致。

pub fn notify<T: Summary>(item1: T, item2: T) {
}

通过 + 指定多个 trait bound

item 需要同时实现两个不同的 trait:Display 和 Summary。这可以通过 + 语法实现:

pub fn notify(item: impl Summary + Display) {
}

+ 语法也适用于泛型的 trait bound:

pub fn notify<T: Summary + Display>(item: T) {
}

通过 where 简化 trait bound

有多个泛型参数的函数在名称和参数列表之间会有很长的 trait bound 信息,这使得函数签名难以阅读。为此,Rust 有另一个在函数签名之后的 where 从句中指定 trait bound 的语法。所以除了这么写:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
}

还可以像这样使用 where 从句:

fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{
}

返回实现了 trait 的类型

也可以在返回值中使用 impl Trait 语法,来返回实现了某个 trait 的类型:

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}

通过使用 impl Summary 作为返回值类型,我们指定了 returns_summarizable 函数返回某个实现了 Summary trait 的类型,但是不确定其具体的类型。

返回一个只是指定了需要实现的 trait 的类型的能力在闭包和迭代器场景十分的有用。闭包和迭代器创建只有编译器知道的类型,或者是非常非常长的类型。impl Trait 允许你简单的指定函数返回一个 Iterator 而无需写出实际的冗长的类型。不过这只适用于返回单一类型的情况。

使用 trait bounds 来修复自定义的 largest 函数

在 largest 函数体中我们想要使用大于运算符 > 比较两个 T 类型的值。这个运算符被定义为标准库中 trait std::cmp::PartialOrd 的一个默认方法。所以需要在 T 的 trait bound 中指定 PartialOrd,这样 largest 函数可以用于任何可以比较大小的类型的 slice。

对于非泛型版本的 largest 函数,我们只尝试了寻找最大的 i32 和 char。像 i32 和 char 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 Copy trait。当我们将 largest 函数改成使用泛型后,现在 list 参数的类型就有可能是没有实现 Copy trait 的。这意味着我们可能不能将 list[0] 的值移动到 largest 变量中,因此需要在 T 的 trait bounds 中增加 Copy。

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];

    for &item in list.iter() {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

如果并不希望限制 largest 函数只能用于实现了 Copy trait 的类型,我们可以在 T 的 trait bounds 中指定 Clone 而不是 Copy。并克隆 slice 的每一个值使得 largest 函数拥有其所有权。使用 clone 函数意味着对于类似 String 这样拥有堆上数据的类型,会潜在的分配更多堆上空间,而堆分配在涉及大量数据时可能会相当缓慢。

另一种 largest 的实现方式是返回在 slice 中 T 值的引用。如果我们将函数返回值从 T 改为 &T 并改变函数体使其能够返回一个引用,我们将不需要任何 Clone 或 Copy 的 trait bounds 而且也不会有任何的堆分配。

使用 trait bound 有条件地实现方法

通过使用带有 trait bound 的泛型参数的 impl 块,可以有条件地只为那些实现了特定 trait 的类型实现方法。

类型 Pair<T> 总是实现了 new 方法,不过只有那些为 T 类型实现了 PartialOrd trait (来允许比较) 和 Display trait (来启用打印)的 Pair<T> 才会实现 cmp_display 方法:

use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

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

impl<T: Display + 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 bound 有条件地实现 trait

也可以对任何实现了特定 trait 的类型有条件地实现 trait。对任何满足特定 trait bound 的类型实现 trait 被称为 blanket implementations,他们被广泛的用于 Rust 标准库中。例如,标准库为任何实现了 Display trait 的类型实现了 ToString trait。这个 impl 块看起来像这样:

impl<T: Display> ToString for T {
    // --snip--
}

因为标准库有了这些 blanket implementation,我们可以对任何实现了 Display trait 的类型调用由 ToString 定义的 to_string 方法。例如,可以将整型转换为对应的 String 值,因为整型实现了 Display:let s = 3.to_string();

泛型 trait

// 不可复制的类型,没有实现 Copy trait 的类型
struct Empty;
struct Null;

// `T` 的泛型 trait。
trait DoubleDrop<T> {
    // 定义一个调用者的方法,接受一个额外的参数 `T`,但不对它做任何事。
    fn double_drop(self, _: T);
}

// 对泛型的调用者类型 `U` 和任何泛型类型 `T` 实现 `DoubleDrop<T>` 。
impl<T, U> DoubleDrop<T> for U {
    // 此方法获得两个传入参数的所有权,并释放它们。
    fn double_drop(self, _: T) {}
}

fn main() {
    let empty = Empty;
    let null  = Null;

    // 释放 `empty` 和 `null`。
    empty.double_drop(null);
    // empty和null不再可访问
}

空 trait

约束的工作机制会产生这样的效果:即使一个 trait 不包含任何功能,你仍然可以用它作为约束。

struct Cardinal;
struct BlueJay;

trait Red {}
trait Blue {}

impl Red for Cardinal {}
impl Blue for BlueJay {}

// 这些函数只对实现了相应的 trait 的类型有效。事实上这些 trait 内部是空的,但这没有关系。
fn red<T: Red>(_: &T)   -> &'static str { "red" }
fn blue<T: Blue>(_: &T) -> &'static str { "blue" }

fn main() {
    let cardinal = Cardinal;
    let blue_jay = BlueJay;

    // 由于约束,`red()` 不能作用于 blue_jay,反过来也一样。
    println!("A cardinal is {}", red(&cardinal));
    println!("A blue jay is {}", blue(&blue_jay));
}

关联类型

关联类型(associated types)是一个将类型占位符与 trait 相关联的方式,这样 trait 的方法签名中就可以使用这些占位符类型。trait 的实现者会针对特定的实现在这个类型的位置指定相应的具体类型。如此可以定义一个使用多种类型的 trait,直到实现此 trait 时都无需知道这些类型具体是什么。

通过把容器内部的类型放到 trait 中作为输出类型,使用 “关联类型” 增加了代码 的可读性。这样的 trait 的定义语法如下:

// `A` 和 `B` 在 trait 里面通过 `type` 关键字来定义。(注意:此处的 `type` 不同于为类型取别名时的 `type`)。
trait Contains {
    type A;
    type B;

    // 这种语法能够泛型地表示这些新类型。
    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
}

注意使用了 Contains trait 的函数就不需要写出 A 或 B 了:

// 不使用关联类型
fn difference<A, B, C>(container: &C) -> i32 where
    C: Contains<A, B> { ... }

// 使用关联类型
fn difference<C: Contains>(container: &C) -> i32 { ... }

使用例子

struct Container(i32, i32);

// 这个 trait 检查给定的 2 个项是否储存于容器中,并且能够获得容器的第一个或最后一个值。
trait Contains {
    // 在这里定义可以被方法使用的泛型类型。
    type A;
    type B;

    fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
    fn first(&self) -> i32;
    fn last(&self) -> i32;
}

impl Contains for Container {
    // 指出 `A` 和 `B` 是什么类型。如果输入类型为 `Container(i32, i32)`,那么输出类型会被确定为 `i32` 和 `i32`。
    type A = i32;
    type B = i32;

    // `&Self::A` 和 `&Self::B` 在这里也是合法的类型。
    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 }
}

fn difference<C: Contains>(container: &C) -> i32 {
    container.last() - container.first()
}

fn main() {
    let number_1 = 3;
    let number_2 = 10;
    let container = Container(number_1, number_2);
    println!("Does container contain {} and {}: {}",
        &number_1, &number_2,
        container.contains(&number_1, &number_2));
    println!("First number: {}", container.first());
    println!("Last number: {}", container.last());
    println!("The difference is: {}", difference(&container));
}

派生

通过 #[derive] 属性,编译器能够提供某些 trait 的基本实现。如果需要更复杂的行为,这些 trait 也可以手动实现。

下面是可以自动派生的 trait:

  • 比较 trait: Eq, PartialEq, Ord, PartialOrd
  • Clone, 用来从 &T 创建副本 T。
  • Copy,使类型具有 “复制语义”(copy semantics)而非 “移动语义”(move semantics)。
  • Hash,从 &T 计算哈希值(hash)。
  • Default, 创建数据类型的一个空实例。
  • Debug,使用 {:?} formatter 来格式化一个值。
// `Centimeters`,可以比较的元组结构体
#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);

// `Inches`,可以打印的元组结构体
#[derive(Debug)]
struct Inches(i32);

impl Inches {
    fn to_centimeters(&self) -> Centimeters {
        let &Inches( inches)  = self; // 解构

        Centimeters(inches as f64 * 2.54)
    }
}

// `Seconds`,不带附加属性的元组结构体
struct Seconds(i32);

fn main() {
    let _one_second = Seconds(1);

    // 报错:`Seconds` 不能打印;它没有实现 `Debug` trait
    //println!("One second looks like: {:?}", _one_second);

    // 报错:`Seconds`不能比较;它没有实现 `PartialEq` trait
    //let _this_is_true = (_one_second == _one_second);

    let foot = Inches(12);
    println!("One foot equals {:?}", foot); // One foot equals Inches(12)

    let meter = Centimeters(100.0);
    let cmp =
        if foot.to_centimeters() < meter {
            "smaller"
        } else {
            "bigger"
        };
    println!("One foot is {} than one meter.", cmp); // One foot is smaller than one meter.
}

使用 dyn 返回 trait

Rust 编译器需要知道每个函数的返回类型需要多少空间。这意味着所有函数都必须返回一个具体类型。与其他语言不同,如果你有个像 Animal 那样的的 trait,则不能编写返回 Animal 的函数,因为其不同的实现将需要不同的内存量。但是,有一个简单的解决方法。相比于直接返回一个 trait 对象,我们的函数返回一个包含一些 Animal 的 Box。Box 只是对堆中某些内存的引用。因为引用的大小是静态已知的,并且编译器可以保证引用指向已分配的堆 Animal,所以我们可以从函数中返回 trait!

每当在堆上分配内存时,Rust 都会尝试尽可能明确。因此,如果你的函数以这种方式返回指向堆的 trait 指针,则需要使用 dyn 关键字编写返回类型,例如 Box<dyn Animal>

struct Sheep {}
struct Cow {}

trait Animal {
    // 实例方法签名
    fn noise(&self) -> &'static str;
}

// 实现 `Sheep` 的 `Animal` trait。
impl Animal for Sheep {
    fn noise(&self) -> &'static str {
        "baaaaah!"
    }
}

// 实现 `Cow` 的 `Animal` trait。
impl Animal for Cow {
    fn noise(&self) -> &'static str {
        "moooooo!"
    }
}

// 返回一些实现 Animal 的结构体,但是在编译时我们不知道哪个结构体。
fn random_animal(random_number: f64) -> Box<dyn Animal> {
    if random_number < 0.5 {
        Box::new(Sheep {})
    } else {
        Box::new(Cow {})
    }
}

fn main() {
    let random_number = 0.234;
    let animal = random_animal(random_number);
    println!("You've randomly chosen an animal, and it says {}", animal.noise());
}

运算符重载

在 Rust 中,很多运算符可以通过 trait 来重载。也就是说,这些运算符可以根据它们的输入参数来完成不同的任务。这之所以可行,是因为运算符就是方法调用的语法糖。例 如,a + b 中的 + 运算符会调用 add 方法(也就是 a.add(b))。这个 add 方 法是 Add trait 的一部分。因此,+ 运算符可以被任何 Add trait 的实现者使用。

use std::ops;

struct Foo;
struct Bar;

#[derive(Debug)]
struct FooBar;

#[derive(Debug)]
struct BarFoo;

// `std::ops::Add` trait 用来指明 `+` 的功能,这里我们实现 `Add<Bar>`,它是用于把对象和 `Bar` 类型加起来的 `trait`。
impl ops::Add<Bar> for Foo {
    type Output = FooBar;

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

        FooBar
    }
}

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

Drop trait

Drop trait 只有一个方法:drop,当对象离开作用域时会自动调用该 方法。Drop trait 的主要作用是释放实现者的实例拥有的资源。

Box,Vec,String,File,以及 Process 是一些实现了 Drop trait 来释放 资源的类型。Drop trait 也可以为任何自定义数据类型手动实现。

struct Droppable {
    name: &'static str,
}

// 这个简单的 `drop` 实现添加了打印到控制台的功能。
impl Drop for Droppable {
    fn drop(&mut self) {
        println!("> Dropping {}", self.name);
    }
}

fn main() {
    let _a = Droppable { name: "a" };

    // 代码块 A
    {
        let _b = Droppable { name: "b" };

        // 代码块 B
        {
            let _c = Droppable { name: "c" };
            let _d = Droppable { name: "d" };
            println!("Exiting block B");
        }
        println!("Just exited block B");
        println!("Exiting block A");
    }
    println!("Just exited block A");

    // 变量可以手动使用 `drop` 函数来销毁。
    drop(_a);

    println!("end of the main function");
    // `_a` *不会*在这里再次销毁,因为它已经被(手动)销毁。
}

输入

Exiting block B
> Dropping d
> Dropping c
Just exited block B
Exiting block A
> Dropping b
Just exited block A
> Dropping a
end of the main function

Iterator trait

Iterator trait 用来对集合(collection)类型(比如数组)实现迭代器。这个 trait 只需定义一个返回 next(下一个)元素的方法,这可手动在 impl 代码块 中定义,或者自动定义(比如在数组或区间中)。

为方便起见,for 结构会使用 .into_iterator() 方法将一些集合类型 转换为迭代器。

struct Fibonacci {
    curr: u32,
    next: u32,
}

// 为 `Fibonacci`(斐波那契)实现 `Iterator`。`Iterator` trait 只需定义一个能返回 `next` 元素的方法。
impl Iterator for Fibonacci {
    type Item = u32;

    // 我们在这里使用 `.curr` 和 `.next` 来定义数列(sequence)。返回类型为 `Option<T>`:
    fn next(&mut self) -> Option<u32> {
        let new_next = self.curr + self.next;

        self.curr = self.next;
        self.next = new_next;

        // 既然斐波那契数列不存在终点,那么 `Iterator` 将不可能返回 `None`,而总是返回 `Some`。
        Some(self.curr)
    }
}

// 返回一个斐波那契数列生成器
fn fibonacci() -> Fibonacci {
    Fibonacci { curr: 1, next: 1 }
}

fn main() {
    // `0..3` 是一个 `Iterator`,会产生:0、1 和 2。
    let mut sequence = 0..3;

    println!("> {:?}", sequence.next());
    println!("> {:?}", sequence.next());
    println!("> {:?}", sequence.next());
    println!("> {:?}", sequence.next());

    // `for` 遍历 `Iterator` 直到返回 `None`,并且每个 `Some` 值都被解包(unwrap),然后绑定给一个变量(这里是 `i`)。
    for i in 0..3 {
        println!("> {}", i);
    }

    // `take(n)` 方法提取 `Iterator` 的前 `n` 项。
    for i in fibonacci().take(4) {
        println!("> {}", i);
    }

    // `skip(n)` 方法移除前 `n` 项
    for i in fibonacci().skip(4).take(4) {
        println!("> {}", i);
    }

    let array = [1u32, 3, 3, 7];
    // `iter` 方法对数组/slice 产生一个 `Iterator`。
    for i in array.iter() {
        println!("> {}", i);
    }
}

Clone trait

当处理资源时,默认的行为是在赋值或函数调用的同时将它们转移。但是我们有时候也需要 把资源复制一份。Clone trait 正好帮助我们完成这任务。通常,我们可以使用由 Clone trait 定义的 .clone() 方法。

// 不含资源的单元结构体
#[derive(Debug, Clone, Copy)]
struct Nil;

// 一个包含资源的结构体,它实现了 `Clone` trait
#[derive(Clone, Debug)]
struct Pair(Box<i32>, Box<i32>);

fn main() {
    // 实例化 `Nil`
    let nil = Nil;
    // 复制 `Nil`,没有资源用于移动(move)
    let copied_nil = nil;
    // 两个 `Nil` 都可以独立使用
    println!("original: {:?}", nil);
    println!("copy: {:?}", copied_nil);

    // 实例化 `Pair`
    let pair = Pair(Box::new(1), Box::new(2));
    println!("original: {:?}", pair);
    // 将 `pair` 绑定到 `moved_pair`,移动(move)了资源
    let moved_pair = pair;
    println!("copy: {:?}", moved_pair);
    // 报错!`pair` 已失去了它的资源。
    //println!("original: {:?}", pair);

    // 将 `moved_pair`(包括其资源)克隆到 `cloned_pair`。
    let cloned_pair = moved_pair.clone();
    // 使用 std::mem::drop 来销毁原始的 pair。
    drop(moved_pair);
    // 报错!`moved_pair` 已被销毁。
    //println!("copy: {:?}", moved_pair);
    // 由 .clone() 得来的结果仍然可用!
    println!("clone: {:?}", cloned_pair);
}

输出

original: Nil
copy: Nil
original: Pair(1, 2)
copy: Pair(1, 2)
clone: Pair(1, 2)

父 trait

Rust 没有“继承”,但是您可以将一个 trait 定义为另一个 trait 的超集(即父 trait)。

trait Person {
    fn name(&self) -> String;
}

// Person 是 Student 的父 trait。实现 Student 需要你也 impl 了 Person。
trait Student: Person {
    fn university(&self) -> String;
}

trait Programmer {
    fn fav_language(&self) -> String;
}

// CompSciStudent 是 Programmer 和 Student 两者的子类。实现 CompSciStudent 需要你同时 impl 了两个父 trait。
trait CompSciStudent: Programmer + Student {
    fn git_username(&self) -> String;
}

fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
    format!(
        "My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
        student.name(),
        student.university(),
        student.fav_language(),
        student.git_username()
    )
}

消除重叠 trait

一个类型可以实现许多不同的 trait。如果两个 trait 都需要相同的名称怎么办?例如,许多 trait 可能拥有名为 get() 的方法。他们甚至可能有不同的返回类型!为了消除它们之间的歧义,我们必须使用完全限定语法(Fully Qualified Syntax)。

trait UsernameWidget {
    fn get(&self) -> String;
}

trait AgeWidget {
    fn get(&self) -> u8;
}

struct Form {
    username: String,
    age: u8,
}

impl UsernameWidget for Form {
    fn get(&self) -> String {
        self.username.clone()
    }
}

impl AgeWidget for Form {
    fn get(&self) -> u8 {
        self.age
    }
}

fn main() {
    let form = Form{
        username: "rustacean".to_owned(),
        age: 28,
    };
    // 如果取消注释此行,则会收到一条错误消息,提示 “multiple `get` found”。因为毕竟有多个名为 `get` 的方法。
    // println!("{}", form.get());

    let username = <Form as UsernameWidget>::get(&form);
    assert_eq!("rustacean".to_owned(), username);
    let age = <Form as AgeWidget>::get(&form);
    assert_eq!(28, age);
}
posted @ 2025-07-21 10:13  carol2014  阅读(17)  评论(0)    收藏  举报