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
代表 Error
,HashMap
使用 K
和 V
,分别代表 Key
和 Value
。这自然而然地引导我们创建自己的自定义泛型。
自定义泛型
创建自定义泛型非常强大。在本节中,我们将展示创建自定义泛型的几种方法,包括枚举、特质和函数。由于上文已经给出了结构体 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 强大的泛型感到更加得心应手了!
你也许感兴趣的:
- 两年的 Rust 使用感悟
- 嵌入式 Rust 开发
- 氧化 Ubuntu:默认采用 Rust 实用工具
- “unsafe”是否会破坏 Rust 的保证?
- 编程语言的选择
- 使用中的CSS :is 选择器
- Linus正面回应Linux内核“Rust之争”:未来必定使用,完全生产级别尚需时日!
- 【外评】一年的 Rust 开发总结
- “革命性”「Safe C++」扩展提案:质疑Rust、理解Rust、成为Rust?
- Rust 的崛起: 这种编程语言为何越来越受欢迎
你对本文的反应是: