@ -1,15 +1,14 @@
## 泛型数据类型
<!-- https://github.com/rust - lang/book/blob/main/src/ch10 - 01 - syntax.md -->
<!-- commit 6c22ad745951671f9ee2689936881e62ef2a8069 -->
[ch10-01-syntax.md ](https://github.com/rust-lang/book/blob/3986f214fd82427b4401adf4d7dc0911c917e1e8/src/ch10-01-syntax.md )
我们可以使用泛型为像函数签名或结构体这样的项创建定义,这样它们就可以用于多种不同的具体数据类型。让我们看看如何使用泛型定义函数、结构体、枚举和方法,然后我们将讨论泛型 如何影响代码性能。
我们使用泛型来为函数签名或结构体之类的项创建定义,这样它们就可以配合多种不同的具体数据类型使用。先来看看如何使用泛型定义函数、结构体、枚举和方法。然后再讨论泛型会 如何影响代码性能。
### 在函数定义中使用泛型
当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。接着我们使用泛型将其合并为一个 函数。
回到 `largest` 函数。示例 10-4 展示了两个都用来寻找切片中最大值的函数。接着,我们会把它们合并成一个使用泛型的 函数。
< span class = "filename" > 文件名: src/main.rs< / span >
@ -21,15 +20,15 @@
`largest_i32` 函数是从示例 10-3 中摘出来的,它用来寻找 slice 中最大的 `i32` 。`largest_char` 函数寻找 slice 中最大的 `char` 。因为两者函数体的代码是一样的,我们可以定义一个函数,再引进泛型参数来消除这种重复。
为了参数化这个新函数中的这些类型,我们需要为类型参数命名,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 `T` , 因为传统上来说, Rust 的类型参数名字都比较短, 通常仅为一个字母, 同时, Rust 类型名的命名规范是首字母大写驼峰式命名法( UpperCamelCase) 。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选 。
为了给这个新函数中的类型做参数化,我们需要给类型参数命名,就像给函数的值参数命名一样。任何标识符都可以作为类型参数名。但这里我们使用 `T` , 因为按照惯例, Rust 中的类型参数名都很短,通常只有一个字母,而 Rust 类型名的命名约定是 UpperCamelCase。`T` 是 _type_ 的缩写,也是大多数 Rust 程序员的默认选择 。
如果要在函数体中使用参数,就必须在函数签名中声明它的名字,好让编译器知道这个名字指代的是什么。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中 ,像这样:
如果你要在函数体中使用某个参数,就必须先在函数签名中声明它的名字,让编译器知道这个名字表示什么。同理,当你在函数签名中使用类型参数名时,也必须先声明它。为了定义泛型版的 `largest` 函数,我们把类型名声明放在函数名和参数列表之间的尖括号 `<>` 里 ,像这样:
```rust,ignore
fn largest< T > (list: & [T]) -> & T {
```
可以这样理解这个定义:函数 `largest` 有泛型类型 `T` 。它有个参数 `list` ,其类型是元素为 `T` 的 slice。`largest` 函数会返回一个与 `T` 相同类型 的引用。
我们可以把这个定义读作:“函数 `largest` 对某个类型 `T` 是泛型的。”这个函数有一个名为 `list` 的参数,它是由 `T` 类型值组成的切片。`largest` 函数会返回一个指向同样类型 `T` 值 的引用。
示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译。
@ -47,11 +46,11 @@ fn largest<T>(list: &[T]) -> &T {
{{#include ../listings/ch10-generic-types-traits-and-lifetimes/listing-10-05/output.txt}}
```
帮助说明中提到了 `std::cmp::PartialOrd` ,这是一个 *trait* 。下一部分会讲到 trait。不过简单来说, 这个错误表明 `largest` 的函数体不能适用于 `T` 的所有可能的类型。因为在函数体需要比较 `T` 类型的值,不过它只能用于我们知道如何排序的类型。为了开启比较功能,标准库中定义的 `std::cmp::PartialOrd` trait 可以实现类型的比较功能(查看附录 C 获取该 trait 的更多信息)。依照帮助说明中的建议,我们限制 `T` 只对实现了 `PartialOrd` 的类型有效后代码就可以编译了,因为标准库 为 `i32` 和 `char` 实现了 `PartialOrd` 。
帮助信息里提到了 `std::cmp::PartialOrd` ,这是一个 trait, 我们会在下一节讨论 trait。现在先知道, 这个错误说明 `largest` 的函数体并不能适用于 `T` 所有可能的类型。因为我们想在函数体中比较 `T` 类型的值,所以只能使用那些值可以排序的类型。为了支持比较,标准库提供了 `std::cmp::PartialOrd` trait, 你可以为类型实现它( 更多内容见附录 C) 。要修复示例 10-5, 我们可以按照帮助信息的建议, 把 `T` 限制为只接受实现了 `PartialOrd` 的类型。这样代码就能编译,因为标准库已经 为 `i32` 和 `char` 实现了 `PartialOrd` 。
### 结构体定义中的泛型
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 定义了一个可以存放任何类型的 `x` 和 `y` 坐标值的结构体 `Point` :
我们也可以使用 `<>` 语法来定义结构体,让一个或多个字段使用泛型类型参数。示例 10-6 定义了一个 `Point<T>` 结构体,用来保存任意类型的 `x` 和 `y` 坐标值 :
< span class = "filename" > 文件名: src/main.rs< / span >
@ -89,7 +88,7 @@ fn largest<T>(list: &[T]) -> &T {
< span class = "caption" > 示例 10-8: 使用两个泛型的 `Point` ,这样 `x` 和 `y` 可能是不同类型</ span >
现在所有这些 `Point` 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你发现代码中需要很多泛型时,这可能表明你的代码需要重构分解成更小的结构 。
现在,示例里的 所有这些 `Point` 实例都合法了!你可以在定义中使用任意多个泛型类型参数,但如果超过几个,代码就会变得难以阅读。如果你发现自己在代码里需要很多泛型类型,那可能意味着这段代码应该被重构成更小的部分 。
### 枚举定义中的泛型
@ -119,7 +118,7 @@ enum Result<T, E> {
### 方法定义中的泛型
在为结构体和枚举实现方法时(像第五章那样),一样也可以用泛型。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>` ,和在其上实现的 名为 `x` 的方法。
我们可以像第五章那样为结构体和枚举实现方法,并在这些方法定义中使用泛型。示例 10-9 展示了示例 10-6 中定义的 `Point<T>` 结构体,以及在其上实现的一个 名为 `x` 的方法。
< span class = "filename" > 文件名: src/main.rs< / span >
@ -161,9 +160,9 @@ enum Result<T, E> {
### 泛型代码的性能
你可能会好奇使用泛型类型参数是否会有运行时消耗。好消息是泛型并不会使程序比具体类型运行得 慢。
你可能会好奇,使用泛型类型参数是否会带来运行时开销。好消息是:使用泛型不会让程序比使用具体类型运行得更 慢。
Rust 通过在编译时进行泛型代码的**单态化**( *monomorphization*)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程 。
Rust 通过在编译时对泛型代码进行**单态化**( *monomorphization*)来实现这一点。单态化就是把泛型代码转换成具体代码的过程,方法是用编译时实际用到的具体类型去填充泛型代码 。
在这个过程中,编译器所做的工作正好与示例 10-5 中我们创建泛型函数的步骤相反。编译器寻找所有泛型代码被调用的位置并使用泛型代码针对具体类型生成代码。