[!|center] 普若哥们儿
泛型
可以使用泛型为函数签名或定义结构体和枚举,这样它们就可以用于多种不同的具体数据类型。
在函数定义中使用泛型
下面的 largest 是一个泛型函数,类型参数声明位于函数名称与参数列表中间的尖括号 <> 中:
fn largest<T>(list: &[T]) -> &T {
泛型函数 largest 有一个泛型类型 T,它有个参数 list,其类型是元素为 T 的 slice。largest 函数会返回一个与 T 相同类型的引用。
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);
}
如果现在就编译这个代码,会出现如下错误:
...
help: consider restricting type parameter `T`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++
编译输出中提到了 std::cmp::PartialOrd,这是一个 trait,trait 的概念类似其它语言中的接口 interface,是一组预定义的函数接口规范,一个类型实现了某个 trati 表明该类型实现了一组函数,具有特定的功能,后面的章节会详细介绍 trait。简单来说,这个错误表明 largest 的函数体不能适用于 T 的所有可能的类型。因为在函数体需要比较 T 类型的值,不过它只能用于我们知道如何排序的类型。标准库中定义的 std::cmp::PartialOrd trait 实现了类型实例的比较功能,依照帮助说明中的建议,我们限制 T 只对实现了 PartialOrd 的类型有效,这样代码就可以编译了,因为标准库为 i32 和 char 实现了 PartialOrd trait。
函数定义应当改为:
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {...}
<T: std::cmp::PartialOrd> 表示对泛型参数 T 的约束,要求类型 T 必须实现了 std::cmp::PartialOrd trait。
需要注意:如果不加约束,Rust 要求泛型函数中的语句适应所有类型,因此 Rust 中的泛型函数通常需要对类型参数进行约束。
结构体定义中的泛型
同样也可以用 <> 语法来定义结构体,它包含一个或多个泛型参数类型字段。示例定义了一个可以存放任何类型的 x 和 y 坐标值的结构体 Point:
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 };
}
注意 Point<T> 的定义中只使用了一个泛型类型,这个定义表明结构体 Point<T> 对于一些类型 T 是泛型的,而且字段 x 和 y 都是相同类型的,无论它具体是何类型。
如果想要定义一个 x 和 y 可以有不同类型,可以使用多个泛型类型参数:
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 };
}
枚举定义中的泛型
和结构体类似,枚举也可以在成员中存放泛型数据类型。标准库提供的 Option<T> 枚举就是一个泛型:
enum Option<T> {
Some(T),
None,
}
Option<T> 是一个拥有泛型 T 的枚举,它有两个成员:Some,它存放了一个类型 T 的值,和不存在任何值的 None。通过 Option<T> 枚举可以表达有一个可能的值的抽象概念,同时因为 Option<T> 是泛型的,无论这个可能的值是什么类型都可以使用这个抽象。
枚举也可以拥有多个泛型类型,标准库提供的Result 枚举定义就是一个这样的例子:
enum Result<T, E> {
Ok(T),
Err(E),
}
Result 枚举有两个泛型类型,T 和 E。Result 有两个成员:Ok,它存放一个类型 T 的值,而 Err 则存放一个类型 E 的值。这个定义使得 Result 枚举能很方便的表达任何可能成功(返回 T 类型的值)也可能失败(返回 E 类型的值)的操作。
使用标准库打开文件时,当成功打开文件,T 对应的是 std::fs::File 类型;而当打开文件出现问题时,E 的值则是 std::io::Error 类型。
泛型的类型推断
前面的例子中编译器自动推断出了泛型的类型,实际上也可以显式指定泛型参数类型,比如:
fn my_function<T>(value: T) -> T {
value
}
fn main() {
let x = my_function::<i32>(5);
let y = my_function(5);
}
第 6 行通过显式指定泛型类型来调用函数。注意显式指定泛型参数类型的语法:函数名::<类型名>(函数参数)。
再比如在结构体中:
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let both_integer = Point::<i32,i32> { x: 5, y: 10 };
let integer_and_float = Point::<i32,f32> { x: 5, y: 10.0 };
}
注意显式指定泛型参数类型的语法:结构体名::<类型名>{指定结构体成员的值}。
由于 Rust 能够自动推断类型,因此显式指定泛型参数类型是不必要的。
默认泛型参数
在结构体中可以指定默认泛型类型,其意义是,当不显式指定泛型类型时使用默认泛型类型。比如:
struct Point<T = i32, U = f32> { // 指定默认泛型类型
x: T,
y: U,
}
fn main() {
let integer_and_float = Point { x: 5, y: 10.0 }; // 使用默认泛型类型
let both_integer = Point::<i32, i32> { x: 5, y: 10 }; // 显式指定泛型类型
let both_float = Point { x: 1.0, y: 4.0 }; // 通过类型推断获取泛型类型
}
第 7 行没有显式指定泛型类型,因此使用默认泛型。
第 8 行显式指定了泛型类型,因此不使用默认泛型类型。
第 9 行通过类型推断获取泛型类型,也不使用默认泛型类型。
同样,由于 Rust 能够自动推断类型,因此指定默认泛型类型通常也是不必要的。
注意,不能为函数参数指定默认泛型类型。
默认泛型类型在 trait 定义中有些情况下是有意义的,这将在关于 trait 的章节中介绍。