泛型数据类型
我们使用泛型为函数签名或 结构体,然后我们可以将其与许多不同的具体数据类型一起使用。让我们 首先看看如何使用 泛 型。然后,我们将讨论泛型如何影响代码性能。
在函数定义中
在定义使用泛型的函数时,我们将泛型放在 签名,我们通常会指定 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_i32function 是我们在示例 10-3 中提取的那个,它找到
最大的i32在切片中。这largest_char函数查找最大的char在切片中。函数体具有相同的代码,因此让我们消除
通过在单个函数中引入泛型类型参数来实现重复。
要在新的单个函数中参数化类型,我们需要将类型命名为
parameter 的值,就像我们对函数的值 parameters 所做的那样。您可以使用
any identifier 作为类型参数名称。但是我们将使用T因为,通过
约定,Rust 中的类型参数名称很短,通常只有一个字母,而
Rust 的类型命名约定是 UpperCamelCase。type 的简称T是
大多数 Rust 程序员的默认选择。
当我们在函数体中使用参数时,我们必须声明
parameter name 的签名,以便编译器知道该名称的含义。
同样,当我们在函数签名中使用类型参数名称时,我们有
来声明类型参数名称。定义类型largestfunction 中,我们将类型名称声明放在函数名称和参数列表之间的尖括号 , 中,如下所示:<>
fn largest<T>(list: &[T]) -> &T {
我们将这个定义理解为:函数largest在某种类型上是泛型T.此函数有一个名为list,它是值的切片
的类型T.这largest函数将返回对
同类型T.
示例 10-5 显示了组合的largest使用泛型
数据类型。清单还显示了我们如何调用函数
使用切片i32values 或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和ytype 的值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
要定义Pointstruct 中,其中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和Nonevariant 中。
通过使用Option<T>enum 中,我们可以表示
optional 值,并且因为Option<T>是通用的,我们可以使用这个抽象
无论 Optional 值的类型是什么。
枚举也可以使用多个泛型类型。的定义Result我们在第 9 章中使用的 enum 就是一个例子:
#![allow(unused)] fn main() { enum Result<T, E> { Ok(T), Err(E), } }
这Resultenum 在两种类型上是泛型的,T和E,并且有两个变体:Ok,它保存 type 为T和Err,它保存 type 为E.此定义便于使用Resultenum 的任意位置
有一个可能成功的作(返回某种类型的值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 中,它将返回对xtype 字段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对于Pointstruct 和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).这p2variable 是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/)为准