键盘快捷键

按 ← 或 → 在章节间导航

按 S 或 / 搜索本书

按 ? 显示此帮助

按 Esc 隐藏此帮助

泛型类型、Trait 与生命周期

每种编程语言都有有效处理概念重复的工具。在 Rust 中,这类工具之一就是泛型:它是具体类型或其他属性的抽象占位符。即使在编译和运行代码时还不知道这些占位符具体代表什么,我们也可以表达泛型的行为,或说明它们之间的关系。

函数可以接受某种泛型类型的参数,而不是像 i32 或 String 这样的具体类型;这与函数接受未知值作为参数、从而对多个具体值运行同一段代码的方式类似。实际上,我们已经在第 6 章使用了 Option<T>,在第 8 章使用了 Vec<T> 和 HashMap<K, V>,并在第 9 章使用了 Result<T, E>。本章将介绍如何用泛型定义自己的类型、函数和方法!

首先,我们会回顾如何提取函数以减少代码重复。然后,对两个仅参数类型不同的函数使用相同技巧,将它们改写为一个泛型函数。我们还会说明如何在结构体和枚举定义中使用泛型类型。

接下来,你将学习如何使用 trait 以泛型方式定义行为。你可以将 trait 与泛型类型结合起来,限制泛型类型只接受具有特定行为的类型,而不是任意类型。

最后,我们会讨论生命周期:这是一类泛型,它向编译器提供引用之间关系的信息。生命周期让我们能够向编译器充分说明借用值的情况,使它能够在比没有这些信息时更多的情形下确保引用有效。

通过提取函数消除重复

泛型允许我们用一个代表多种类型的占位符替换具体类型,从而消除代码重复。在深入了解泛型语法之前,我们先看看不涉及泛型类型的消除重复方法:提取一个函数,用代表多个值的占位符替换具体值。然后,我们会使用同样的技巧提取泛型函数!了解如何识别可提取到函数中的重复代码后,你也会开始发现哪些重复代码可以使用泛型。

我们先看清单 10-1 中的简短程序,它会找出列表中的最大值。

Filename: src/main.rs
fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {largest}");
    assert_eq!(*largest, 100);
}
清单 10-1:找出数字列表中的最大值

我们将一个整数列表存入变量 number_list,并在名为 largest 的变量中保存对列表第一个数字的引用。接着遍历列表中的所有数字;如果当前数字大于 largest 中保存的数字,就替换该变量中的引用。如果当前数字小于或等于目前遇到的最大值,变量就保持不变,代码继续处理列表中的下一个数字。检查完列表中的所有数字后,largest 应该指向最大值,本例中即为 100。

现在,我们需要找出两个不同数字列表中的最大值。为此,可以复制清单 10-1 中的代码,并在程序的两个位置使用相同逻辑,如清单 10-2 所示。

Filename: src/main.rs
fn main() {
    let number_list = vec![34, 50, 25, 100, 65];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {largest}");

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let mut largest = &number_list[0];

    for number in &number_list {
        if number > largest {
            largest = number;
        }
    }

    println!("The largest number is {largest}");
}
清单 10-2:找出两个数字列表中最大值的代码

虽然这段代码可以正常工作,但重复代码既繁琐又容易出错。想要修改时,我们还必须记得在多个地方同步更新。

为了消除重复,我们可以定义一个函数作为抽象,让它处理作为参数传入的任意整数列表。这样代码会更清晰,也能让我们以抽象的方式表达“找出列表中的最大值”这一概念。

在清单 10-3 中,我们将找最大值的代码提取到名为 largest 的函数中。然后调用该函数,找出清单 10-2 中两个列表的最大值。将来遇到其他 i32 值列表时,也可以使用这个函数。

Filename: src/main.rs
fn largest(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        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}");
    assert_eq!(*result, 100);

    let number_list = vec![102, 34, 6000, 89, 54, 2, 43, 8];

    let result = largest(&number_list);
    println!("The largest number is {result}");
    assert_eq!(*result, 6000);
}
清单 10-3:抽象出用于找出两个列表中最大值的代码

largest 函数有一个名为 list 的参数,它代表我们可能传入函数的任意具体 i32 切片。因此,调用函数时,代码会针对我们传入的特定值运行。

总结一下,从清单 10-2 改写为清单 10-3 的步骤如下:

  1. 找出重复代码。
  2. 将重复代码提取到函数体中,并在函数签名中指定这段代码的输入和返回值。
  3. 将两处重复代码改为调用该函数。

接下来,我们会用泛型执行同样的步骤来减少代码重复。正如函数体可以处理抽象的 list 而非特定值一样,泛型也允许代码处理抽象类型。

例如,假设我们有两个函数:一个找出 i32 切片中的最大项,另一个找出 char 切片中的最大项。如何消除这两者之间的重复呢?让我们继续探索!