长安的 Rust 入门教程长安的 Rust 入门教程
首页
基础教程
进阶内容
Rust 官网
编程指南
首页
基础教程
进阶内容
Rust 官网
编程指南
  • 进阶内容

    • 🚀 进阶内容
    • 第1章 - 泛型
    • 第2章 - Trait
    • 第3章 - 生命周期
    • 第4章 - 智能指针
    • 第5章 - 并发编程

第1章 - 泛型

嗨,朋友!我是长安。

欢迎来到进阶教程的第一章!今天我们要聊一个非常强大的特性——泛型(Generics)。

泛型可以让你写出适用于多种类型的代码,避免重复。听起来有点抽象?别担心,我会用最简单的方式给你讲清楚。

🤔 为什么需要泛型?

想象一下,你要写两个函数,一个找出整数列表中的最大值,一个找出浮点数列表中的最大值:

fn largest_i32(list: &[i32]) -> i32 {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

fn largest_f64(list: &[f64]) -> f64 {
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

你会发现,这两个函数的逻辑完全一样,只是类型不同。如果再来一个 char 类型,还要再写一遍?太麻烦了!

泛型的解决方案:写一个函数,适用于所有类型!

长安说

泛型就像是一个模板,你定义一次,可以用于多种类型。就像做蛋糕的模具,同一个模具可以做巧克力蛋糕、草莓蛋糕、抹茶蛋糕!

📖 泛型函数

基本语法

使用 <T> 来定义泛型参数:

fn largest<T>(list: &[T]) -> T {
    let mut largest = list[0];
    for &item in list {
        if item > largest {  // ❌ 这样还不能编译
            largest = item;
        }
    }
    largest
}

这里 T 是一个类型参数,代表"任意类型"。

注意

上面的代码还不能编译!因为不是所有类型都能用 > 比较。我们需要添加 trait 约束,下一章会详细讲。现在先看一个能编译的例子。

实际例子:交换两个值

fn swap<T>(a: T, b: T) -> (T, T) {
    (b, a)
}

fn main() {
    // 用于整数
    let (x, y) = swap(1, 2);
    println!("x = {}, y = {}", x, y);  // x = 2, y = 1
    
    // 用于字符串
    let (s1, s2) = swap("hello", "world");
    println!("s1 = {}, s2 = {}", s1, s2);  // s1 = world, s2 = hello
    
    // 用于浮点数
    let (f1, f2) = swap(3.14, 2.71);
    println!("f1 = {}, f2 = {}", f1, f2);  // f1 = 2.71, f2 = 3.14
}

同一个 swap 函数,可以用于不同类型!Rust 编译器会根据使用情况自动推断 T 的具体类型。

多个泛型参数

你可以使用多个泛型参数:

fn print_pair<T, U>(a: T, b: U) {
    println!("a = {:?}, b = {:?}", a, b);
}

fn main() {
    print_pair(5, "hello");        // T=i32, U=&str
    print_pair(3.14, true);        // T=f64, U=bool
    print_pair("rust", 2024);      // T=&str, U=i32
}

🏗️ 泛型结构体

结构体也可以使用泛型:

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);
}

注意

在上面的例子中,x 和 y 必须是相同类型,因为它们都是 T。

如果你想让 x 和 y 可以是不同类型,需要两个泛型参数:

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let mixed_point = Point { x: 5, y: 4.0 };  // x是i32,y是f64
    println!("混合点: ({}, {})", mixed_point.x, mixed_point.y);
}

🔧 泛型方法

结构体的方法也可以使用泛型:

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
    
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point::new(5, 10);
    println!("x 坐标: {}", p.x());
}

注意 impl<T> 中的 <T>,这表示我们为泛型类型 T 实现方法。

为特定类型实现方法

你也可以只为某个具体类型实现方法:

impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let p = Point { x: 3.0, y: 4.0 };
    println!("距离原点: {}", p.distance_from_origin());  // 5.0
    
    // let p2 = Point { x: 3, y: 4 };  
    // p2.distance_from_origin();  // ❌ 错误!只有 Point<f64> 有这个方法
}

📦 泛型枚举

你已经见过泛型枚举了!Option<T> 和 Result<T, E> 都是泛型枚举:

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

自己定义一个泛型枚举:

enum Container<T> {
    Single(T),
    Double(T, T),
    Triple(T, T, T),
}

fn main() {
    let c1 = Container::Single(5);
    let c2 = Container::Double("hello", "world");
    let c3 = Container::Triple(1.1, 2.2, 3.3);
    
    match c2 {
        Container::Double(a, b) => println!("{} {}", a, b),
        _ => {}
    }
}

⚡ 泛型的性能

你可能会担心:泛型会不会影响性能?

好消息:在 Rust 中,泛型是零成本抽象!

编译器会为每个具体类型生成专门的代码,这个过程叫做单态化(Monomorphization)。

例如这段代码:

fn swap<T>(a: T, b: T) -> (T, T) {
    (b, a)
}

fn main() {
    swap(1, 2);
    swap("a", "b");
}

编译器会生成两个版本的 swap 函数:

// 编译器自动生成
fn swap_i32(a: i32, b: i32) -> (i32, i32) {
    (b, a)
}

fn swap_str(a: &str, b: &str) -> (&str, &str) {
    (b, a)
}

所以运行时性能和手写的代码完全一样!

长安说

这就是 Rust 的魔法:你写的是泛型代码(简洁、不重复),但运行的是具体类型的代码(快速、高效)!

🎯 实战例子:通用容器

让我们写一个简单的容器,可以存储任意类型的值:

struct Box<T> {
    value: T,
}

impl<T> Box<T> {
    fn new(value: T) -> Self {
        Box { value }
    }
    
    fn get(&self) -> &T {
        &self.value
    }
    
    fn set(&mut self, value: T) {
        self.value = value;
    }
}

fn main() {
    let mut int_box = Box::new(42);
    println!("整数盒子: {}", int_box.get());
    
    int_box.set(100);
    println!("修改后: {}", int_box.get());
    
    let string_box = Box::new(String::from("Rust"));
    println!("字符串盒子: {}", string_box.get());
}

💡 小结

在这一章,我们学会了:

  • 泛型让你写出适用于多种类型的代码
  • 使用 <T> 定义泛型参数
  • 泛型函数:一个函数,多种类型
  • 泛型结构体:灵活的数据结构
  • 泛型枚举:Option<T> 和 Result<T, E>
  • 零成本抽象:泛型不会影响性能

核心要点:

  • 泛型参数用 <T> 表示
  • 可以有多个泛型参数:<T, U, V>
  • 编译器会自动推断类型
  • 单态化保证性能

长安的建议

刚开始用泛型可能会觉得语法有点复杂,但多写几次就习惯了。泛型是 Rust 中非常强大的特性,能让你的代码更加灵活和可复用!

🚀 下一步

你可能注意到,我们还没讲如何对泛型进行约束(比如要求 T 必须能比较大小)。

下一章,我们会学习 Trait,它和泛型配合使用,威力无穷!

第2章 - Trait →

💪 练习题

  1. 写一个泛型函数 first<T>,返回切片的第一个元素(返回 Option<&T>)
  2. 定义一个泛型结构体 Pair<T>,包含两个相同类型的值,并实现方法打印这对值
  3. 定义一个泛型枚举 Choice<T, U>,可以是 First(T) 或 Second(U)
  4. 为 Point<T> 实现一个方法 swap,交换 x 和 y 的值
答案示例
// 练习1
fn first<T>(list: &[T]) -> Option<&T> {
    if list.is_empty() {
        None
    } else {
        Some(&list[0])
    }
}

// 练习2
struct Pair<T> {
    first: T,
    second: T,
}

impl<T: std::fmt::Display> Pair<T> {
    fn print(&self) {
        println!("({}, {})", self.first, self.second);
    }
}

// 练习3
enum Choice<T, U> {
    First(T),
    Second(U),
}

// 练习4
impl<T> Point<T> {
    fn swap(&mut self) {
        std::mem::swap(&mut self.x, &mut self.y);
    }
}

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    if let Some(f) = first(&numbers) {
        println!("第一个元素: {}", f);
    }
    
    let pair = Pair { first: 10, second: 20 };
    pair.print();
    
    let choice1: Choice<i32, &str> = Choice::First(42);
    let choice2: Choice<i32, &str> = Choice::Second("hello");
}
最近更新: 2025/12/26 18:01
Contributors: 王长安
Prev
🚀 进阶内容
Next
第2章 - Trait