跳转至

智能指针

智能指针

📚 章节概述

智能指针是拥有额外元数据和功能的指针。Rust 标准库提供了多种智能指针类型,本章将介绍最常用的几种:Box、Rc、Arc、RefCell 和 Weak。

🎯 学习目标

  • 理解智能指针的概念
  • 掌握 Box 的使用场景
  • 学会使用 Rc 实现多重所有权
  • 理解 Arc 的线程安全特性
  • 掌握 RefCell 的内部可变性
  • 学会使用 Weak 避免循环引用

📖 智能指针基础

1.1 什么是智能指针

智能指针是数据结构,它们的行为类似指针,但拥有额外的元数据和功能:

  • Box:堆上分配
  • Rc:引用计数
  • Arc:原子引用计数(线程安全)
  • RefCell:运行时借用检查

1.2 智能指针与引用的区别

Rust
// 引用:只借用数据
let x = 5;
let y = &x;

// 智能指针:拥有数据
let x = 5;
let y = Box::new(x);

📦 Box

2.1 使用 Box 在堆上分配

Rust
fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

2.2 Box 的使用场景

递归类型

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

大数据转移所有权

Rust
struct LargeData {
    data: [u8; 1000000],
}

fn main() {
    let large_data = LargeData { data: [0; 1000000] };
    let boxed_data = Box::new(large_data);
    // 只传递指针,不复制数据
}

Trait 对象

Rust
trait Draw {
    fn draw(&self);
}

struct Button {
    width: u32,
    height: u32,
}

impl Draw for Button {
    fn draw(&self) {
        println!("Drawing button {}x{}", self.width, self.height);
    }
}

fn main() {
    let components: Vec<Box<dyn Draw>> = vec![  // Box<dyn Trait> 动态分发,运行时多态
        Box::new(Button { width: 100, height: 50 }),
    ];

    for component in components {
        component.draw();
    }
}

🔗 Rc

3.1 Rc 基础

Rust
use std::rc::Rc;

fn main() {
    let a = Rc::new(5);
    let b = Rc::clone(&a);
    let c = Rc::clone(&a);

    println!("count after creating c: {}", Rc::strong_count(&a));
}

3.2 Rc 的使用场景

Rust
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)))));
    let b = Cons(3, Rc::clone(&a));
    let c = Cons(4, Rc::clone(&a));

    println!("count after creating b: {}", Rc::strong_count(&a));
    println!("count after creating c: {}", Rc::strong_count(&a));
}

⚡ Arc

4.1 Arc 基础

Rust
use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..5 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            println!("Thread {}: {:?}", i, data);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

4.2 Arc 的使用场景

Rust
use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

🔓 RefCell

5.1 RefCell 基础

Rust
use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);

    // 获取可变引用
    *data.borrow_mut() += 1;

    // 获取不可变引用
    println!("data: {}", data.borrow());
}

5.2 内部可变性

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

pub struct LimitTracker<'a, T: Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

impl<'a, T> LimitTracker<'a, T>
where
    T: Messenger,
{
    pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
        LimitTracker {
            messenger,
            value: 0,
            max,
        }
    }

    pub fn set_value(&mut self, value: usize) {
        self.value = value;

        let percentage = self.value as f64 / self.max as f64;

        if percentage >= 1.0 {
            self.messenger.send("Error: You are over your quota!");
        } else if percentage >= 0.9 {
            self.messenger.send("Urgent warning: You've used 90% of your quota!");
        } else if percentage >= 0.75 {
            self.messenger.send("Warning: You've used 75% of your quota!");
        }
    }
}

5.3 Rc

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

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

    // Rc自动解引用到RefCell -> borrow_mut()获取可变借用RefMut<i32> -> *解引用修改内部值
    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

🔗 Weak

6.1 避免循环引用

Rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

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

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
        parent: RefCell::new(Weak::new()),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
        parent: RefCell::new(Weak::new()),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

📝 练习题

练习 1:Box 使用

Rust
// TODO: 使用 Box 实现一个二叉树
// 包含 value, left, right 三个字段

enum BinaryTree {
    // TODO: 定义变体
}

fn main() {
    // TODO: 创建一个简单的二叉树并打印
}

练习 2:Rc 使用

Rust
use std::rc::Rc;

// TODO: 使用 Rc 实现一个图结构
// 多个节点可以共享同一个子节点

struct Node {
    // TODO: 定义字段
}

fn main() {
    // TODO: 创建图结构并测试引用计数
}

练习 3:RefCell 使用

Rust
use std::cell::RefCell;

// TODO: 实现一个简单的缓存结构
// 使用 RefCell 实现内部可变性

struct Cache<T> {
    // TODO: 定义字段
}

impl<T: Clone> Cache<T> {
    // TODO: 实现方法
}

fn main() {
    // TODO: 测试缓存
}

练习 4:Weak 使用

Rust
use std::cell::RefCell;
use std::rc::{Rc, Weak};

// TODO: 实现一个双向链表
// 使用 Weak 避免循环引用

struct Node {
    // TODO: 定义字段
}

fn main() {
    // TODO: 创建双向链表并测试
}

💡 最佳实践

1. 选择合适的智能指针

Rust
// Box: 单一所有权,堆上分配
let boxed = Box::new(5);

// Rc: 多重所有权,单线程
let shared = Rc::new(5);

// Arc: 多重所有权,多线程
use std::sync::Arc;
let shared = Arc::new(5);

// RefCell: 内部可变性,单线程
use std::cell::RefCell;
let mutable = RefCell::new(5);

2. 避免循环引用

Rust
// 好:使用 Weak 避免循环引用
use std::rc::{Rc, Weak};
let weak = Rc::downgrade(&rc);

// 避免:直接使用 Rc 创建循环引用

3. 使用 Rc::clone 而不是 clone

Rust
// 好
let b = Rc::clone(&a);

// 避免
let b = a.clone(); // 可能深度克隆

4. 检查引用计数

Rust
// 好:检查引用计数
println!("count: {}", Rc::strong_count(&rc));

// 避免:忽略引用计数

⚠️ 常见错误

1. 运行时借用检查失败

Rust
// 错误:运行时 panic
use std::cell::RefCell;
let data = RefCell::new(5);
let ref1 = data.borrow();
let ref2 = data.borrow_mut(); // panic!

// 正确:释放引用后再借用
use std::cell::RefCell;
let data = RefCell::new(5);
{
    let ref1 = data.borrow();
    println!("{}", ref1);
}
let ref2 = data.borrow_mut();
*ref2 += 1;

2. 循环引用导致内存泄漏

Rust
// 错误:循环引用
use std::rc::Rc;
use std::cell::RefCell;

struct Node {
    next: RefCell<Option<Rc<Node>>>,
}

let a = Rc::new(Node { next: RefCell::new(None) });
let b = Rc::new(Node { next: RefCell::new(Some(Rc::clone(&a))) });
*a.next.borrow_mut() = Some(Rc::clone(&b));

// 内存泄漏!a 和 b 的引用计数永远不会为 0

3. 在多线程中使用 Rc

Rust
// 错误:Rc 不是线程安全的
use std::rc::Rc;
use std::thread;

let data = Rc::new(5);
thread::spawn(move || {
    println!("{}", data); // 编译错误
});

// 正确:使用 Arc
use std::sync::Arc;
let data = Arc::new(5);
thread::spawn(move || {
    println!("{}", data);
});

📚 扩展阅读

🎯 本章小结

本章介绍了 Rust 的智能指针:

  • ✅ 理解智能指针的概念
  • ✅ 掌握 Box 的使用场景
  • ✅ 学会使用 Rc 实现多重所有权
  • ✅ 理解 Arc 的线程安全特性
  • ✅ 掌握 RefCell 的内部可变性
  • ✅ 学会使用 Weak 避免循环引用

下一章: 我们将学习 Rust 的宏编程,包括声明宏、过程宏和宏规则。