泛型、特质与生命周期

每种编程语言都有着用于有效处理重复概念的一些工具。在 Rust 中,一种这样的工具就是 泛型,generics:将一些具体类型或其他属性的替身抽象出来。对于在编译及运行代码时泛型处有着什么,咱们无需知悉就可以表达泛型的行为,或与其他泛型之间的关系,abstract stand-ins for concret types or other properties. We can express the bevavior of generics or how they relate to other generics without knowing what will be in their place when compiling and running the code。

与函数取一些未知值,以在多个具体值上运行函数体中同样代码的方式一样,其也可以取一些泛型的参数,而非像是 i32String 这样的具体类型。事实上,前面在第 6 章的 Option<T>,第 8 章的 Vec<T>HashMap<K, V>,还有第 9 章的 Result<T, E> 中,就已经用到了泛型特性。本章中,将探讨怎样定义咱们自己的、带有泛型特性的类型、函数及方法!

首先,这里会回顾怎样对函数进行凝练,从而减少代码重复。随后会使用同样技巧,来将两个只是参数类型不同的函数,构造为一个泛型函数。这里还会说明,怎样在结构体与枚举定义中使用泛型。

接着就会掌握怎样使用 特质,traits,来以泛型方式定义动作行为。可将特质与泛型结合,来将某个泛型约束为只接受有着特定行为的那些类型,而不再是任意类型。

最后,这里将讨论 生命周期,lifetimes:给到编译器有关引用之间关系信息的各种泛型。生命周期特性实现了给到编译器有关借用值的足够信息,从而在相比于没有咱们帮助而未给到这些信息时,编译器就能够于更多的不同情形下,确保这些引用的有效性。

通过提取出函数,而去除重复

泛型特性允许咱们以表示多种类型方式的占位符,替换掉特定类型,而消除代码重复。在进入到泛型语法之前,咱们先来看看,怎样以不涉及泛型的,而是用表示多个值的占位符替换特定值,提取出函数的方式消除重复。随后就会把这同样技巧,应用到提取泛型函数上!通过看到如何识别出可提取到函数中的重复代码,咱们就将开始识别出可使用泛型特性的重复代码。

这里会以下面清单 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! ("极大数为 {}", largest);
}

清单 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! ("极大数为 {}", 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! ("极大数为 {}", 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! ("极大数为 {}", result);

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

    let result = largest(&number_list);
    println! ("极大数为 {}", result);
}

清单 10-3:抽象后的找出两个清单中极大数的代码

这个 largest 函数有着一个名为 list 的参数,该参数表示了任意的、可能传入到该函数的一些 i32 值的切片。那么由此而来,在调用该函数时,该代码就会运行在所传入的那些特定值上。

总的来说,以下就是将代码从清单 10-2 修改为清单 10-3 所用的步骤:

  1. 识别出重复代码;
  2. 将重复代码提取到目标函数的函数体中,并在函数签名中指定重复代码的输入与输出值;
  3. 将重复代码的两个实例,更新为调用这个提取出的函数。

接下来,就要在泛型下,使用这些同样步骤来降低代码重复了。与函数体可以在抽象的 list, 而非具体值上运作的方式一样,泛型实现了代码在抽象类型上的操作。

比如,假设说这里有两个函数:一个时在 i32 值的切片中,找出极大项,而另一个是在 char 值的切片中,找出极大项。那该怎样消除重复呢?下面就来解决这个问题!

Last change: 2023-11-30, commit: fd9c605