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

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

第4章 - 智能指针

嗨,朋友!我是长安。

这一章我们要学习 Rust 中一些非常有用的特殊类型——智能指针(Smart Pointers)。

智能指针和普通的引用不同,它们不仅仅是指向数据的指针,还拥有一些额外的元数据和能力。

🤔 什么是智能指针?

普通的引用只是“借用”数据,不拥有所有权。而智能指针通常:

  1. 拥有它指向的数据
  2. 离开作用域时会自动清理数据
  3. 有特殊的能力(比如引用计数、内部可变性等)

长安说

普通引用就像"借书卡",而智能指针像"书的所有权证书"。拥有智能指针,就拥有了数据。

最常用的智能指针有三个:

  • Box<T> - 堆上分配
  • Rc<T> - 引用计数
  • RefCell<T> - 内部可变性

让我们一个一个来看。

📦 Box<T> - 堆上分配

为什么需要 Box?

默认情况下,Rust 把数据存储在栈上。但有时候你需要把数据放在堆上:

  1. 数据太大,不想复制
  2. 编译时不知道数据的大小
  3. 想要转移所有权但不在乎具体类型

基本用法

fn main() {
    let b = Box::new(5);  // 在堆上分配一个 i32
    println!("b = {}", b);
}  // b 离开作用域,堆上的数据被清理

这看起来和普通变量没什么区别,但 Box 的真正威力在于处理递归类型。

递归类型示例:链表

不使用 Box 会报错:

enum List {
    Cons(i32, List),  // ❌ 错误!编译器不知道多大
    Nil,
}

使用 Box 解决:

enum List {
    Cons(i32, Box<List>),  // ✅ 指针大小是固定的
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

长安说

Box 的大小是固定的(就是一个指针的大小),所以编译器可以计算出结构体的大小。

实际应用:存储大对象

struct LargeStruct {
    data: [u8; 10000],  // 10KB 的数据
}

fn main() {
    // 避免栈上分配大对象
    let large = Box::new(LargeStruct {
        data: [0; 10000],
    });
    
    println!("Large object allocated on heap!");
}

🔗 Rc<T> - 引用计数

为什么需要 Rc?

有时候一个值需要有多个所有者。比如图结构中,多个边可能指向同一个节点。

Rc<T> 就是 Reference Counted(引用计数)的缩写,它会记录有多少个引用指向这个值,只有当引用计数为 0 时才会清理数据。

注意

Rc<T> 只能用于单线程场景!多线程要用 Arc<T>(下一章会讲)。

基本用法

use std::rc::Rc;

fn main() {
    let a = Rc::new(5);
    println!("引用计数: {}", Rc::strong_count(&a));  // 1
    
    let b = Rc::clone(&a);  // 增加引用计数
    println!("引用计数: {}", Rc::strong_count(&a));  // 2
    
    {
        let c = Rc::clone(&a);
        println!("引用计数: {}", Rc::strong_count(&a));  // 3
    }  // c 离开作用域
    
    println!("引用计数: {}", Rc::strong_count(&a));  // 2
}  // a 和 b 离开作用域,计数为 0,数据被清理

长安说

Rc::clone(&a) 不会深拷贝数据,只是增加引用计数,非常快!

实际应用:共享数据

use std::rc::Rc;

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    
    // b 和 c 共享 a
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));
    
    println!("a 的引用计数: {}", Rc::strong_count(&a));  // 3
}

🔒 RefCell<T> - 内部可变性

为什么需要 RefCell?

记得借用规则吗?

  • 同一时间,只能有一个可变引用,或多个不可变引用

但有时候你需要在拥有不可变引用时修改数据,RefCell<T> 就是为此设计的。

RefCell<T> 使用内部可变性模式:在运行时检查借用规则,而不是编译时。

注意

如果运行时违反借用规则,程序会 panic!

基本用法

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    
    // 借用不可变引用
    let value = data.borrow();
    println!("value: {}", *value);
    drop(value);  // 释放借用
    
    // 借用可变引用
    let mut value_mut = data.borrow_mut();
    *value_mut += 10;
    drop(value_mut);
    
    println!("modified: {}", *data.borrow());
}

实际应用:Mock 对象

use std::cell::RefCell;

trait Messenger {
    fn send(&self, msg: &str);
}

struct MockMessenger {
    sent_messages: RefCell<Vec<String>>,
}

impl MockMessenger {
    fn new() -> MockMessenger {
        MockMessenger {
            sent_messages: RefCell::new(vec![]),
        }
    }
}

impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        // 虽然 self 是不可变的,但可以修改 RefCell 内部的数据
        self.sent_messages.borrow_mut().push(String::from(message));
    }
}

fn main() {
    let messenger = MockMessenger::new();
    messenger.send("消恗1");
    messenger.send("消恗2");
    
    assert_eq!(messenger.sent_messages.borrow().len(), 2);
}

🔥 组合使用:Rc<RefCell<T>>

最强大的组合:多个所有者 + 内部可变性!

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });
    
    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
    
    // leaf 有两个所有者:leaf 和 branch
    println!("leaf 引用计数: {}", Rc::strong_count(&leaf));  // 2
    
    // 修改 branch 的 children
    branch.children.borrow_mut().push(Rc::clone(&leaf));
    
    println!("leaf 引用计数: {}", Rc::strong_count(&leaf));  // 3
}

📊 智能指针对比

类型所有权可变性检查时机用途
Box<T>单一编译时编译时堆上分配
Rc<T>多个不可变编译时共享数据
RefCell<T>单一内部可变运行时内部可变性
Arc<T>多个不可变编译时线程安全共享
Mutex<T>多个可变运行时线程安全可变

长安的选择指南

  • 需要在堆上分配?→ Box<T>
  • 需要多个所有者?→ Rc<T>(单线程)或 Arc<T>(多线程)
  • 需要内部可变?→ RefCell<T>(单线程)或 Mutex<T>(多线程)
  • 需要多个所有者 + 可变?→ Rc<RefCell<T>>(单线程)或 Arc<Mutex<T>>(多线程)

💡 小结

在这一章,我们学会了:

  • 智能指针拥有数据并有额外能力
  • Box<T>:在堆上分配数据
  • Rc<T>:允许多个所有者(引用计数)
  • RefCell<T>:内部可变性,运行时检查借用规则
  • Rc<RefCell<T>>:组合使用,多个所有者 + 可变性

核心要点:

  • Box<T> 解决递归类型和大对象问题
  • Rc<T> 允许数据有多个所有者
  • RefCell<T> 在不可变引用下修改数据
  • 根据需求选择合适的智能指针

长安的建议

智能指针不难,关键是理解它们解决什么问题:

  1. Box:最简单,就是把数据放堆上
  2. Rc:共享所有权,多个变量指向同一个数据
  3. RefCell:绕过编译时借用检查,在运行时检查

大多数情况下,Box 就够用了。只有在需要共享所有权或内部可变性时,才用 Rc 或 RefCell。

🚀 下一步

恭喜你掌握了智能指针!现在你已经掌握了 Rust 的大部分高级特性。

最后一章,我们要学习 Rust 最强大的特性之一——并发编程!

第5章 - 并发编程 →

💪 练习题

  1. 使用 Box 创建一个递归的二叉树结构
  2. 使用 Rc 创建一个共享的配置对象,多个组件都能访问
  3. 使用 RefCell 实现一个计数器,虽然对象是不可变的,但能增加计数
  4. 结合 Rc<RefCell<T>> 创建一个可以共享且可修改的数据结构
答案示例
use std::cell::RefCell;
use std::rc::Rc;

// 练习1
enum BinaryTree {
    Node(i32, Box<BinaryTree>, Box<BinaryTree>),
    Leaf,
}

use BinaryTree::{Node, Leaf};

fn create_tree() -> BinaryTree {
    Node(
        1,
        Box::new(Node(2, Box::new(Leaf), Box::new(Leaf))),
        Box::new(Node(3, Box::new(Leaf), Box::new(Leaf))),
    )
}

// 练习2
struct Config {
    max_connections: u32,
    timeout: u64,
}

fn main() {
    let config = Rc::new(Config {
        max_connections: 100,
        timeout: 30,
    });
    
    let component1 = Rc::clone(&config);
    let component2 = Rc::clone(&config);
    
    println!("Component1 max: {}", component1.max_connections);
    println!("Component2 timeout: {}", component2.timeout);
}

// 练习3
struct Counter {
    count: RefCell<i32>,
}

impl Counter {
    fn new() -> Self {
        Counter {
            count: RefCell::new(0),
        }
    }
    
    fn increment(&self) {
        *self.count.borrow_mut() += 1;
    }
    
    fn get(&self) -> i32 {
        *self.count.borrow()
    }
}

fn test_counter() {
    let counter = Counter::new();
    counter.increment();
    counter.increment();
    assert_eq!(counter.get(), 2);
}

// 练习4
#[derive(Debug)]
struct SharedData {
    value: RefCell<i32>,
}

fn test_shared() {
    let data = Rc::new(SharedData {
        value: RefCell::new(0),
    });
    
    let data1 = Rc::clone(&data);
    let data2 = Rc::clone(&data);
    
    *data1.value.borrow_mut() += 10;
    *data2.value.borrow_mut() += 5;
    
    println!("Final value: {}", *data.value.borrow());  // 15
}
最近更新: 2025/12/26 18:13
Contributors: 王长安
Prev
第3章 - 生命周期
Next
第5章 - 并发编程