泛型类型、特征和生命周期


每种编程语言都有有效处理概念重复的工具。在 Rust 中,一个这样的工具是 generics:具体类型或其他属性的抽象替代项。我们可以表达泛型的行为或它们与其他泛型的关系,而无需知道在编译和运行代码时它们的位置是什么。


函数可以采用某种泛型类型的参数,而不是像 i32String 这样的具体类型,就像它们采用具有未知值的参数来对多个具体值运行相同的代码一样。事实上,我们已经在第 6 章中使用了泛型的 Option<T>,在第 8 章中使用了 Vec<T>,并且 HashMap<K、V>,以及第 9 章中的 Result<T、E>。在本章中,您将探索如何使用泛型定义自己的类型、函数和方法!


首先,我们将回顾如何提取函数以减少代码重复。然后,我们将使用相同的技术从两个函数中创建一个泛型函数,这两个函数仅在参数类型上有所不同。我们还将解释如何在 struct 和 enum 定义中使用泛型类型。


然后,您将学习如何使用 trait 以通用方式定义行为。您可以将 trait 与泛型类型组合在一起,以约束泛型类型仅接受具有特定行为的那些类型,而不仅仅是任何类型。


最后,我们将讨论生命周期:各种泛型,为编译器提供有关引用如何相互关联的信息。生命周期允许我们向编译器提供有关借用值的足够信息,以便它可以确保引用在更多情况下有效,而不是在没有我们帮助的情况下。


通过提取函数删除重复项


泛型允许我们将特定类型替换为表示多个类型的占位符,以消除代码重复。在深入研究泛型语法之前,我们首先看一下如何通过提取一个函数来以不涉及泛型类型的方式删除重复项,该函数将特定值替换为表示多个值的占位符。然后,我们将应用相同的技术来提取泛型函数!通过了解如何识别可以提取到函数中的重复代码,您将开始识别可以使用泛型的重复代码。


我们将从示例 10-1 中的短程序开始,它在列表中找到最大的数字。


文件名: 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 所示。


文件名: 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:在 2 中找到最大数字的代码 数字列表


尽管此代码有效,但复制代码既乏味又容易出错。我们还必须记住,当我们想要更改代码时,要在多个地方更新代码。


为了消除这种重复,我们将通过定义一个函数来创建一个抽象,该函数对作为参数传入的任何整数列表进行作。这个解决方案使我们的代码更清晰,并让我们抽象地表达在列表中查找最大数字的概念。


在示例 10-3 中,我们将找到最大数字的代码提取到名为 largest 的函数中。然后我们调用函数来查找示例 10-2 中的两个列表中的最大数字。我们还可以将该函数用于将来可能拥有的任何其他 i32 值列表。


文件名: 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:在两个列表中查找最大数字的抽象代码


最大的函数有一个名为 list 的参数,它表示我们可能传递给函数的任何 i32 值的具体切片。因此,当我们调用函数时,代码将对我们传入的特定值运行。


总之,以下是我们将代码从示例 10-2 更改为示例 10-3 所采取的步骤:


  1. 识别重复代码。

  2. 将重复代码提取到函数体中,并在函数签名中指定该代码的输入和返回值。

  3. 更新重复代码的两个实例以改为调用该函数。


接下来,我们将对泛型使用这些相同的步骤来减少代码重复。与函数体可以对抽象列表而不是特定值进行作的方式相同,泛型允许代码对抽象类型进行作。


例如,假设我们有两个函数:一个用于查找 i32 值切片中的最大项,另一个用于查找 char 切片中的最大项 值。我们将如何消除这种重复?让我们来了解一下!