mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2026-08-19 04:33:27 +08:00
Updated src/generic_types_traits_and_lifetimes/generics.md'.
This commit is contained in:
6
projects/generic_func/Cargo.toml
Normal file
6
projects/generic_func/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "generic_func"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
31
projects/generic_func/src/main.rs
Normal file
31
projects/generic_func/src/main.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use std::cmp::PartialOrd;
|
||||||
|
|
||||||
|
fn largest<T: PartialOrd>(list: &[T]) -> &T {
|
||||||
|
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 char_list = vec! ['y', 'm', 'a', 'q'];
|
||||||
|
|
||||||
|
let result = largest(&char_list);
|
||||||
|
println! ("最大字符为 {result}");
|
||||||
|
|
||||||
|
let str_list = vec! ["one", "two", "three"];
|
||||||
|
|
||||||
|
let result = largest(&str_list);
|
||||||
|
println! ("最大字符串为 {result}");
|
||||||
|
|
||||||
|
}
|
||||||
6
projects/generic_method/Cargo.toml
Normal file
6
projects/generic_method/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "generic_method"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
22
projects/generic_method/src/main.rs
Normal file
22
projects/generic_method/src/main.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
struct Point<T> {
|
||||||
|
x: T,
|
||||||
|
y: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Point<T> {
|
||||||
|
fn x(&self) -> &T {
|
||||||
|
&self.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point<f32, f32> {
|
||||||
|
fn distance_from_origin(&self) -> f32 {
|
||||||
|
(self.x.powi(2) + self.y.powi(2)).sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let p = Point { x: 5, y: 10 };
|
||||||
|
|
||||||
|
println!("p.x = {}", p.x());
|
||||||
|
}
|
||||||
6
projects/generic_struct/Cargo.toml
Normal file
6
projects/generic_struct/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "generic_struct"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
17
projects/generic_struct/src/main.rs
Normal file
17
projects/generic_struct/src/main.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
struct Point<T, U> {
|
||||||
|
x: T,
|
||||||
|
y: U,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let both_integer = Point { x: 5, y: 10 };
|
||||||
|
let both_float = Point { x: 1.0, y: 4.0 };
|
||||||
|
let integer_and_float = Point { x: 5, y: 4.0 };
|
||||||
|
|
||||||
|
println! ("
|
||||||
|
{both_integer:?}
|
||||||
|
{both_float:?}
|
||||||
|
{integer_and_float:?}
|
||||||
|
");
|
||||||
|
}
|
||||||
@@ -1,22 +1,17 @@
|
|||||||
# 通用数据类型
|
# 通用数据类型
|
||||||
|
|
||||||
**Generic Data Types**
|
我们使用泛型来创建函数签名或结构体等项目的定义,然后咱们可以对许多不同的具体数据类型使用他们。咱们来首先看看怎样使用泛型定义函数、结构体、枚举及方法等。然后,我们将讨论到泛型会怎样影响代码性能。
|
||||||
|
|
||||||
这里会使用泛型,来创建诸如函数签名或结构体等的定义,随后咱们便可以将这些定义,用于许多不同的具体数据类型。首先咱们来看看,怎样运用泛型特性来定义函数、结构体、枚举及方法等。接下来就会讨论到,泛型如何影响到代码性能。
|
|
||||||
|
|
||||||
|
|
||||||
## 函数定义方面
|
## 函数定义方面
|
||||||
|
|
||||||
**In Function Definitions**
|
在定义使用泛型的函数时,我们放置泛型于函数的签名中,我们通常将于其中指定参数与返回值的数据类型。这样做使我们的代码更灵活,并在防止代码重复的同时,提供更多功能给咱们函数的调用者。
|
||||||
|
|
||||||
|
继续我们的 `largest` 函数,下面清单 10-4 显示了两个函数,均为找出切片中最大值。我们随后将合并这两个函数为使用泛型的单个函数。
|
||||||
|
|
||||||
在定义用到泛型的函数时,就要把泛型放在咱们通常于其中,指明参数与返回值数据类型的函数签名中。这样做就会在阻止代码重复的同时,令到代码更为灵活,同时提供到更多功能给咱们函数的调用者。
|
<a name="listing_10-4"></a>
|
||||||
|
|
||||||
继续之前的 `largest` 函数,下面清单 10-4 给出了两个均为找出某个切片中极大值的函数。这随后就要将这两个函数,合并为使用泛型特性的单个函数。
|
|
||||||
|
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn largest_i32(list: &[i32]) -> &i32 {
|
fn largest_i32(list: &[i32]) -> &i32 {
|
||||||
let mut largest = &list[0];
|
let mut largest = &list[0];
|
||||||
@@ -46,31 +41,32 @@ fn main() {
|
|||||||
let number_list = vec! [34, 50, 25, 100, 65];
|
let number_list = vec! [34, 50, 25, 100, 65];
|
||||||
|
|
||||||
let result = largest_i32(&number_list);
|
let result = largest_i32(&number_list);
|
||||||
println! ("极大数为 {}", result);
|
println! ("最大数为 {result}");
|
||||||
|
|
||||||
let char_list = vec! ['y', 'm', 'a', 'q'];
|
let char_list = vec! ['y', 'm', 'a', 'q'];
|
||||||
|
|
||||||
let result = largest_char(&char_list);
|
let result = largest_char(&char_list);
|
||||||
println! ("极大字符为 {}", result);
|
println! ("最大字符为 {result}");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-4:两个只是名字与签名中类型不同的函数*
|
**清单 10-4**:仅在名字和签名类型上不同的两个函数
|
||||||
|
|
||||||
其中的 `largest_i32` 函数,即为在清单 10-3 中所提取出的那个,找出某个切片中最大的 `i32` 函数。而这里的 `largest_char` 函数则是找出某个切片中的极大 `char`。由于这两个函数体有着同样代码,因此这里就要通过在单个函数中,引入泛型参数来消除重复。
|
`largest_i32` 函数是我们在 [清单 10-3](../Ch10_Generic_Types_Traits_and_Lifetimes.md#listing_10-3) 中提取的函数,会找出切片中最大的 `i32` 函数。`largest_char` 函数会找出切片中的最大 `char`。两个函数体有着同样的代码,因此让我们通过在单个函数中引入泛型参数来消除重复。
|
||||||
|
|
||||||
为将新单一函数中的类型参数化,咱们需要给类型参数命名,就如同咱们对某个函数的那些实参(值参数),the value parameters,所做的那样。可将任意标识符,用作类型参数名字。不过咱们将使用 `T`,这是因为根据约定,Rust 中的参数名字都是简短的,通常只有一个字母,还因为 Rust 的类型命名约定为驼峰式大小写命名规则(CamelCase)。而 `T` 作为 “type” 的简写,其便是大多数 Rust 程序员的默认选择了。
|
为了参数化新的单个函数中的类型,咱们需要命名类型参数,就像我们对函数的值参数,the value parameters,所做的那样。咱们可以使用任何标识符作为类型参数的名字。但咱们将使用 `T`,因为根据约定,Rust 中的类型参数名字要短,通常只有一个字母,并且 Rust 的类型命名约定,type-naming convention,为驼峰式大小写命名规则,UpperCamelCase。`T` 为 *type* 的缩写,便是大多数 Rust 程序员的默认选择。
|
||||||
|
|
||||||
当咱们要在函数体中,用到某个参数时,咱们必须在函数签名中声明出这个参数,如此编译器便知道那个名字表示什么。与此类似,当咱们要在函数签名中,用到某个类型参数名字时,在使用该类型参数之前,咱们必须声明出这个类型参数。要定义这个泛型的 `largest` 函数,就要把类型名字声明,放在尖括号(`<>`)里,于函数名字与参数列表之间,如下所示:
|
当我们在函数体中使用参数时,我们必须在签名中声明出参数名字,以便编译器知道该名字表示什么。同样,当咱们在函数签名中使用类型参数名字时,我们必须在使用他之前声明出类型参数名字。为了定义通用的 `largest` 函数,我们就要放置类型名字声明于函数名字与参数列表之间的尖括号,`<>` 内,如下所示:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn largest<T>(list: &<T>) -> &T {
|
fn largest<T>(list: &<T>) -> &T {
|
||||||
```
|
```
|
||||||
|
|
||||||
咱们把这个定义读作:函数 `largest` 对某个类型 `T` 通用(the function `largest` is generic over some type `T`)。该函数有着一个名为 `list` 的参数,其为类型 `T` 值切片。`largest` 函数将返回一个到同样类型 `T` 值的引用。
|
我们把这个定义读作:“函数 `largest` 对某一类型 `T` 通用,the function `largest` is generic over some type `T`”。这个函数有着一个名为 `list` 的参数,其为类型 `T` 的值的切片。`largest` 函数将返回一个对同一类型 `T` 的值的引用。
|
||||||
|
|
||||||
下面清单 10-5 给出了这个在其签名中用到通用数据类型的合并 `largest` 函数的定义。这个清单还展示了咱们可以怎样使用 `i32` 值切片,或 `char` 值切片调用该函数。请注意此代码尚不会编译,但咱们将在本章后面修复他。
|
下面清单 10-5 显示了在其签名中使用通用数据类型合并后的 `largest` 函数的定义。这个清单还展示了咱们可以怎样以一个 `i32` 的值或 `char` 的值的切片调用该函数。请注意这段代码尚不会编译。
|
||||||
|
|
||||||
|
<a name="listing_10-5"></a>
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -90,22 +86,22 @@ fn main() {
|
|||||||
let number_list = vec! [34, 50, 25, 100, 65];
|
let number_list = vec! [34, 50, 25, 100, 65];
|
||||||
|
|
||||||
let result = largest(&number_list);
|
let result = largest(&number_list);
|
||||||
println! ("极大数为 {}", result);
|
println! ("最大数为 {result}");
|
||||||
|
|
||||||
let char_list = vec! ['y', 'm', 'a', 'q'];
|
let char_list = vec! ['y', 'm', 'a', 'q'];
|
||||||
|
|
||||||
let result = largest(&char_list);
|
let result = largest(&char_list);
|
||||||
println! ("极大字符为 {}", result);
|
println! ("最大字符为 {result}");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-5:使用泛型参数的 `largest` 函数;此代码尚不会编译*
|
**清单 10-5**:使用泛型参数的 `largest` 函数;这段代码尚不编译
|
||||||
|
|
||||||
现在编译此代码,将得到如下错误信息:
|
当我们现在编译这段代码,我们将得到下面这个报错:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ cargo run lennyp@vm-manjaro
|
$ cargo run
|
||||||
Compiling generics_demo v0.1.0 (/home/lennyp/rust-lang/generics_demo)
|
Compiling generic_func v0.1.0 (/home/hector/rust-lang-zh_CN/projects/generic_func)
|
||||||
error[E0369]: binary operation `>` cannot be applied to type `&T`
|
error[E0369]: binary operation `>` cannot be applied to type `&T`
|
||||||
--> src/main.rs:5:17
|
--> src/main.rs:5:17
|
||||||
|
|
|
|
||||||
@@ -114,25 +110,23 @@ error[E0369]: binary operation `>` cannot be applied to type `&T`
|
|||||||
| |
|
| |
|
||||||
| &T
|
| &T
|
||||||
|
|
|
|
||||||
help: consider restricting type parameter `T`
|
help: consider restricting type parameter `T` with trait `PartialOrd`
|
||||||
|
|
|
|
||||||
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
|
||||||
| ++++++++++++++++++++++
|
| ++++++++++++++++++++++
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0369`.
|
For more information about this error, try `rustc --explain E0369`.
|
||||||
error: could not compile `generics_demo` due to previous error
|
error: could not compile `generic_func` (bin "generic_func") due to 1 previous error
|
||||||
```
|
```
|
||||||
|
|
||||||
帮助文本消息提到 `std::cmp::PartialOrd`,其是个 *特质(trait)*,而咱们在下一小节就会讲到特质。至于现在,明白这个报错指出了,`largest` 函数体不会对所有 `T` 可能的类型工作就行。由于咱们是要在该函数体中,比较两个类型 `T` 的值,那么咱们就只能使用值可被排序的类型。为能进行比较,标准库便有这个咱们可在类型上应用的 `std::cmp::PartialOrd` 特质(请参阅附录 C 了解该特质的更多信息)。按照该帮助信息的建议,咱们就要把对 `T` 有效的类型,限制为仅那些实现了 `PartialOrd` 的类型,而由于标准库在 `i32` 与 `char` 上,均实现了 `PartialOrd` 特质,那么这个示例就会编译了。
|
帮助文本提到了 `std::cmp::PartialOrd`,其是个特质,而我们即将在下一小节中讨论特质。现在只需知道,这个报错指出 `largest` 的主体将不工作于 `T` 可能的所有可能类型。因为我们打算在函数主体中比较类型 `T` 的值,所以我们只能使用值可以排序的类型。为了支持比较操作,标准库提供了 `std::cmp::PartialOrd` 特质,咱们可以对类型实现他(请参阅 [附录 C](../appendix/derivable_traits.md#用于排序比较的-partialord-与-ord) 了解有关这一特质的更多信息)。要修复清单 10-5,我们可以按照帮助文本的建议,限制 `T` 的有效类型为仅实现 `PartialOrd` 的类型。该清单随后将编译,因为标准库在 `i32` 与 `char` 上均实现了 `PartialOrd`。
|
||||||
|
|
||||||
|
|
||||||
## 结构体定义方面
|
## 结构体定义方面
|
||||||
|
|
||||||
**In Struct Definitions**
|
我们也可以使用 `<>` 语法,定义结构体为在一个或多个字段中使用泛型参数。下面清单 10-6 定义了一个 `Point<T>` 结构体来保存任意类型的 `x` 与 `y` 坐标值。
|
||||||
|
|
||||||
|
|
||||||
咱们也可使用这种 `<>` 语法,将结构体定义为在其一个或多个字段中使用泛型参数。清单 10-6 定义了一个 `Point<T>` 的结构体,来保存任意类型的 `x` 与 `y` 坐标值。
|
|
||||||
|
|
||||||
|
<a name="listing_10-6"></a>
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -147,12 +141,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-6:保存类型 `T` 的 `x` 与 `y` 值的 `Point<T>` 结构体*
|
**清单 10-6**:保存类型 `T` 的 `x` 与 `y` 值的 `Point<T>` 结构体
|
||||||
|
|
||||||
在结构体定义中使用泛型特性的语法,与在函数定义中用到的类似。首先,在紧接着结构体名字之后,咱们于尖括号内部,声明了类型参数的名字。随后咱们在原本指明具体类型的结构体定义中,用到了那个泛型。
|
在结构体定义中使用泛型的语法与在函数定义中用到的类似。首先,我们在紧接着结构体名字后的尖括号内声明类型参数的名字。然后,咱们在结构体定义中,原本指定具体类型之处使用泛型类型。
|
||||||
|
|
||||||
请注意由于咱们只使用了一个泛型来定义 `Point<T>`,那么这个定义就是说,`Point<T>` 结构体对某些类型 `T` 通用,且不论那种类型为何,字段 `x` 与 `y` *均为* 那同一类型。当咱们要创建有着不同类型值的某个 `Point<T>` 时,如下面清单 10-7 中,那么咱们的代码就不会编译。
|
请注意,由于咱们仅使用了一种泛型类型来定义 `Point<T>`,所以这一定义表明,`Point<T>` 结构体对某一类型 `T` 是通用的,并且字段 `x` 和 `y` 均为同一类型,无论该类型为何。当我们创建一个有着不同类型值的 `Point<T>`,如下面清单 10-7 中所示,我们的代码将不编译。
|
||||||
|
|
||||||
|
<a name="listing_10-7"></a>
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -166,13 +161,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-7:由于字段 `x` 与 `y` 有着同一泛型数据类型 `T`,因此他们必须为同一类型*
|
**清单 10-7**:字段 `x` 与 `y` 必须是同一类型,因为他们都有着相同的通用数据类型 `T`
|
||||||
|
|
||||||
在此示例中,当咱们把整数值 `5` 赋值给 `x` 时,咱们就让编译器明白,这个 `Point<T>` 实例的泛型 `T` 将是个整数。随后在咱们把 `4.0` 指定给那个已被咱们定义为与 `x` 有着同一类型的 `y` 时,咱们将得到一个下面这样的类型不匹配错误:
|
在这个示例中,当咱们指派整数值 `5` 给 `x` 时,咱们就让编译器知道了,这个 `Point<T>` 实例的泛型类型 `T` 将是个整数。然后,当我们为咱们已定义其与 `x` 有着同一类型的 `y` 指定 `4.0` 时,咱们将得到下面这样的类型不匹配错误:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ cargo run lennyp@vm-manjaro
|
$ cargo run
|
||||||
Compiling generics_demo v0.1.0 (/home/lennyp/rust-lang/generics_demo)
|
Compiling generic_struct v0.1.0 (/home/hector/rust-lang-zh_CN/projects/generic_struct)
|
||||||
error[E0308]: mismatched types
|
error[E0308]: mismatched types
|
||||||
--> src/main.rs:7:38
|
--> src/main.rs:7:38
|
||||||
|
|
|
|
||||||
@@ -180,11 +175,12 @@ error[E0308]: mismatched types
|
|||||||
| ^^^ expected integer, found floating-point number
|
| ^^^ expected integer, found floating-point number
|
||||||
|
|
||||||
For more information about this error, try `rustc --explain E0308`.
|
For more information about this error, try `rustc --explain E0308`.
|
||||||
error: could not compile `generics_demo` due to previous error
|
error: could not compile `generic_struct` (bin "generic_struct") due to 1 previous error
|
||||||
```
|
```
|
||||||
|
|
||||||
要定义出其中 `x` 与 `y` 同时为泛型,又可以有着不同类型的 `Point` 结构体,咱们可使用多个泛型参数。比如,在下面清单 10-8 中,就将 `Point` 的定义,修改为了对类型 `T` 与 `U` 通用,其中 `x` 为类型 `T`,而 `y` 则是类型 `U`。
|
要定义一个 `Point` 结构体,其中 `x` 与 `y` 均为泛型但可以有着不同类型,我们可以使用多个泛型类型参数。比如,在下面清单 10-8 中,我们修改 `Point` 的定义为对类型 `T` 与 `U` 泛型,其中 `x` 为类型 `T`,`y` 为类型 `U`。
|
||||||
|
|
||||||
|
<a name="listing_10-8"></a>
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -200,17 +196,14 @@ fn main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-8:对两种类型通用的 `Point<T, U>`,进而 `x` 与 `y` 可以是不同类型的值*
|
**清单 10-8**:对两种类型泛型的 `Point<T, U>`,从而 `x` 与 `y` 可以是不同类型的值
|
||||||
|
|
||||||
现在上面给出的全部 `Point` 实例,便都是允许的了!咱们可在某个定义中,使用咱们想要泛型参数个数,不过用多了就会令到代码难于阅读。若发现代码中需要很多泛型,那就可能表示咱们的代码,需要重新组织架构为更小的片段了。
|
现在展示的所有 `Point` 实例都是允许的!咱们可在定义中使用任意数量的泛型类型参数,但使用过多会使咱们的代码难于阅读。当咱们发现代码中需要大量泛型时,则可能表明咱们的代码需要重新组织为一些更小的部分。
|
||||||
|
|
||||||
|
|
||||||
## 枚举定义方面
|
## 枚举定义方面
|
||||||
|
|
||||||
**In Enum Definitions**
|
正如我们对结构体所做的那样,咱们可以定义枚举为他们的变种中保存通用数据类型。咱们再来看看标准库提供的 `Option<T>` 枚举,咱们曾在第 6 章中使用过他:
|
||||||
|
|
||||||
|
|
||||||
如同咱们在结构体下所做的那样,咱们可定义出在其变种中,保存一些通用数据类型的枚举。咱们来换个角度看看,咱们在第 6 章中曾使用过的,标准库所提供的 `Option<T>`:
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
enum Option<T> {
|
enum Option<T> {
|
||||||
@@ -219,9 +212,9 @@ enum Option<T> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
对咱们来说,这个定义现在应有着更多意涵了。可以看到,`Option<T>` 枚举对类型 `T` 是通用的,并有着两个变种:保存着一个类型 `T` 值的 `Some`,与一个不保存任何值的 `None` 变种。经由使用这个 `Option<T>` 枚举,咱们便可表达出可选值,an optional value,的抽象概念,而由于 `Option<T>` 是通用的,因此咱们就可以在无关乎该可选值为何种类型下,用到这个抽象。
|
这个定义现在对咱们来说应该更有意义了。正如咱们所见,`Option<T>` 枚举对类型 `T` 是通用的,并有着两个变种:`Some`,保存着类型 `T` 的一个值,以及 `None`,不保存任何值。通过使用 `Option<T>` 枚举,咱们可以表达可选值的抽象概念,the abstract concept of an optional value,而由于 `Option<T>` 是泛型的,所以无论可选值是何种类型,咱们都可以使用这种抽象。
|
||||||
|
|
||||||
枚举也可以使用多个泛型。在第 9 章中用到的 `Result` 枚举定义,就是一个示例:
|
枚举也可以使用多个泛型类型。我们曾在第 9 章中用到的 `Result` 枚举的定义就是一个示例:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
enum Result <T, E> {
|
enum Result <T, E> {
|
||||||
@@ -230,51 +223,48 @@ enum Result <T, E> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`Result` 枚举对 `T` 和 `E` 两种类型通用,并有着两个变种:保存了一个类型 `T` 值的 `Ok`,与保存了一个类型 `E` 值的 `Err`。这个定义使得在某个操作可能成功(便返回某种类型 `T` 的一个值),或失败(便返回一个某种类型 `E` 的值)的地方,使用 `Result` 枚举方便起来。事实上,这正是咱们在清单 9-3 中,打开某个文件时所用到的,在文件被成功打开时,其中的 `T` 就以 `std::fs::File` 给填上了,而当打开那个文件时,若存在某些问题,那么其中的 `E` 就会被 `std::io::Error` 填充。
|
`Result` 枚举对 `T` 和 `E` 两种类型通用,并有着两个变种:`Ok`,保存类型 `T` 的值,以及 `Err`,保存类型 `E` 的值。这一定义使得在我们有个可能成功(返回某种类型 `T` 的值)或失败(返回某种类型 `E` 的值)的任何地方,使用 `Result` 枚举都很方便。事实上,这就是我们在 [清单 9-3](../error_handling/result.md#listing_9-3) 中用于打开文件的,其中当文件成功打开时, `T` 被填充以 `std::fs::File`,而当打开文件存在问题时,`E` 就被填充以 `std::io::Error`。
|
||||||
|
|
||||||
当咱们认识到咱们的代码中,有着仅在其所保存值类型方面有区别的多个结构体或枚举的情况时,咱们就可以通过使用泛型避免代码重复。
|
当咱们发现咱们的代码中,多个结构体或枚举仅在他们保存的值的类型方面不同的情况时,咱们可通过使用泛型类型来避免代码重复。
|
||||||
|
|
||||||
|
|
||||||
## 方法定义方面
|
## 方法定义方面
|
||||||
|
|
||||||
**In Method Definitions**
|
我们可以在结构体及枚举上实现方法(正如我们在 [第 5 章中](../structs/method_syntax.md) 咱们所做的那样,*译注:及 [第 6 章](../enums_and_pattern_matching/defining_an_enum.md)*),并也可以在他们定义中使用泛型类型。下面清单 10-9 展示了我们在 [清单 10-6](#listing_10-6) 中定义的 `Point<T>` 结构体,带有一个定义在其上的名为 `x` 的方法。
|
||||||
|
|
||||||
|
|
||||||
咱们可以在结构体与枚举上实现方法(正如在第 5 章中咱们所做的),并也可以在他们定义中使用泛型。下面清单 10-9 展示了于其上实现了名为 `x` 方法的,咱们曾在清单 10-6 中定义的 `Point<T>` 结构体。
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="listing_10-9"></a>
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
struct Point<T, U> {
|
struct Point<T> {
|
||||||
x: T,
|
x: T,
|
||||||
y: U,
|
y: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, U> Point<T, U> {
|
impl<T> Point<T> {
|
||||||
fn x(&self) -> &T {
|
fn x(&self) -> &T {
|
||||||
&self.x
|
&self.x
|
||||||
}
|
}
|
||||||
|
|
||||||
fn y(&self) -> &U {
|
|
||||||
&self.y
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let p = Point { x: 5, y: 10 };
|
let p = Point { x: 5, y: 10 };
|
||||||
|
|
||||||
println! ("{}, {}", p.x(), p.y());
|
println!("p.x = {}", p.x());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-9:在 `Point<T, U>` 结构体上,实现将返回到类型 `T` 的 `x` 字段的引用的一个名为 `x` 的方法*
|
**清单 10-9**:在 `Point<T>` 结构体上实现一个名为 `x` 的方法,将返回对类型 `T` 的 `x` 字段的引用
|
||||||
|
|
||||||
这里已在 `Point<T, U>` 上,定义了名为 `x` 的、返回到字段 `x` 中数据引用的一个方法。经由在 `impl` 后,将 `T` 声明为泛型,Rust 就可以识别出,`Point` 中尖括号(`<>`) 里的类型是个泛型而非具体类型。对于这个泛型参数,咱们可以选择不同于前面结构体定义中,所声明的泛型参数名字,但使用同一个名字是依照惯例的。在声明了泛型的 `impl` 里编写的方法,不论泛型最终将以何种具体类型所代替,这些方法都将定义在该类型的所有实例上。
|
在这里,我们已在 `Point<T>` 上定义了个名为 `x` 的方法,返回到字段 `x` 中的数据的引用。
|
||||||
|
|
||||||
当咱们在类型上定义方法时,咱们还可以在泛型上指定约束条件。比如,只在 `Point<f32>` 的实例,而非任意泛型的 `Point<T>` 实例上实现方法。在下面清单 10-10 中,咱们使用了具体类型 `f32`,意味着在 `impl` 之后咱们没有声明任何类型。
|
请注意,我们必须在 `imple` 之后声明 `T`,以便我们可以使用 `T` 来指定我们正在实现类型 `Point<T>` 上的方法。通过在 `impl` 后声明 `T` 为泛型类型,Rust 就可以识别 `Point` 中尖括号里的类型是泛型类型而非具体类型。我们可以为这个泛型参数选择一个不同的泛型参数名字,相比在结构体定义中声明的泛型参数名字,但使用同一个名字是惯例。当咱们在声明了泛型类型的 `impl` 内编写某个方法时,那么该方法将定义在这一类型的任何实例上,无论最终替换泛型类型的何种具体类型。
|
||||||
|
|
||||||
|
在定义泛型类型上的方法时,我们还可以指定泛型类型上的约束。例如,我们可以仅对 `Point<f32>` 的实例实现方法,而非对有着任意泛型类型的 `Point<T>` 实例。在下面清单 10-10 中,我们使用具体类型 `f32`,这意味着我们未在 `impl` 之后声明任何类型。
|
||||||
|
|
||||||
|
|
||||||
|
<a name="listing_10-10"></a>
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
@@ -285,11 +275,11 @@ impl Point<f32, f32> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
*清单 10-10:只适用于有着特定具体类型泛型参数 `<T, U>` 的结构体的一个 `impl` 代码块*
|
**清单 10-10**:一个 `impl` 代码块,仅适用于有着针对泛型类型参数 `<T, U>` 的特定具体类型的结构体
|
||||||
|
|
||||||
此代码表示类型 `Option<f32, f32>` 将有一个 `distance_from_origin` 方法;其中 `T, U` 不是 `f32` 的其他 `Option<T, U>` 实例,就不会被定义这个方法。该方法度量了咱们的点与坐标 `(0.0, 0.0)` 处点的距离,并使用了只对浮点数类型可行的数学运算。
|
这段代码表示类型 `Option<f32>` 将有着 `distance_from_origin` 方法;而其中 `T` 不是 `f32` 的其他 `Option<T>` 实例将不会被定义这个方法。该方法测量我们的点距坐标 `(0.0, 0.0)` 处点的有多远,并使用了仅对浮点类型可用的数学运算。
|
||||||
|
|
||||||
结构体定义中的泛型参数,并不总与咱们在同一结构体方法签名中,所使用的那些泛型参数相同。为让示例更明确,下面清单 10-11 对 `Point` 结构体,使用了泛型 `T` 与 `U`,而对 `mixup` 方法签名则使用了 `X` `Y`。
|
结构体定义中的泛型类型参数,并不总与咱们在同一结构体的方法签名中使用的泛型类型参数相同。为让示例更明确,下面清单 10-11 对 `Point` 结构体,使用了泛型 `T` 与 `U`,而对 `mixup` 方法签名则使用了 `X` `Y`。
|
||||||
这个方法使用来自 `self` `Point` 的 `x` 值(类型为 `T`),与来自传入的那个 `Point` 值的 `y` (类型为 `Y`),创建出一个新的 `Point`。
|
这个方法使用来自 `self` `Point` 的 `x` 值(类型为 `T`),与来自传入的那个 `Point` 值的 `y` (类型为 `Y`),创建出一个新的 `Point`。
|
||||||
|
|
||||||
文件名:`src/main.rs`
|
文件名:`src/main.rs`
|
||||||
|
|||||||
Reference in New Issue
Block a user