泛型(Generics)是指把类型抽象成一种"参数"。泛型通过在类型或函数定义中使用类型参数来实现,这些类型参数可以在具体使用时被替换为实际的具体类型。
泛型类型参数通常用单个大写字母表示,如 T、U、V 等,这只是一种约定俗成的命名方式,你也可以根据需要使用更具描述性的名称。
在Rust中,有很多常见的数据结构都使用了泛型,如:
Option<T>
Result<T, E>
Vec<T>
HashMap<K, V>
Box<T>
Rc<T>
Arc<T>
Iterator<Item=T>
以Option<T> 为例
在 Rust 中,Option 是一个非常常用的枚举类型,它使用了泛型,用于表示一个值可能存在或不存在的情况。
Option 类型定义在标准库中,其定义如下:
rust
enum Option<T> {
Some(T),
None,
}
其中,T 是一个泛型类型参数,可以代表任何类型。Some 变体用于包装一个具体的值,表示该值存在;None 变体表示值不存在,不包含任何数据。
这个泛型参数类型T,可以在使用时指定具体类型。例如:
rust
fn main() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
match some_value {
Some(v) => println!("有值,值为: {}", v),
None => println!("没有值"),
}
match none_value {
Some(v) => println!("有值,值为: {}", v),
None => println!("没有值"),
}
}
这里直接创建了两个 Option<i32> 类型的变量,一个初始化为包含值的 Some 状态,另一个初始化为 None 状态,同样使用 match 语句来分别处理这两个变量,展示不同情况下的输出结果。
Rust规定,所有的泛型参数必须是真的被使用过的。
下面的代码就会报错:
rust
struct Num<T> {
data: i32
}
编译器会提示一下错误:
bash
type parameter `T` is never used
consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
if you intended `T` to be a const parameter, use `const T: /* Type */` instead
正确的用法是:
rust
struct Person<T> {
name: T
}
fn main() {
let p1 = Person { name: "Jack Ma" };
let p2 = Person { name: String::from("Robin") };
println!("person1:{},person2:{}",p1.name,p2.name)
}
在Rust中我们可以使用泛型来定义函数、结构体、枚举和方法
。
输出泛型
rust
#[derive(Debug)]
struct Person<T> {
name: T
}
fn main() {
let p1 = Person { name: "Jack Ma" };
println!("{:?}",p1);
println!("{:#?}",p1);
}
输出结果如下:
bash
Person { name: "Jack Ma" }
Person {
name: "Jack Ma",
}
函数中的泛型
假如,我们要编写如下两个函数,通过在切片中,找出最大值,如果不使用泛型,代码如下:
rust
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}");
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&char_list);
println!("The largest char is {result}");
}
很明显,上述两个函数体中有很多重复代码,我们可以只定义一个函数,引入泛型类型参数(Generic type parameter)来消除重复的代码。
改进的代码如下:
rust
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}");
}
上述代码,使用泛型消除了重复的代码。当然如果编译的话,会报如下错误:
bash
error[E0369]: binary operation `>` cannot be applied to type `&T`
--> src/main.rs:44:15
|
44 | if item > largest {
| ---- ^ ------- &T
| |
| &T
|
help: consider restricting type parameter `T`
|
40 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++
泛型参数约束
这个编译错误说得很清楚了,由于泛型参数T没有任何约束,因此编译器认为item > largest
这个表达式是不合理的,因为它只能作用于支持比较运算符的类型。
在Rust中,只有impl了PartialOrd的类型,才能支持比较运算符。
在 Rust 中,可以使用 where 子句或直接在泛型参数声明中使用 : 来为泛型参数添加约束。
使用 where
子句
这是最常见的方式,语法为 where T: Trait1 + Trait2 +...,其中 T 是泛型参数,Trait1、Trait2 等是需要 T 实现的特征。
可以修改上述代码为:
rust
fn largest<T>(list: &[T]) -> &T
where T: std::cmp::PartialOrd
{
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
在泛型参数声明中使用 :
语法为 fn function_name<T: Trait1 + Trait2 +...>(parameters),这种方式将约束直接写在泛型参数声明的后面。
可以修改上述代码为:
rust
fn largest<T: std::cmp::PartialOrd >(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
当然,如果有多个参数,可以分别为每个泛型参数添加约束,也可以在 where 子句中组合多个约束条件。
rust
fn compare<T, U>(a: T, b: U) -> bool
where
T: std::cmp::PartialEq<U>,
U: std::fmt::Debug,
{
a == b
}
结构体中的泛型
在结构体定义时,在结构体名称后面的尖括号中指定泛型参数,然后在结构体的字段类型中使用该泛型参数。例如:
rust
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 };
println!("x={},y={}",integer.x,integer.y);
println!("x={},y={}",float.x,float.y);
}
在上述代码中,Point 结构体有一个泛型参数 T,x 和 y 字段的类型都为 T,这意味着 x 和 y 的类型必须相同。
因此,如果x 和 y 的类型不相同,会报错:
rust
struct Point<T> {
x: T,
y: T,
}
fn main() {
// 错误示例,x和y类型不一致
// mismatched types
// expected integer, found floating-point number
let errorp = Point { x: 5, y: 3.14 };
}
因为,当赋值 x = 5
, rust 会自动推断 T 为 integer
类型。
当然,如果要求x/y 为不同的类型,可以在结构体中定义多个泛型参数:
rust
struct Point<T, U> {
x: T,
y: U,
}
fn main() {
let integer_and_float = Point { x: 5, y: 4.1 };
println!("x={},y={}",integer_and_float.x,integer_and_float.y);
}
泛型结构体实现方法
可以为泛型结构体实现方法,在方法定义中,泛型参数通常需要在 impl 关键字后面再次声明,除非方法中不使用泛型参数。例如:
rust
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
fn new(x: T, y: T) -> Self {
Point { x, y }
}
fn x(&self) -> &T {
&self.x
}
fn y(&self) -> &T {
&self.y
}
}
fn main() {
let p = Point::new(5, 10);
println!("x: {}", p.x());
println!("y: {}", p.y());
}
在上述代码中,impl<T> Point<T>
表示为 Point<T>
结构体实现方法,其中 T 是泛型参数,new 方法用于创建一个新的 Point 实例,x 和 y 方法分别用于获取 x 和 y 坐标的引用。
泛型结构体与特征约束
可以在结构体的泛型参数上添加特征约束,以限制泛型的具体类型必须实现某些特征。例如:
rust
use std::fmt::Display;
struct Container<T: Display> {
value: T,
}
impl<T: Display> Container<T> {
fn print(&self) {
println!("{}", self.value);
}
}
fn main() {
let c = Container { value: 5 };
c.print();
let c2 = Container { value: "hello" };
c2.print();
}
在上述代码中,Container 结构体的泛型参数 T 受到 Display 特征的约束,这意味着只有实现了 Display 特征的类型才能作为 Container 的泛型参数,并且可以在 print 方法中使用 Display 特征的方法来打印 value 的值。
枚举中的泛型
在 Rust 中,枚举可以使用泛型来增加灵活性和代码的复用性。
例如,标准库中的 Option
和 Result
。
rust
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
具体使用实例如下:
rust
fn main() {
let some_value: Option<i32> = Some(42);
let none_value: Option<i32> = None;
match some_value {
Some(num) => println!("The value is {}", num),
None => println!("There is no value"),
}
match none_value {
Some(num) => println!("The value is {}", num),
None => println!("There is no value"),
}
let result1: Result<i32, &str> = Ok(100);
let result2: Result<i32, &str> = Err("An error occurred");
match result1 {
Ok(value) => println!("The result is {}", value),
Err(err) => println!("Error: {}", err),
}
match result2 {
Ok(value) => println!("The result is {}", value),
Err(err) => println!("Error: {}", err),
}
}
输出结果为:
bash
The value is 42
There is no value
The result is 100
Error: An error occurred
自定义泛型枚举的方法
对于自定义泛型枚举,我们也可以为其实现方法。
rust
enum MyEnum<T> {
Value(T),
List(Vec<T>),
}
impl<T:std::fmt::Debug> MyEnum<T> {
fn print_value(&self) {
match self {
MyEnum::Value(val) => println!("The value is {:?}", val),
MyEnum::List(list) => println!("The list contains {:?}", list),
}
}
}
fn main() {
let value_enum = MyEnum::Value(42);
let list_enum = MyEnum::List(vec![1, 2, 3]);
value_enum.print_value();
list_enum.print_value();
}
输出结果如下:
bash
The value is 42
The list contains [1, 2, 3]
方法中的泛型
在Rust中,可以在 impl 块中为结构体、枚举或 trait 定义泛型方法。
其实,我们上面已经进行了详细介绍,即在 impl 关键字后面加上泛型参数声明,例如 impl<T> MyStruct<T>
表示为 MyStruct
结构体定义泛型方法,其中 T
是泛型参数, 在方法签名中使用泛型参数,就像在函数中一样。
例如:
rust
struct MyStruct<T> {
value: T,
}
impl<T> MyStruct<T> {
fn get_value(&self) -> &T {
&self.value
}
fn set_value(&mut self, new_value: T) {
self.value = new_value;
}
}