Rust从入门到精通之进阶篇:16.智能指针

时间:2025-03-27 10:23:31

智能指针

智能指针是一种数据结构,它们的行为类似于指针,但具有额外的元数据和功能。在 Rust 中,智能指针通常实现了 DerefDrop 特质,允许它们像引用一样工作并在离开作用域时自动清理资源。在本章中,我们将探索 Rust 中的各种智能指针类型及其用途。

引用回顾

在深入智能指针之前,让我们先回顾一下 Rust 中的普通引用:

fn main() {
    let x = 5;
    let y = &x;  // y 是 x 的引用
    
    assert_eq!(5, x);
    assert_eq!(5, *y);  // 使用解引用运算符 * 访问引用的值
}

引用是 Rust 中最简单的指针类型,它们没有任何特殊功能,只是借用值而不获取所有权。

Box

Box<T> 是 Rust 中最简单的智能指针类型,它允许你将值存储在堆上而不是栈上:

fn main() {
    let b = Box::new(5);  // 在堆上分配值 5
    println!("b = {}", b);
    
    // 可以像使用引用一样使用 Box
    assert_eq!(5, *b);
} // b 离开作用域时,它指向的堆内存会被自动释放

Box 的主要用途

1. 存储已知大小但较大的数据

当你有一个较大的数据结构,但不想在栈上分配内存时,可以使用 Box

struct LargeStruct {
    data: [u8; 1000000],  // 1MB 的数据
}

fn main() {
    // 在栈上分配可能导致栈溢出
    // let large_struct = LargeStruct { data: [0; 1000000] };
    
    // 在堆上分配更安全
    let large_struct = Box::new(LargeStruct { data: [0; 1000000] });
    
    println!("结构体已创建");
}
2. 创建递归类型

递归类型的大小在编译时无法确定,因此需要使用 Box 来创建:

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

fn main() {
    let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil))))));
    
    // 使用模式匹配访问列表元素
    let mut current = &list;
    while let List::Cons(value, next) = current {
        println!("值: {}", value);
        current = next;
    }
}
3. 特质对象

Box 可以用来创建特质对象,允许在运行时使用动态分发:

trait Draw {
    fn draw(&self);
}

struct Circle {
    radius: f64,
}

impl Draw for Circle {
    fn draw(&self) {
        println!("画一个半径为 {} 的圆", self.radius);
    }
}

struct Square {
    side: f64,
}

impl Draw for Square {
    fn draw(&self) {
        println!("画一个边长为 {} 的正方形", self.side);
    }
}

fn main() {
    let shapes: Vec<Box<dyn Draw>> = vec![
        Box::new(Circle { radius: 1.0 }),
        Box::new(Square { side: 2.0 }),
    ];
    
    for shape in shapes {
        shape.draw();
    }
}

Deref 特质

Deref 特质允许自定义解引用运算符 * 的行为。智能指针通过实现 Deref 特质,使它们的行为类似于引用:

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;
    
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);
    
    assert_eq!(5, x);
    assert_eq!(5, *y);  // 相当于 *(y.deref())
}

解引用强制转换

Rust 提供了解引用强制转换(deref coercion)功能,当将一个实现了 Deref 特质的类型的值作为参数传递给函数或方法时,如果参数类型不匹配,Rust 会自动应用 deref 方法:

fn hello(name: &str) {
    println!("你好,{}!", name);
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);  // 解引用强制转换:&MyBox<String> -> &String -> &str
}

Drop 特质

Drop 特质允许你自定义当值离开作用域时发生的行为。这对于释放资源(如文件句柄或网络连接)非常有用:

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("释放 CustomSmartPointer,数据: `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("我的数据") };
    let d = CustomSmartPointer { data: String::from("其他数据") };
    println!("创建了智能指针");
    // c 和 d 在这里离开作用域,Rust 会自动调用它们的 drop 方法
}

提前丢弃值

有时你可能需要提前丢弃一个值。Rust 提供了 std::mem::drop 函数来实现这一点:

fn main() {
    let c = CustomSmartPointer { data: String::from("提前丢弃") };
    println!("创建了智能指针");
    drop(c);  // 手动调用 drop 函数
    println!("在 main 函数结束前丢弃了智能指针");
}

Rc

Rc<T>(引用计数,Reference Counting)允许多个所有者共享同一数据的所有权。当最后一个所有者离开作用域时,数据才会被清理:

use std::rc::Rc;

fn main() {
    let a = Rc::new(5);  // 创建一个引用计数的值
    println!("创建 a,引用计数 = {}", Rc::strong_count(&a));  // 1
    
    let b = Rc::clone(&a);  // 增加引用计数,而不是复制数据
    println!("创建 b,引用计数 = {}", Rc::strong_count(&a));  // 2
    
    {
        let c = Rc::clone(&a);  // 再次增加引用计数
        println!("创建 c,引用计数 = {}", Rc::strong_count(&a));  // 3
    }  // c 离开作用域,引用计数减少
    
    println!("c 离开作用域后,引用计数 = {}", Rc::strong_count(&a));  // 2
    
    // 可以通过任何一个引用访问数据
    println!("a = {}, b = {}", a, b);
}  // a 和 b 离开作用域,引用计数变为 0,数据被清理

Rc 的限制

Rc<T> 只能用于单线程场景,并且只提供不可变访问:

use std::rc::Rc;

fn main() {
    let a = Rc::new(vec![1, 2, 3]);
    let b = Rc::clone(&a);
    
    // 错误:不能获取可变引用
    // a.push(4);
    
    println!("a = {:?}, b = {:?}", a, b);
}

RefCell 和内部可变性

内部可变性是 Rust 的一种设计模式,它允许你在拥有不可变引用的情况下修改数据。RefCell<T> 提供了内部可变性:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    
    // 获取不可变引用
    let a = data.borrow();
    println!("a = {}", a);
    
    // 必须先释放不可变引用,才能获取可变引用
    drop(a);
    
    // 获取可变引用并修改值
    let mut b = data.borrow_mut();
    *b += 1;
    println!("b = {}", b);
}

借用规则在运行时检查

与普通引用不同,RefCell<T> 在运行时而不是编译时检查借用规则:

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(5);
    
    let a = data.borrow();
    let b = data.borrow();  // 可以同时有多个不可变借用
    
    println!("a = {}, b = {}", a, b);
    
    // 下面的代码会在运行时 panic,因为已经有不可变借用
    // let mut c = data.borrow_mut();
}

结合 Rc 和 RefCell

Rc<T>RefCell<T> 经常一起使用,以提供多所有者和内部可变性:

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

fn main() {
    let data = Rc::new(RefCell::new(vec![1, 2, 3]));
    
    let a = Rc::clone(&data);
    let b = Rc::clone(&data);
    
    // 通过 a 修改数据
    a.borrow_mut().push(4);
    
    // 通过 b 也能看到修改后的数据
    println!("b = {:?}", b.borrow());
}

Weak

Weak<T> 提供了对 Rc<T> 数据的非所有权引用,不会增加强引用计数,因此不会阻止数据的清理:

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

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

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: None,
        children: RefCell::new(vec![]),
    });
    
    println!("leaf 强引用计数 = {}", Rc::strong_count(&leaf));  // 1
    println!("leaf 弱引用计数 = {}", Rc::weak_count(&leaf));    // 0
    
    {
        let branch = Rc::new(Node {
            value: 5,
            parent: None,
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });
        
        // 设置 leaf 的父节点为 branch,使用 Weak 引用避免循环引用
        leaf.parent = Some(Rc::downgrade(&branch));
        
        println!("branch 强引用计数 = {}", Rc::strong_count(&branch));  // 1
        println!("branch 弱引用计数 = {}", Rc::weak_count(&branch));    // 1
        
        println!("leaf 强引用计数 = {}", Rc::strong_count(&leaf));      // 2
        println!("leaf 弱引用计数 = {}", Rc::weak_count(&leaf));        // 0
        
        // 访问 leaf 的父节点
        if let Some(parent) = &leaf.parent {
            // 尝试将 Weak 引用升级为 Rc
            if let Some(parent) = parent.upgrade() {
                println!("leaf 的父节点是 {}", parent.value);
            }
        }
    }  // branch 离开作用域,强引用计数变为 0,数据被清理
    
    // branch 已被清理,所以 leaf.parent 现在是悬空的 Weak 引用
    println!("leaf 强引用计数 = {}", Rc::strong_count(&leaf));  // 1
    
    // 尝试访问已清理的父节点
    if let Some(parent) = &leaf.parent {
        if let Some(_) = parent.upgrade() {
            println!("leaf 的父节点仍然存在");
        } else {
            println!("leaf 的父节点已被清理");
        }
    }
}

Arc

Arc<T>(原子引用计数,Atomic Reference Counting)是 Rc<T> 的线程安全版本,可以在多线程环境中安全地共享数据:

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

fn main() {
    let data = Arc::new(vec![1, 2, 3]);
    let mut handles = vec![];
    
    for i in 0..3 {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            println!("线程 {}: 数据 = {:?}", i, data_clone);
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
}

Mutex

Mutex<T>(互斥锁)提供了线程安全的内部可变性,确保在任何时刻只有一个线程可以访问数据:

use std::sync::Mutex;
use std::thread;

fn main() {
    let counter = Mutex::new(0);
    let mut handles = vec![];
    
    for _ in 0..10 {
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    println!("结果: {}", *counter.lock