泛型(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 中,枚举可以使用泛型来增加灵活性和代码的复用性。

例如,标准库中的 OptionResult

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;
    }
}