|
|
|
@ -8,9 +8,9 @@
|
|
|
|
|
|
|
|
|
|
### 在函数定义中使用泛型
|
|
|
|
|
|
|
|
|
|
当使用泛型定义函数时,我们在函数签名中通常为参数和返回值指定数据类型的位置放置泛型。以这种方式编写的代码将更灵活并能向函数调用者提供更多功能,同时不引入重复代码。
|
|
|
|
|
当使用泛型定义函数时,本来在函数签名中指定参数和返回值的类型的地方,会改用泛型来表示。采用这种技术,使得代码适应性更强,从而为函数的调用者提供更多的功能,同时也避免了代码的重复。
|
|
|
|
|
|
|
|
|
|
回到 `largest` 函数上,示例 10-4 中展示了两个提供了相同的寻找 slice 中最大值功能的函数。
|
|
|
|
|
回到 `largest` 函数,示例 10-4 中展示了两个函数,它们的功能都是寻找 slice 中最大值。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -54,21 +54,21 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-4:两个只在名称和签名中类型有所不同的函数</span>
|
|
|
|
|
<span class="caption">示例 10-4:两个函数,不同点只是名称和签名类型</span>
|
|
|
|
|
|
|
|
|
|
`largest_i32` 函数是从示例 10-3 中提取的寻找 slice 中 `i32` 最大值的函数。`largest_char` 函数寻找 slice 中 `char` 的最大值:这两个函数有着相同的代码,所以让我们在一个单独的函数中引入泛型参数来消除重复。
|
|
|
|
|
`largest_i32` 函数是从示例 10-3 中摘出来的,它用来寻找 slice 中最大的 `i32`。`largest_char` 函数寻找 slice 中最大的 `char`。因为两者函数体的代码是一样的,我们可以定义一个函数,再引进泛型参数来消除这种重复。
|
|
|
|
|
|
|
|
|
|
为了参数化要定义的函数的签名中的类型,我们需要像给函数的值参数起名那样为这类型参数起一个名字。任何标识符都可以作为类型参数名。不过选择 `T` 是因为 Rust 的习惯是让变量名尽量短,通常就只有一个字母,同时 Rust 类型命名规范是骆驼命名法(CamelCase)。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
|
|
|
|
|
为了参数化新函数中的这些类型,我们也需要为类型参数取个名字,道理和给函数的形参起名一样。任何标识符都可以作为类型参数的名字。这里选用 `T`,因为传统上来说,Rust 的参数名字都比较短,通常就只有一个字母,同时,Rust 类型名的命名规范是骆驼命名法(CamelCase)。`T` 作为 “type” 的缩写是大部分 Rust 程序员的首选。
|
|
|
|
|
|
|
|
|
|
当需要在函数体中使用一个参数时,必须在函数签名中声明这个参数以便编译器能知道函数体中这个名称的意义。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中,像这样:
|
|
|
|
|
如果要在函数体中使用参数,就必须在函数签名中声明它的名字,好让编译器知道这个名字指代的是什么。同理,当在函数签名中使用一个类型参数时,必须在使用它之前就声明它。为了定义泛型版本的 `largest` 函数,类型参数声明位于函数名称与参数列表中间的尖括号 `<>` 中,像这样:
|
|
|
|
|
|
|
|
|
|
```rust,ignore
|
|
|
|
|
fn largest<T>(list: &[T]) -> T {
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
这可以理解为:函数 `largest` 有泛型类型 `T`。它有一个参数 `list`,它的类型是一个 `T` 值的 slice。`largest` 函数将会返回一个与 `T` 相同类型的值。
|
|
|
|
|
可以这样理解这个定义:函数 `largest` 有泛型类型 `T`。它有个参数 `list`,其类型是元素为 `T` 的 slice。`largest` 函数的返回值类型也是 `T`。
|
|
|
|
|
|
|
|
|
|
示例 10-5 展示一个在签名中使用了泛型的统一的 `largest` 函数定义。该示例也向我们展示了如何对 `i32` 值的 slice 或 `char` 值的 slice 调用 `largest` 函数。注意这些代码还不能编译,不过本章稍后部分会修复错误。
|
|
|
|
|
示例 10-5 中的 `largest` 函数在它的签名中使用了泛型,统一了两个实现。该示例也展示了如何调用 `largest` 函数,把 `i32` 值的 slice 或 `char` 值的 slice 传给它。请注意这些代码还不能编译,不过稍后在本章会解决这个问题。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -98,9 +98,9 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-5:一个还不能编译的使用泛型参数的 `largest` 函数定义</span>
|
|
|
|
|
<span class="caption">示例 10-5:一个使用泛型参数的 `largest` 函数定义,尚不能编译</span>
|
|
|
|
|
|
|
|
|
|
如果现在就尝试编译这些代码,会出现如下错误:
|
|
|
|
|
如果现在就编译这个代码,会出现如下错误:
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
error[E0369]: binary operation `>` cannot be applied to type `T`
|
|
|
|
@ -118,7 +118,7 @@ error[E0369]: binary operation `>` cannot be applied to type `T`
|
|
|
|
|
|
|
|
|
|
### 结构体定义中的泛型
|
|
|
|
|
|
|
|
|
|
同样也可以使用 `<>` 语法来定义拥有一个或多个泛型参数类型字段的结构体。示例 10-6 展示了如何定义和使用一个可以存放任何类型的 `x` 和 `y` 坐标值的结构体 `Point`:
|
|
|
|
|
同样也可以用 `<>` 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例 10-6 展示了如何定义和使用一个可以存放任何类型的 `x` 和 `y` 坐标值的结构体 `Point`:
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
@ -153,7 +153,7 @@ fn main() {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-7:字段 `x` 和 `y` 必须是相同类型,因为他们都有相同的泛型类型 `T`</span>
|
|
|
|
|
<span class="caption">示例 10-7:字段 `x` 和 `y` 的类型必须相同,因为他们都有相同的泛型类型 `T`</span>
|
|
|
|
|
|
|
|
|
|
在这个例子中,当把整型值 5 赋值给 `x` 时,就告诉了编译器这个 `Point<T>` 实例中的泛型 `T` 是整型的。接着指定 `y` 为 4.0,它被定义为与 `x` 相同类型,就会得到一个像这样的类型不匹配错误:
|
|
|
|
|
|
|
|
|
@ -188,11 +188,11 @@ fn main() {
|
|
|
|
|
|
|
|
|
|
<span class="caption">示例 10-8:使用两个泛型的 `Point`,这样 `x` 和 `y` 可能是不同类型</span>
|
|
|
|
|
|
|
|
|
|
现在所有这些 `Point` 实例都是被允许的了!你可以在定义中使用任意多的泛型类型参数,不过太多的话代码将难以阅读和理解。当你的代码中需要许多泛型类型时,它可能表明你的代码需要重组为更小的部分。
|
|
|
|
|
现在所有这些 `Point` 实例都合法了!你可以在定义中使用任意多的泛型类型参数,不过太多的话,代码将难以阅读和理解。当你的代码中需要许多泛型类型时,它可能表明你的代码需要重构,分解成更小的结构。
|
|
|
|
|
|
|
|
|
|
### 枚举定义中的泛型
|
|
|
|
|
|
|
|
|
|
类似于结构体,枚举也可以在其成员中存放泛型数据类型。第六章我们使用过了标准库提供的 `Option<T>` 枚举,让我们再看看:
|
|
|
|
|
和结构体类似,枚举也可以在成员中存放泛型数据类型。第六章我们曾用过标准库提供的 `Option<T>` 枚举,这里再回顾一下:
|
|
|
|
|
|
|
|
|
|
```rust
|
|
|
|
|
enum Option<T> {
|
|
|
|
@ -201,7 +201,7 @@ enum Option<T> {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
现在这个定义看起来就更容易理解了。如你所见 `Option<T>` 是一个拥有泛型 `T` 的枚举,它有两个成员:`Some`,它存放了一个类型 `T` 的值,和不存在任何值的`None`。通过 `Option<T>` 枚举可以表达有一个可能的值的抽象概念,同时因为 `Option<T>` 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。
|
|
|
|
|
现在这个定义应该更容易理解了。如你所见 `Option<T>` 是一个拥有泛型 `T` 的枚举,它有两个成员:`Some`,它存放了一个类型 `T` 的值,和不存在任何值的`None`。通过 `Option<T>` 枚举可以表达有一个可能的值的抽象概念,同时因为 `Option<T>` 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。
|
|
|
|
|
|
|
|
|
|
枚举也可以拥有多个泛型类型。第九章使用过的 `Result` 枚举定义就是一个这样的例子:
|
|
|
|
|
|
|
|
|
@ -212,15 +212,13 @@ enum Result<T, E> {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`Result` 枚举有两个泛型类型,`T` 和 `E`。`Result` 有两个成员:`Ok`,它存放一个类型 `T` 的值,而 `Err` 则存放一个类型 `E` 的值。这个定义使得 `Result` 枚举能很方便的表达任何可能成功(返回 `T` 类型的值)也可能失败(返回 `E` 类型的值)的操作。回忆一下示例 9-3 中打开一个文件的场景:当文件被成功打开 `T` 被放入了 `std::fs::File` 类型而当打开文件出现问题时 `E` 被放入了 `std::io::Error` 类型。
|
|
|
|
|
`Result` 枚举有两个泛型类型,`T` 和 `E`。`Result` 有两个成员:`Ok`,它存放一个类型 `T` 的值,而 `Err` 则存放一个类型 `E` 的值。这个定义使得 `Result` 枚举能很方便的表达任何可能成功(返回 `T` 类型的值)也可能失败(返回 `E` 类型的值)的操作。实际上,这就是我们在示例 9-3 用来打开文件的方式:当成功打开文件的时候,`T` 对应的是 `std::fs::File` 类型;而当打开文件时出现问题时,`E` 的值则是 `std::io::Error` 类型。
|
|
|
|
|
|
|
|
|
|
当发现代码中有多个只有存放的值的类型有所不同的结构体或枚举定义时,你就应该像之前的函数定义中那样引入泛型类型来减少重复代码。
|
|
|
|
|
当你意识到代码中定义了多个结构体或枚举,它们不一样的地方只是其中的值的类型的时候,不妨通过泛型类型来避免重复。
|
|
|
|
|
|
|
|
|
|
### 方法定义中的泛型
|
|
|
|
|
|
|
|
|
|
也可以在定义中使用泛型在结构体和枚举上实现方法(像第五章那样)。
|
|
|
|
|
|
|
|
|
|
可以像第五章介绍的那样来为其定义中带有泛型的结构体或枚举实现方法。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>`,和在其上实现的名为 `x` 的方法。
|
|
|
|
|
在为结构体和枚举实现方法时(像第五章那样),一样也可以用泛型。示例 10-9 中展示了示例 10-6 中定义的结构体 `Point<T>`,和在其上实现的名为 `x` 的方法。
|
|
|
|
|
|
|
|
|
|
<span class="filename">文件名: src/main.rs</span>
|
|
|
|
|
|
|
|
|
|