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> 是一个泛型结构体,x 和 y 的类型相同(都是 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%

🙏 帮我们做得更好

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

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