泛型数据类型
我们使用泛型为函数签名或 结构体,然后我们可以将其与许多不同的具体数据类型一起使用。让我们 首先看看如何使用 泛 型。然后,我们将讨论泛型如何影响代码性能。
在函数定义中
在定义使用泛型的函数时,我们将泛型放在 签名,我们通常会指定 parameters 和 return value 的 API 中。这样做使我们的代码更加灵活,并提供 为函数的调用者提供更多功能,同时防止代码重复。
继续我们的largest
函数中,示例 10-4 显示了两个函数,它们
两者都能找到 slice 中的最大值。然后,我们将这些组合成一个
使用泛型的函数。
文件名: src/main.rs
fn largest_i32(list: &[i32]) -> &i32 { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest } fn largest_char(list: &[char]) -> &char { 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_i32(&number_list); println!("The largest number is {result}"); assert_eq!(*result, 100); let char_list = vec!['y', 'm', 'a', 'q']; let result = largest_char(&char_list); println!("The largest char is {result}"); assert_eq!(*result, 'y'); }
示例 10-4:两个函数的区别仅在于 names 及其签名中的类型
这largest_i32
function 是我们在示例 10-3 中提取的那个,它找到
最大的i32
在切片中。这largest_char
函数查找最大的char
在切片中。函数体具有相同的代码,因此让我们消除
通过在单个函数中引入泛型类型参数来实现重复。
要在新的单个函数中参数化类型,我们需要将类型命名为
parameter 的值,就像我们对函数的值 parameters 所做的那样。您可以使用
any identifier 作为类型参数名称。但是我们将使用T
因为,通过
约定,Rust 中的类型参数名称很短,通常只有一个字母,而
Rust 的类型命名约定是 UpperCamelCase。type 的简称T
是
大多数 Rust 程序员的默认选择。
当我们在函数体中使用参数时,我们必须声明
parameter name 的签名,以便编译器知道该名称的含义。
同样,当我们在函数签名中使用类型参数名称时,我们有
来声明类型参数名称。定义类型largest
function 中,我们将类型名称声明放在函数名称和参数列表之间的尖括号 , 中,如下所示:<>
fn largest<T>(list: &[T]) -> &T {
我们将这个定义理解为:函数largest
在某种类型上是泛型T
.此函数有一个名为list
,它是值的切片
的类型T
.这largest
函数将返回对
同类型T
.
示例 10-5 显示了组合的largest
使用泛型
数据类型。清单还显示了我们如何调用函数
使用切片i32
values 或char
值。请注意,此代码不会
编译,但我们将在本章后面修复它。
文件名: src/main.rs
fn largest<T>(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!("The largest number is {result}");
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("The largest char is {result}");
}
示例 10-5:largest
使用泛型类型的函数
参数;this 尚未编译
如果我们现在编译这段代码,我们将收到这个错误:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0369]: binary operation `>` cannot be applied to type `&T`
--> src/main.rs:5:17
|
5 | if item > largest {
| ---- ^ ------- &T
| |
| &T
|
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++
For more information about this error, try `rustc --explain E0369`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
帮助文本提到std::cmp::PartialOrd
,这是一个 trait,而我们是
将在下一节中讨论 traits。现在,请知道这个错误
声明largest
不适用于所有可能的类型T
可能是。因为我们想要比较T
在体内,我们可以
仅使用其值可以排序的类型。为了启用比较,标准
library 具有std::cmp::PartialOrd
你可以在类型上实现的 trait
(有关此性状的更多信息,请参见附录 C)。按照帮助文本的
建议,我们会限制对T
仅对那些实现PartialOrd
并且此示例将编译,因为 standard 库
实现PartialOrd
在两者上i32
和char
.
在结构定义中
我们还可以定义结构体以在一个或多个
字段。示例 10-6 定义了一个<>
Point<T>
struct 来持有x
和y
坐标值。
文件名: src/main.rs
struct Point<T> { x: T, y: T, } fn main() { let integer = Point { x: 5, y: 10 }; let float = Point { x: 1.0, y: 4.0 }; }
示例 10-6:一个Point<T>
结构体,该x
和y
type 的值T
在结构体定义中使用泛型的语法类似于 函数定义。首先,我们在 结构名称后面的尖括号。然后我们使用泛型 type 在 struct 定义中,否则我们将指定具体数据 类型。
请注意,因为我们只使用了一个泛型类型来定义Point<T>
这
定义表示Point<T>
struct 在某种类型上是泛型T
和
字段x
和y
都是同一类型,无论该类型是什么。如果
我们创建一个Point<T>
,它的值不同,如
示例 10-7,我们的代码无法编译。
文件名: src/main.rs
struct Point<T> {
x: T,
y: T,
}
fn main() {
let wont_work = Point { x: 5, y: 4.0 };
}
示例 10-7:字段x
和y
必须相同
type 的 intent 数据类型,因为两者具有相同的泛型数据类型T
.
在此示例中,当我们分配整数值5
自x
,我们让
编译器知道泛型类型T
将是此实例的整数Point<T>
.然后,当我们指定4.0
为y
,我们将其定义为具有
与 相同类型x
,我们将收到如下类型不匹配错误:
$ cargo run
Compiling chapter10 v0.1.0 (file:///projects/chapter10)
error[E0308]: mismatched types
--> src/main.rs:7:38
|
7 | let wont_work = Point { x: 5, y: 4.0 };
| ^^^ expected integer, found floating-point number
For more information about this error, try `rustc --explain E0308`.
error: could not compile `chapter10` (bin "chapter10") due to 1 previous error
要定义Point
struct 中,其中x
和y
都是泛型,但可能具有
不同的类型,我们可以使用多个泛型类型参数。例如,在
示例 10-8,我们将Point
成为 type 上的泛型T
和U
哪里x
属于 类型T
和y
属于 类型U
.
文件名: src/main.rs
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 }; }
示例 10-8:一个Point<T, U>
泛型胜过两种类型,所以
那x
和y
可以是不同类型的值
现在,所有Point
显示是允许的!您可以使用任意数量的通用
根据需要在定义中键入参数,但使用多个参数会使
你的代码很难阅读。如果你发现你需要大量的泛型类型
您的代码,它可能表明您的代码需要重组为更小的代码
件。
在 Enum 定义中
就像我们对结构体所做的那样,我们可以定义枚举来保存其
变种。我们再看一下Option<T>
enum 表示标准
library 提供,我们在第 6 章中使用了它:
#![allow(unused)] fn main() { enum Option<T> { Some(T), None, } }
现在,此定义对您来说应该更有意义。如您所见,Option<T>
enum 是 type 上的泛型T
,并且有两个变体:Some
哪
包含一个 类型的值T
和None
variant 中。
通过使用Option<T>
enum 中,我们可以表示
optional 值,并且因为Option<T>
是通用的,我们可以使用这个抽象
无论 Optional 值的类型是什么。
枚举也可以使用多个泛型类型。的定义Result
我们在第 9 章中使用的 enum 就是一个例子:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
这Result
enum 在两种类型上是泛型的,T
和E
,并且有两个变体:Ok
,它保存 type 为T
和Err
,它保存 type 为E
.此定义便于使用Result
enum 的任意位置
有一个可能成功的作(返回某种类型的值T
) 或失败
(返回某种类型的错误E
).事实上,这就是我们用来打开
file 中,其中T
填充了类型std::fs::File
什么时候
文件已成功打开,并且E
填充了类型std::io::Error
当打开文件时出现问题。
当你识别到代码中具有多个 struct 或 enum 的情况时 定义仅在它们所持有的值的类型上有所不同,您可以 通过使用泛型类型来避免重复。
在方法定义中
我们可以在结构和枚举上实现方法(就像我们在第 5 章中所做的那样)并使用
泛型类型。示例 10-9 显示了Point<T>
struct 中,我们在示例 10-6 中定义的 struct 中,使用名为x
在其上实现。
文件名: src/main.rs
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 5, y: 10 }; println!("p.x = {}", p.x()); }
示例 10-9:实现一个名为x
在Point<T>
struct 中,它将返回对x
type 字段T
在这里,我们定义了一个名为x
上Point<T>
返回一个引用
到现场的数据x
.
请注意,我们必须声明T
紧接着impl
所以我们可以使用T
以指定
我们正在 type 上实现方法Point<T>
.通过声明T
作为
之后的泛型类型impl
中,Rust 可以识别出 angle
括号Point
是泛型类型,而不是具体类型。我们可以
为此泛型参数选择的名称与泛型
parameter 的 Parameter 中声明的,但使用相同的名称是
协定的。在impl
声明泛型类型
将在该类型的任何实例上定义,无论具体类型以何种结尾
up 替换泛型类型。
在
类型。例如,我们只能在Point<f32>
实例
而不是打开Point<T>
实例。在示例 10-10 中,我们
使用 concrete 类型f32
,这意味着我们不会在impl
.
文件名: src/main.rs
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } impl Point<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()); }
示例 10-10:一个impl
仅适用于
struct 替换为泛型类型参数的特定具体类型T
此代码表示类型Point<f32>
将具有distance_from_origin
方法;其他实例Point<T>
哪里T
不是类型f32
不会
定义此方法。该方法测量我们的点与
指向坐标 (0.0, 0.0),并使用
仅适用于浮点类型。
结构体定义中的泛型类型参数并不总是相同的
你在同一结构体中使用 method signatures。示例 10-11 使用泛型
类型X1
和Y1
对于Point
struct 和X2
Y2
对于mixup
方法
签名以使示例更清晰。该方法会创建一个新的Point
实例替换为x
值self
Point
(类型X1
) 和y
传入的值Point
(类型Y2
).
文件名: src/main.rs
struct Point<X1, Y1> { x: X1, y: Y1, } impl<X1, Y1> Point<X1, Y1> { fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> { Point { x: self.x, y: other.y, } } } fn main() { let p1 = Point { x: 5, y: 10.4 }; let p2 = Point { x: "Hello", y: 'c' }; let p3 = p1.mixup(p2); println!("p3.x = {}, p3.y = {}", p3.x, p3.y); }
示例 10-11:使用不同泛型类型的方法 从其结构体的定义
在main
,我们定义了一个Point
具有i32
为x
(值5
)
以及一个f64
为y
(值10.4
).这p2
variable 是Point
结构
它有一个字符串 slicex
(值"Hello"
) 和char
为y
(值c
).叫mixup
上p1
带有参数p2
给我们p3
,
它将具有i32
为x
因为x
来自p1
.这p3
变量
将具有char
为y
因为y
来自p2
.这println!
宏
call 将打印p3.x = 5, p3.y = c
.
此示例的目的是演示一些泛型
参数使用impl
有些是使用
定义。此处,泛型参数X1
和Y1
在impl
因为它们与 struct 定义一起使用。泛型参数X2
和Y2
在fn mixup
因为它们仅与
方法。
使用泛型的代码性能
您可能想知道使用泛型类型时是否有运行时成本 参数。好消息是,使用泛型类型不会使您的程序 运行速度比使用 Concrete 类型慢。
Rust 通过使用 generics 的 generics 进行编译。Monomorphization 是转为泛型 code 转换为特定代码,方法是填写 编译。在这个过程中,编译器执行与我们使用的步骤相反的作 创建示例 10-5 中的泛型函数:编译器会查看所有 调用泛型代码并为具体类型生成代码的位置 泛型代码被调用 with。
让我们看看通过使用标准库的泛型Option<T>
enum 的
#![allow(unused)] fn main() { let integer = Some(5); let float = Some(5.0); }
当 Rust 编译此代码时,它会执行单态化。在此期间
进程中,编译器会读取Option<T>
实例并标识两种Option<T>
: 1 是i32
和另一个
是f64
.因此,它扩展了Option<T>
一分为二
专门用于i32
和f64
,从而替换泛型
定义与特定 Ones 一起使用。
代码的单态化版本类似于以下内容( compiler 使用的名称与我们在此处使用的名称不同):
文件名: src/main.rs
enum Option_i32 { Some(i32), None, } enum Option_f64 { Some(f64), None, } fn main() { let integer = Option_i32::Some(5); let float = Option_f64::Some(5.0); }
泛型Option<T>
替换为由
编译器。因为 Rust 将泛型代码编译成指定
type 时,我们无需为使用泛型支付运行时成本。当代码
运行,它的性能就像我们通过
手。单态化过程使 Rust 的泛型非常高效
在运行时。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准