Rust 泛型 – 这是什么?

导言

Rust 泛型可以让函数、结构体、枚举和其他数据类型与任何类型协同工作,从而让我们编写出灵活且可重用的代码。在本教程中,我们将介绍如何使用泛型、何时使用泛型是个好主意,以及如何限制泛型以保证安全性、清晰性和更高的可用性。泛型功能强大,有时也会令人困惑,让我们来unwrap这个话题,确保你在使用时不会panic

熟悉的泛型

当我们说一个类型是泛型时,这意味着它可以接受多个不同的类型作为参数。有时,它们可以接受的类型有限制;有时则没有任何限制。你可能在不知不觉中使用了泛型!Rust 的许多内置类型都是泛型。

对于初学者和经验丰富的 Rustaceans 来说,最常用的内置泛型是向量类型 Vec<T>。定义中的 T 表示它接受一个泛型参数。

Vec<T> 泛型示例

#[derive(Debug)]
struct MyType {
    type_msg: String,
}

impl MyType {
    fn new(msg: impl Into<String>) -> Self {
        Self { type_msg: msg.into() }
    }
}

fn main() {
    // Vector of Strings
    let str_vec: Vec<String> = vec!["Hello".to_string(), "Generics!".to_string()];

    // Vector of integers
    let int_vec: Vec<i32> = vec![1, 2, 3];

    // Vector of custom structs
    let my_type_vec: Vec<MyType> = vec![MyType::new("Custom Type!")];

    // Print each vector
    println!("{:?}", my_type_vec);
    println!("{:?}", str_vec);
    println!("{:?}", int_vec);
}

Output:

[MyType { type_msg: "Custom Type!" }]
["Hello", "Generics!"]
[1, 2, 3]

我们可以将任何类型放入 Vec 中,包括我们自己的自定义类型。Rust 中的其他内置通用类型包括

  • Option<T>
  • Result<T, E>
  • HashMap<K, V>

请注意,泛型并不局限于一个参数;类型可以有多个泛型参数。还要注意的是,T 并非必须用来表示泛型。Result 使用 T E,其中 E 代表 ErrorHashMap 使用 KV,分别代表 KeyValue。这自然而然地引导我们创建自己的自定义泛型。

自定义泛型

创建自定义泛型非常强大。在本节中,我们将展示创建自定义泛型的几种方法,包括枚举、特质和函数。由于上文已经给出了结构体 Vec<T> 的示例,因此无需再次展示。

泛型枚举示例

下面将介绍如何创建一个泛型枚举,将类型限制为整数类型:

// Define a generic enum representing only integer type API responses
enum ApiResponse<T> 
where
    // Traits that only define integers when combined
    T: Add<Ouput = T> + Sub<Output = T> +
       PartialEq + Eq + ParialOrd + Ord +
       Copy + Display
{
    Success(T),
    Error(String),
}

fn main() {
    // Successful response holding integer data
    let success_response: ApiResponse = ApiResponse::Success(200);

    // Error response with error message
    let error_response: ApiResponse = ApiResponse::Error("Resource Not Found".into());

    match success_response {
        ApiResponse::Success(code) => println!("Success with code {}", code),
        ApiResponse::Error(msg) => println!("Error: {}", msg),
    }

    match error_response {
        ApiResponse::Success(code) => println!("Success with code {}", code),
        ApiResponse::Error(msg) => println!("Error occurred: {}", msg),
    }
}

Output:

Success with code 200
Error occurred: Resource Not Found

创建自定义Generic Trait

你也可以使用通用参数定义自己的traits。下面是一个例子:

// Define a trait for combining two values into one result
trait Combiner<T> {
    fn combine(&self, other: T) -> T;
}

// Implement trait for u32 type
impl Combiner for u32 {
    fn combine(&self, other: u32) -> u32 {
        self + other
    }
}

// Implement trait for String concatenation
impl Combine for String {
    fn combine(&self, other: &String) -> String {
        format!("{} {}", self, other)
    }
}

fn main() {
    let num1: u32 = 10;
    let num_result = num1.combine(20);
    println!("Combined numbers: {}", num_result); // Output: 30

    let hello = "Hello ".to_string();
    let world = "World!".to_string();
    
    let combined_str = hello.combine(&world);
    println!("{}", combined_str); // Output: Hello World
}

创建自定义泛型函数

泛型函数使您可以编写跨多种类型的可重用逻辑。

use std::fmt::Display;

// Generic function that prints value of any type, implementing Display trait, multiple times
fn repeat_print<T: Display>(item: T, times: usize) {
    for _ in 0..times {
        println!("{}", item);
    }
}

fn main() {
    print_multiple_times("Hello Generics!", 3);
    print_multiple_times(42, 2);
}

Output:

Hello Generics!
Hello Generics!
Hello Generics!
42
42

使用Trait边界限制类型

如前所述,泛型功能强大,因为它可以接受任何类型。然而,这并不总是可取的!通常,我们需要将泛型限制在某些定义了必要行为的traits 上。在上面的示例中我们已经略微了解了这一点,下面我们来具体解释一下:

use std::fmt::Display;
use std::ops::{Add, AddAssign, Sub, SubAssign};

#[derive(Debug)]
pub struct MyRestrictedType<T>
where
    T: Display + Add<Output = T> + AddAssign + Sub<Output = T> + SubAssign + Copy,
{
    pub number: T,
}

impl<T> MyRestrictedType<T>
where
    T: Display + Add<Output = T> + AddAssign + Sub<Output = T> + SubAssign + Copy,
{
    pub fn new(number: T) -> Self {
        Self { number }
    }

    pub fn add(&mut self, add_value: T) {
        self.number += add_value;
    }

    pub fn sub(&mut self, sub_value: T) {
        self.number -= sub_value;
    }

    pub fn print(&self) {
        println!("{}", self.number);
    }
}

fn main() {
    // Using f64 (floating-point number)
    let mut restricted_float = MyRestrictedType::new(4.2);
    restricted_float.print(); // Output: 4.2
    restricted_float.add(2.4);
    restricted_float.print(); // Output: 6.6
    restricted_float.sub(1.2);
    restricted_float.print(); // Output: 5.4

    // Using u64 (integer)
    let mut restricted_int = MyRestrictedType::new(42u64);
    restricted_int.print(); // Output: 42
    restricted_int.add(24);
    restricted_int.print(); // Output: 66
    restricted_int.sub(33);
    restricted_int.print(); // Output: 33
}

如果我们尝试使用不支持的类型(如 String),编译就会失败:

fn main() {
    let mut invalid_type = MyRestrictedType::new("Doesn't compile!".to_string());
}

Compiler error:

error[E0277]: cannot add `String` to `String`
...
error[E0277]: cannot subtract `String` from `String`
...
error[E0277]: the trait bound `String: Copy` is not satisfied
...

何时使用泛型

我们已经探讨了什么是泛型以及如何创建泛型,现在我们来讨论一下何时应该使用泛型。

  • 在以下情况下使用泛型
  • 您需要灵活处理多种不同类型。
  • 您不知道用户或开发人员会准确传递什么类型。

希望在编译时保证允许对这些类型进行哪些操作。

请务必考虑您的泛型参数必须具备哪些功能,并相应地使用特性边界。例如

如果您的函数或方法使用 {} 打印内容,请确保您的泛型实现了 Display 特性。

  • 如果您的函数执行算术运算(+、-),请确保您的泛型实现了算术特质(Add、Sub 等)。
  • 这将确保您的代码在编译时保持安全和可预测。

结论

在本教程中,我们学到了

  • 泛型是什么,为什么它们很有用。
  • Rust 的内置类型(Vec、Option 等)如何使用泛型。
  • 如何创建自定义泛型结构体、枚举、特质和函数。
  • 如何使用特质边界限制泛型以确保安全性和正确性。
  • 何时适合在自己的代码中使用泛型。

弄清了这些基本原理后,你应该会对使用 Rust 强大的泛型感到更加得心应手了!

你也许感兴趣的:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注