泛型、特征和生命周期
每种编程语言都有有效地处理概念重复的工具。在 Rust 中,其中一种工具是泛型:具体类型或其他属性的抽象占位符。我们可以在不知道编译和运行代码时它们的位置的情况下,表达泛型的行为或它们与其他泛型的关系。
函数可以采用某种泛型类型的参数,而不是像 i32
或 String
这样的具体类型,就像它们采用具有未知值的参数一样,以在多个具体值上运行相同的代码。事实上,我们已经在第 6 章中使用了带有 Option<T>
的泛型,在第 8 章中使用了 Vec<T>
和 HashMap<K, V>
,在第 9 章中使用了 Result<T, E>
。在本章中,你将探索如何使用泛型定义自己的类型、函数和方法!
首先,我们将回顾如何提取函数以减少代码重复。然后,我们将使用相同的技术,从两个仅参数类型不同的函数中创建一个泛型函数。我们还将解释如何在结构体和枚举定义中使用泛型类型。
然后你将学习如何使用特征以泛型方式定义行为。你可以将特征与泛型类型结合使用,以将泛型类型限制为仅接受具有特定行为的类型,而不是任何类型。
最后,我们将讨论生命周期:一种泛型,它为编译器提供有关引用之间如何相互关联的信息。生命周期使我们能够为编译器提供足够的关于借用值的信息,以便它可以确保引用在更多情况下有效,否则在没有我们的帮助的情况下它无法做到这一点。
通过提取函数消除重复
泛型允许我们用表示多种类型的占位符替换特定类型,以消除代码重复。在深入研究泛型语法之前,让我们先看看如何在不涉及泛型类型的情况下消除重复,方法是提取一个函数,该函数用表示多个值的占位符替换特定值。然后,我们将应用相同的技术来提取泛型函数!通过观察如何识别可以提取到函数中的重复代码,你将开始识别可以使用泛型的重复代码。
我们将从清单 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:查找两个数字列表中最大数字的代码
虽然这段代码可以工作,但复制代码既繁琐又容易出错。当我们想更改代码时,我们还必须记住在多个位置更新代码。
为了消除这种重复,我们将通过定义一个对作为参数传递的任何整数列表进行操作的函数来创建一个抽象。此解决方案使我们的代码更清晰,并使我们能够抽象地表达在列表中查找最大数字的概念。
在清单 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:抽象的代码来查找两个列表中的最大数字
largest
函数有一个名为 list
的参数,它表示我们可能传递给该函数的任何具体的 i32
值切片。因此,当我们调用该函数时,代码会在我们传入的特定值上运行。
总之,以下是我们用来将代码从清单 10-2 更改为清单 10-3 的步骤
- 识别重复的代码。
- 将重复的代码提取到函数的主体中,并在函数签名中指定该代码的输入和返回值。
- 更新重复代码的两个实例以改为调用该函数。
接下来,我们将使用相同的泛型步骤来减少代码重复。与函数主体可以对抽象的 list
而不是特定值进行操作的方式相同,泛型允许代码对抽象类型进行操作。
例如,假设我们有两个函数:一个查找 i32
值切片中的最大项,另一个查找 char
值切片中的最大项。我们如何消除这种重复?让我们来了解一下!