Rust 中的泛型简介
泛型编程(generics)是 Rust 的关键概念之一,它允许编写灵活、可复用且安全的代码。如果您从其他语言(例如 C++ 或 Java)转向 Rust,您可能已经接触过模板或泛型。在 Rust 中,generics 的工作方式类似,但特别强调性能和内存安全。
为什么需要 generics?假设您需要编写一个函数来查找列表中的最大元素。没有 generics,您必须为 i32、f64、String 以及每个自定义类型编写单独的版本。使用 generics,您可以编写一个函数,该函数适用于任何支持比较的类型。
在本文中,我们将探讨 Rust 中的 generics 是什么,如何将它们与函数、结构体和 traits 一起使用,并查看实际示例。
Generics 基础:函数和结构体
泛型函数
了解 generics 最简单的方法是编写一个泛型函数。语法使用尖括号 <T>,其中 T 是类型参数(type parameter)。
fn largest<T>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest}
fn main() { let numbers = vec![34, 50, 25, 100, 65]; let result = largest(&numbers); println!("最大数字: {}", result);
let chars = vec!['y', 'm', 'a', 'q']; let result = largest(&chars); println!("最大字符: {}", result);}但是,这段代码无法编译!Rust 要求类型 T 实现 PartialOrd trait(用于比较)。我们稍后会讨论约束(trait bounds),现在请记住:generics 允许抽象出具体类型,但编译器必须知道哪些操作是允许的。
泛型结构体
Generics 不仅适用于函数,也适用于自定义数据类型。例如,一个具有坐标的点可以存储整数或浮点数:
struct Point<T> { x: T, y: T,}
fn main() { let integer_point = Point { x: 5, y: 10 }; let float_point = Point { x: 1.0, y: 4.0 }; println!("整数点: ({}, {})", integer_point.x, integer_point.y); println!("浮点数点: ({}, {})", float_point.x, float_point.y);}如果字段具有不同类型,可以使用多个类型参数:
struct Pair<T, U> { first: T, second: U,}
fn main() { let pair = Pair { first: 42, second: "答案" }; println!("对: {} 和 {}", pair.first, pair.second);}带有 Traits 和约束(Trait Bounds)的 Generics
什么是 trait bounds?
Trait bounds 是一种告诉编译器泛型类型必须实现特定 traits 的方式。没有它们,我们无法在泛型值上调用方法。让我们回到 largest 函数并修复它:
use std::cmp::PartialOrd;
fn largest<T: PartialOrd>(list: &[T]) -> &T { let mut largest = &list[0]; for item in list { if item > largest { largest = item; } } largest}
fn main() { let numbers = vec![34, 50, 25, 100, 65]; println!("最大值: {}", largest(&numbers));}现在该函数只接受支持比较(PartialOrd)的类型。
多重约束
您可以通过 + 指定多个 traits。例如,类型必须既可比较又可克隆:
use std::fmt::Display;
fn print_and_compare