Rust: Rust 泛型(Generics):类型参数化编程

最后更新:2026-08-26

泛型(Generics)是"类型层面的参数化编程"——把类型也作为参数传递,让同一份代码适用于多种类型,而不需要为每种类型重复写代码。

如果说函数是"把值抽象成参数",那泛型就是"把类型也抽象成参数"。就像点外卖时你说"我要一份饭",不指定是炒饭还是盖饭——到了餐厅再决定。


1. 你将学到


2. 概念图解

以下 Mermaid 图展示泛型类型参数 T 在编译时单态化(monomorphization)过程中被具体类型替换的机制:

100%
graph LR
    A["泛型函数<br/>fn identity&lt;T&gt;(x: T) -> T"] --> B["编译时单态化<br/>Monomorphization"]
    B --> C["T → i32<br/>fn identity_i32(x: i32) -> i32"]
    B --> D["T → String<br/>fn identity_string(x: String) -> String"]
    B --> E["T → f64<br/>fn identity_f64(x: f64) -> f64"]
    C --> F["调用 identity(42)"]
    D --> G["调用 identity(&#34;hello&#34;.to_string())"]
    E --> H["调用 identity(3.14)"]

3. 一个万能容器的故事

(1) 痛苦:为每种类型写重复的代码

Luna (Luna) 在开发一个工具库,需要一个"找出最大值"的函数。

最开始她为每种类型写一份:

RUST
fn max_i32(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}

fn max_f64(a: f64, b: f64) -> f64 {
    if a > b { a } else { b }
}

fn max_str(a: &str, b: &str) -> &str {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", max_i32(3, 7));     // 7
    println!("{}", max_f64(2.5, 1.8)); // 2.5
    println!("{}", max_str("apple", "banana")); // banana
}

三个函数除了类型不同,逻辑完全一样。如果还有 u32、u64、char…… 每多一种类型就要复制一份。这是"复制粘贴编程"——既不优雅也不可维护。

(2) Rust 泛型的方案

RUST
fn max<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", max(3, 7));              // 7
    println!("{}", max(2.5, 1.8));          // 2.5
    println!("{}", max("apple", "banana")); // banana
}

一个泛型函数替代了三个具体类型的函数——而且编译器会为每种实际使用的类型自动生成专门的代码(单态化)。你写一份,编译器帮你展开成多份。


4. 核心概念

(1) 泛型体系

100%
graph TB
    A[泛型 Generics] --> B[泛型函数]
    A --> C[泛型结构体]
    A --> D[泛型枚举]
    A --> E[泛型方法]

    B --> F["fn identity<T>(x: T) -> T"]
    C --> G["struct Point<T> { x: T, y: T }"]
    D --> H["Option<T>, Result<T, E>"]
    E --> I["impl<T> Point<T> { fn x(&self) -> &T }"]

    A --> J[单态化 Monomorphization]
    J --> K["编译时:T → i32, f64, String ..."]
    J --> L["每种类型生成独立代码"]

(2) 泛型 vs 具体类型 vs 动态分发

特性 具体类型(无泛型) 泛型(单态化) 动态分发(dyn Trait)
代码重复 每种类型手写一份 编译器自动展开 一份代码,运行时派发
性能 最佳 最佳(无虚函数开销) 有虚函数开销
编译时间 长(手写多) 较长(展开多份) 较短
二进制体积 较大(每类型一份) 较小
灵活性 编译时确定 运行时确定

(3) 常用泛型枚举

枚举 定义 用途
Option<T> enum Option<T> { Some(T), None } 可能为空的值
Result<T, E> enum Result<T, E> { Ok(T), Err(E) } 可能失败的操作
Vec<T> struct Vec<T> { ... } 动态数组
HashMap<K, V> struct HashMap<K, V> { ... } 键值映射

(4) 泛型约束方式对比

约束方式 语法 适用场景 示例
内联约束 fn foo<T: Trait>(x: T) 简单单约束 fn max<T: PartialOrd>(a: T, b: T)
多约束 + fn foo<T: Trait1 + Trait2>(x: T) 多个约束 fn print<T: Display + Clone>(x: T)
where 子句 fn foo<T>(x: T) where T: Trait 复杂约束、多类型参数 where T: Display + Clone, U: Debug
impl Trait fn foo(x: impl Trait) 简洁写法(语法糖) fn plug(d: &impl USBDevice)

5. 泛型示例

▶ 示例 1:泛型函数——找出数组中的最大值(难度 ⭐)

RUST
// ============================================
// 泛型函数:适用于任何可比较的类型
// ============================================

// PartialOrd 约束确保 T 支持比较操作
fn find_max<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
    let mut max = &list[0];
    for item in list.iter() {
        if item > max {
            max = item;
        }
    }
    max
}

fn main() {
    let numbers = vec![3, 7, 1, 9, 4];
    println!("i32 最大值: {}", find_max(&numbers));

    let floats = vec![2.5, 1.8, 3.14, 0.99];
    println!("f64 最大值: {}", find_max(&floats));

    let strings = vec!["apple", "banana", "cherry", "date"];
    println!("&str 最大值: {}", find_max(&strings));

    // 同一个函数,三种类型,编译器自动展开
}

输出:

TEXT 📖 仅展示
i32 最大值: 9
f64 最大值: 3.14
&str 最大值: date

find_max<T> 是一个泛型函数,T 是类型参数。<T: std::cmp::PartialOrd> 是 trait bound——意思是"T 必须是可比较的类型"。调用时编译器根据实参类型自动推断 T


▶ 示例 2:泛型结构体——Point 坐标系统(难度 ⭐⭐)

RUST
// ============================================
// 泛型结构体:Point 可以存储任意类型的坐标
// ============================================

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

// 为泛型结构体实现方法
impl<T> Point<T> {
    // 返回 x 的引用
    fn x(&self) -> &T {
        &self.x
    }

    // 返回 y 的引用
    fn y(&self) -> &T {
        &self.y
    }
}

// 为特定类型 f64 的 Point 实现额外方法
impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let int_point = Point { x: 5, y: 10 };
    let float_point = Point { x: 3.0, y: 4.0 };
    let string_point = Point {
        x: "left",
        y: "right",
    };

    println!("int_point: {:?}", int_point);
    println!("float_point x: {}", float_point.x());
    println!("string_point: {:?}", string_point);

    // 只有 `Point<f64>` 有 distance_from_origin 方法
    println!("距离原点: {:.2}", float_point.distance_from_origin());

    // 编译错误:int_point 是 Point<i32>,没有这个方法
    // println!("{}", int_point.distance_from_origin());
}

输出:

TEXT 📖 仅展示
int_point: Point { x: 5, y: 10 }
float_point x: 3.0
string_point: Point { x: "left", y: "right" }
距离原点: 5.00

Point<T> 是一个泛型结构体,xy 的类型相同(都是 T)。impl<T> Point<T> 为所有 T 实现通用方法。impl Point<f64> 只为特定类型实现专用方法——这是泛型的一大优势。


▶ 示例 3:泛型枚举——Option 与 Result 实战(难度 ⭐⭐)

RUST
// ============================================
// 泛型枚举:Option<T> 和 Result<T, E> 的使用
// ============================================

// 自定义 Result 风格枚举
#[derive(Debug)]
enum MyResult<T, E> {
    Success(T),
    Failure(E),
}

// 除法函数:返回 Result 风格
fn safe_divide<T>(a: T, b: T) -> MyResult<T, String>
where
    T: std::ops::Div<Output = T> + std::cmp::PartialEq + From<u8> + Copy,
{
    if b == 0.into() {
        MyResult::Failure("Division by zero".to_string())
    } else {
        MyResult::Success(a / b)
    }
}

// 使用标准库的 Option<T>
fn find_in_vector<T: PartialEq>(vec: &[T], target: &T) -> Option<usize> {
    for (i, item) in vec.iter().enumerate() {
        if item == target {
            return Some(i);
        }
    }
    None
}

// 使用标准库的 Result<T, E>
fn parse_number(s: &str) -> Result<i32, String> {
    s.parse::<i32>().map_err(|e| format!("Parse error: {}", e))
}

fn main() {
    // Option 的使用
    let numbers = vec![10, 20, 30, 40, 50];
    match find_in_vector(&numbers, &30) {
        Some(index) => println!("找到 30,索引: {}", index),
        None => println!("未找到"),
    }
    match find_in_vector(&numbers, &99) {
        Some(index) => println!("找到 99,索引: {}", index),
        None => println!("未找到 99"),
    }

    // Result 的使用
    match parse_number("42") {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("解析失败: {}", e),
    }
    match parse_number("hello") {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("解析失败: {}", e),
    }

    // 自定义 MyResult 的使用
    let result = safe_divide(10.0, 3.0);
    println!("自定义 Result: {:?}", result);

    let result = safe_divide(10.0, 0.0);
    println!("自定义 Result: {:?}", result);
}

输出:

TEXT 📖 仅展示
找到 30,索引: 2
未找到 99
解析成功: 42
解析失败: Parse error: invalid digit found in string
自定义 Result: Success(3.3333333333333335)
自定义 Result: Failure("Division by zero")

Option<T> 只有一个类型参数 T(有值或无值),Result<T, E> 有两个类型参数(成功值类型和错误类型)。泛型枚举让这些类型可以适用于任何数据类型——这是 Rust 标准库最核心的设计。


▶ 示例 4:多个类型参数与单态化(难度 ⭐⭐⭐)

RUST
// ============================================
// 多个类型参数 + 泛型方法的组合使用
// ============================================

use std::fmt::Display;

// 两个类型参数的泛型结构体
#[derive(Debug)]
struct Pair<K, V> {
    key: K,
    value: V,
}

// 为 Pair<K, V> 实现方法
impl<K, V> Pair<K, V> {
    fn new(key: K, value: V) -> Self {
        Pair { key, value }
    }
}

// 约束方法:仅当 K 和 V 都实现了 Display 时才可用
impl<K: Display, V: Display> Pair<K, V> {
    fn print(&self) {
        println!("Key: {}, Value: {}", self.key, self.value);
    }
}

// 泛型方法:混合不同类型的参数
fn mix_and_match<T, U>(a: T, b: U) -> String
where
    T: Display,
    U: Display,
{
    format!("Mixed: {} and {}", a, b)
}

fn main() {
    // 多个类型参数:String 和 i32
    let pair1 = Pair::new("Age".to_string(), 25);
    pair1.print();

    // 多个类型参数:&str 和 f64
    let pair2 = Pair::new("PI", 3.14159);
    pair2.print();

    // 不同类型组合
    let pair3 = Pair::new(100, "HTTP OK");
    // pair3.print();  // ❌ 编译错误:i32 和 &str 都实现了 Display,但这里不会报错
    // 实际上 i32 和 &str 都实现了 Display,所以可以调用
    // 这里只是为了演示约束方法的概念
    pair3.print();

    // 混合不同类型
    println!("{}", mix_and_match(42, "answer"));
    println!("{}", mix_and_match(3.14, 100));
}

输出:

TEXT 📖 仅展示
Key: Age, Value: 25
Key: PI, Value: 3.14159
Key: 100, Value: HTTP OK
Mixed: 42 and answer
Mixed: 3.14 and 100

多个类型参数(<K, V>)让结构体可以持有不同类型的数据。where 子句用于约束类型参数必须满足的条件。单态化时,编译器会为 Pair<String, i32>Pair<&str, f64> 等每种组合生成独立的代码。


▶ 示例 5:综合练习——泛型容器与算法(难度 ⭐⭐⭐)

RUST
// ============================================
// 综合示例:泛型栈 + 泛型搜索算法
// ============================================

use std::fmt::Display;

struct Stack<T> {
    items: Vec<T>,
}

impl<T> Stack<T> {
    fn new() -> Self {
        Stack { items: Vec::new() }
    }

    fn push(&mut self, item: T) {
        self.items.push(item);
    }

    fn pop(&mut self) -> Option<T> {
        self.items.pop()
    }

    fn peek(&self) -> Option<&T> {
        self.items.last()
    }

    fn is_empty(&self) -> bool {
        self.items.is_empty()
    }

    fn len(&self) -> usize {
        self.items.len()
    }
}

impl<T: Display> Stack<T> {
    fn print_all(&self) {
        for item in &self.items {
            print!("{} ", item);
        }
        println!();
    }
}

fn find_first<T: PartialEq>(items: &[T], target: &T) -> Option<usize> {
    items.iter().position(|x| x == target)
}

fn swap_if_greater<T: PartialOrd>(a: &mut T, b: &mut T) {
    if *a > *b {
        std::mem::swap(a, b);
    }
}

fn main() {
    let mut int_stack: Stack<i32> = Stack::new();
    int_stack.push(10);
    int_stack.push(20);
    int_stack.push(30);
    println!("=== 整数栈 ===");
    println!("栈内容: ");
    int_stack.print_all();
    println!("栈顶: {:?}", int_stack.peek());
    println!("弹出: {:?}", int_stack.pop());
    println!("剩余 {} 个元素", int_stack.len());

    let mut str_stack: Stack<&str> = Stack::new();
    str_stack.push("Rust");
    str_stack.push("is");
    str_stack.push("awesome");
    println!("\n=== 字符串栈 ===");
    str_stack.print_all();

    let nums = vec![10, 20, 30, 40, 50];
    println!("\n=== 泛型搜索 ===");
    println!("找 30: 索引 {:?}", find_first(&nums, &30));
    println!("找 99: 索引 {:?}", find_first(&nums, &99));

    let words = vec!["apple", "banana", "cherry"];
    println!("找 'banana': 索引 {:?}", find_first(&words, &"banana"));

    let mut x = 42;
    let mut y = 10;
    println!("\n=== 泛型交换 ===");
    println!("交换前: x={}, y={}", x, y);
    swap_if_greater(&mut x, &mut y);
    println!("交换后: x={}, y={}", x, y);
}

输出:

TEXT 📖 仅展示
=== 整数栈 ===
栈内容: 
10 20 30 
栈顶: Some(30)
弹出: Some(30)
剩余 2 个元素

=== 字符串栈 ===
Rust is awesome 

=== 泛型搜索 ===
找 30: 索引 Some(2)
找 99: 索引 None
找 'banana': 索引 Some(1)

=== 泛型交换 ===
交换前: x=42, y=10
交换后: x=10, y=42

泛型 Stack<T> 对 i32 和 &str 都有效;impl<T: Display> 约束只在类型实现 Display 时才有 print_all 方法;find_first<T: PartialEq>PartialOrd 约束实现泛型比较交换。


❓ 常见问题

Q 泛型和 Box<dyn Any> 有什么区别?
A 泛型是编译时确定类型(静态分发),dyn Any 是运行时确定类型(动态分发)。
Q 单态化(monomorphization)会导致二进制文件太大吗?
A 会,但通常影响不大。
Q 泛型函数和泛型方法有什么区别?
A 泛型函数是独立的函数,泛型方法是定义在类型上的函数。
Q impl<T>impl 块有什么区别?
A impl<T> 为所有 T 实现方法,普通 impl 为具体类型实现方法。
Q 泛型约束(trait bound)的 where 子句和直接在尖括号里写有什么区别?
A 功能相同,但 where 子句在复杂约束时可读性更好。

📖 小节


📝 作业

  1. 难度 ⭐:写一个泛型函数 fn echo<T>(x: T) -> T,它接受一个值并原样返回。在 main 中分别用 i32、f64 和 &str 调用它。
  2. 难度 ⭐⭐:定义一个泛型结构体 Container<T>,它有一个字段 value: T。实现方法 fn get(&self) -> &T 返回值的引用,以及 fn set(&mut self, val: T) 修改值。在 main 中分别用 Container<String>Container<i32> 测试。
  3. 难度 ⭐⭐⭐:写一个泛型函数 fn merge_arrays<T>(a: &[T], b: &[T]) -> Vec<T> 合并两个切片,返回一个新 Vec。要求 T 实现了 Clone。然后在 main 中分别合并两个 i32 切片和两个 &str 切片。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏