使用泛型复用数据结构

RustBeginner
立即练习

简介

当数据只有一种固定结构时,具体类型很有用。但如果为数字和文本重复定义几乎相同的类型,程序就会变得难以维护。你已经使用过 Option<u32>Vec<String>HashMap<String, u32>Result<T, E> 等标准泛型类型。在本实验中,你将亲自定义一个泛型类型。Rust 泛型可以让一个定义适用于多种具体类型,同时由编译器检查每次使用是否正确。

在本实验中,你将把准备好的 Pair 结构转换为泛型结构,然后将一个小型借用辅助函数也改为泛型。较长的程序和示例数据已经准备好,因此你只需关注少数几个能够实现复用的类型位置。

让 Pair 结构支持泛型

在本步骤中,你将把结构中固定的 i32 类型替换为一个类型参数,使同一个定义既能保存分数,也能保存路线名称。

进入准备好的 Cargo 包目录:

cd /home/labex/project/pair-demo

查看源文件。sed -n 命令只会打印指定的行范围,不会修改文件:

sed -n '1,120p' src/main.rs

scores 值包含整数,而 routes 包含拥有所有权的 String 值。当前的 Pair 定义将两个字段都固定为 i32,因此无法同时表示这两种值。准备好的 #[derive(Debug)] 属性可以为该类型生成调试格式化实现,但程序中没有使用这种格式化功能;它不属于本次泛型练习的内容。

泛型类型会在尖括号中引入类似 T 的占位符。每个构造出的 Pair 仍然具有一个具体类型:编译器会为 scores 推断出 Pair<i32>,为 routes 推断出 Pair<String>

使用 Nano 打开源文件:

nano src/main.rs

在文件顶部附近完成以下三处修改:

  • struct Pair { 改为 struct Pair<T> {
  • left 的类型从 i32 改为 T
  • right 的类型从 i32 改为 T

修改完成后的结构如下:

struct Pair<T> {
    left: T,
    right: T,
}

这里两个字段使用同一个 T,因此一个二元组不会意外混用互不相关的字段类型。按 Ctrl+O 保存,按 Enter 确认,然后按 Ctrl+X 退出。

让 Cargo 检查该包的类型:

cargo check

如果看到以 Finished 结尾的行,说明编译器已经接受了 Pair<i32>Pair<String>。现在运行程序:

cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82

前两行表明,同一个结构定义现在可以保存两种不同的具体类型。现有的 first 辅助函数仍然只接受 Pair<i32>,下一步将把它改为泛型函数。

让借用辅助函数支持泛型

在本步骤中,你将为 first 函数添加自己的类型参数,使它能够借用任意 Pair<T>,并返回其第一个值的引用。

查看文件顶部附近当前的辅助函数:

sed -n '1,30p' src/main.rs

它的输入类型 Pair<i32> 和输出类型 &i32 将函数限制为只能处理分数。泛型函数会在函数名后声明 <T>,然后在相关输入类型和输出类型中使用同一个 T

打开文件:

nano src/main.rs

只修改函数声明,将:

fn first(pair: &Pair<i32>) -> &i32 {

改为:

fn first<T>(pair: &Pair<T>) -> &T {

该函数借用二元组并返回其中一个字段的借用引用,因此既不会移动也不会复制存储的值。Rust 可以根据每次传入的参数推断 T,调用者不需要显式写出类型。

接下来,将 // FIRST_ROUTE_REPORT 替换为下面这次调用,以证明该辅助函数也能处理文本:

    println!("first route: {}", first(&routes));

保存并退出 Nano。然后检查类型并运行完整程序:

cargo check
cargo run --quiet
scores: 82 and 91
routes: north and east
first score: 82
first route: north

两行 first 输出都来自同一个泛型函数。Rust 会为分数调用推断 T = i32,为路线调用推断 T = String,然后分别使用对应的具体类型检查每次调用。代码复用并不会牺牲静态类型检查。

总结

你将一个具体的二元组替换为 Pair<T>,让它同时处理整数和字符串,并将一个借用辅助函数改为泛型函数,而无需引入 trait 或复制实现。