Rust智能指针

概述

智能指针是结构体,通过实现Deref和Drop trait,模拟指针行为并管理内存生命周期

  • Deref: 允许智能指针像普通指针一样被解引用
  • Drop: 定义指针离开作用域时的清理逻辑

Rc

Rc Reference Counting Pointer,是一种共享所有权的智能指针,适用于单线程环境下多个部分代码同时拥有对同一数据的访问权

struct RcBox<T> {
    data: T,          // 实际数据
    strong: usize,    // 强引用计数
    weak: usize,      // 弱引用计数
}

特性

  • 共享所有权
  • 引用计数
  • 不可变借用: Rc仅提供原数据的不可变引用
  • 单线程限制:非线程安全

方法

// Rc::new(Value :T) -> Rc<T>
use std::rc::Rc;

let rc = Rc::new(42); // 引用计数 =1 

// Rc::clone(&rc) -> Rc<T>,克隆Rc示例,引用计数加一
let rc_clone = Rc::clone(&rc); // 引用计数 = 2

// Rc::strong_count(&rc) -> usize, 获取强引用计数
println!("Count: {}", Rc::strong_count(&rc)); // 输出 2

// Rc::downgrade(&rc) -> Weak<T> 创建弱引用,不增加引用计数
// Rc 可能导致循环引用,造成内存泄漏。此时需结合 Weak<T> 弱引用打破循环
let weak = Rc::downgrade(&rc) 

Box

Box 是Rust中最基础的智能指针,用于在上分配数据,并提供对该数据的唯一所有权,主要作用是将数据从栈移动到堆以解决以下问题:

  • 存储动态大小类型:如递归、trait
  • 避免大对象栈溢出:栈空间有限,大对象更适合在堆上分配
  • 明确所有权转移:强制数据在堆上唯一归属,简化所有权规则

特性

  • 唯一所有权
  • 自动内存释放:当Box离开作用域时,会调用Drop trait自动释放堆内存
  • 零运行时开销:Box本身仅是一个指针
  • 支持Deref和Drop:解引用和自动清理

方法

let b = Box::new(43); // 43在堆上存储
println!("value = {}", *b);
let b2 = b; // 所有权转移到b2

典型场景

  1. 递归类型如链表,rust要求类型在编译期有固定大小,递归类型无法直接定义,使用Box分配在堆上
enum Node{
    Cons(i32, Box<Node>), // 使用 Box 包裹下一节点
    Nil
}

let Node = Node::Cons(1, Box::new(Node::Cons(2, Box::new(Node::Nil))));
  1. 转移大对象所有权:直接传递大对象时如数组,会引发拷贝效率低
fn process(data: Box<[u8; 1_000_000]>) { /* ... */ }

let big_data = Box::new([0u8; 1_000_000]); // 堆上分配
process(big_data); // 仅传递指针,无数据拷贝
  1. Trait对象动态分发,Box
trait Animal {
    fn speak(&self);
}

struct Dog;
impl Animal for Dog { /* ... */ }

struct Cat;
impl Animal for Cat { /* ... */ }

let animals: Vec<Box<dyn Animal>> = vec![
    Box::new(Dog),
    Box::new(Cat),
];

Arc

Arc Atomic Reference Counting 是Rust中用于多线程共享所有权的智能指针,它在Rc的基础上通过原子操作实现线程安全,是构建高并发程序的核心工具之一

struct ArcInner<T> {
    data: T,
    strong: AtomicUsize, // 原子强引用计数
    weak: AtomicUsize,   // 原子弱引用计数
}

特性:

  • 线程安全
  • 共享所有权
  • 不可变借用
  • 性能开销:比普通操作略慢

方法

use std::sync::Arc;
let data = Arc::new(42);
let data_clone = Arc::clone(&data);
println!("Count: {}", Arc::strong_count(&data));
let treahe_data = Arc::clone(&data);
let handle = std::thread::spawn(move||{println!("thread value: {}", *thread_data);});
handle.join().unwrap();

共享可变数据

结合Mutex

let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
    let counter_clone = Arc::clone(&counter);
    handles.push(std::thread::spawn(move || {
        let mut num = counter_clone.lock().unwrap();
        *num += 1;
    
    });
}

RefCell

RefCell是Rust中用于实现内部可变性的智能指针,允许在拥有不可变引用(&T)时修改数据,突破Rust编译期借用检查的限制,将借用规则的验证从编译推迟到运行时

struct RefCell<T> {
    data: UnsafeCell<T>,  // 实际存储数据(允许内部可变性)
    borrows: Cell<BorrowFlag>, // 追踪当前借用状态
}

// BorrowFlag 的简化表示:
// - 正数:活跃的不可变借用数量
// - -1:存在活跃的可变借用
type BorrowFlag = isize;
  • ​​borrow()​​:检查 borrows,若为 -1 则 panic!,否则不可变计数 +1。
    ​​- borrow_mut()​​:检查 borrows,若不为 0 则 panic!,否则标记为 -1。
    ​​- Drop 实现​​:释放 Ref 或 RefMut 时更新计数器。

特性

  • 内部可变性:允许通过不可变引用修改数据
  • 运行时借用检查: 在运行时追踪活跃的借用,违反则触发panic!
  • 单线程专用

方法

  • borrow() -> Ref :获取不可变引用,运行时检查是否存在活跃的可变引用
  • borrow_mut() -> RefMut: 获取可变引用,运行时检查是否存在任何活跃的借用
  • try_borrow()和try_borrow_ut(): 非阻塞尝试获取引用,返回Result而非触发panic!

借用规则(运行时验证)

  • 不可变借用 Ref: 可同时存在多个
  • 可变借用RefMut: 同一时间只能存在一个,且不可与不可变借用共存
// 一个日志系统:对外不可变,但内部可记录日志
struct Logger {
    logs: RefCell<Vec<String>>,
}

impl Logger {
    fn new() -> Self {
        Logger { logs: RefCell::new(Vec::new()) }
    }

    // 即使 &self 是不可变引用,也能修改内部数据
    fn log(&self, message: &str) {
        self.logs.borrow_mut().push(message.to_string());
    }
}

let logger = Logger::new();
logger.log("Event 1"); // 通过不可变引用修改数据

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

// 多个所有者共享可变数据
let shared_data = Rc::new(RefCell::new(42));

let clone1 = Rc::clone(&shared_data);
let clone2 = Rc::clone(&shared_data);

*clone1.borrow_mut() += 1; // 修改数据
*clone2.borrow_mut() *= 2; // 再次修改

println!("Result: {}", shared_data.borrow()); // 输出 86

Cow

Copy on Write,写时克隆, Cow是rust中用于优化数据赋值的只能指针,核心思想是延迟克隆,读时报错借用,修改数据时进行克隆,最小化拷贝操作,尤其适用于处理大体积数据或高频次读取的场景

pub enum Cow<'a, B>
where
    B: 'a + ToOwned + ?Sized,
{
    Borrowed(&'a B),      // 借用数据
    Owned(<B as ToOwned>::Owned), // 拥有克隆后的数据
}

特性

  • 两种状态:Borrowed: 持有不可变引用,无所有权;Owned:持有数据的克隆副本,拥有所有权
  • 零拷贝优化: 默认不克隆数据,修改时克隆
  • 实现Deref

方法

// Cow::to_mut(): 获取可变引用
// 如果当前为Borrowed,触发克隆并转为Owned
// 如果为Owned,直接返回可变引用
let mut cow = Cow::Borrowed("hell");
let s = cow.to_mut(); // &mut String
s.push_str("word!);

// Cow::into_owned():提取所有权数据
// 如果为Owned,直接返回数据
// 如果为Borrowed,触发克隆并返回数据
let cow: Cow<str> = Cow::Borrowed("hello");
let owned = cow.into_owned(); // 克隆 生成String


use std::borrow::Cow;

fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
    for i in 0..input.len() {
        let v = input[i];
        if v < 0 {
            // Clones into a vector if not already owned.
            input.to_mut()[i] = -v;
        }
    }
    input
}

fn owned_mutation() -> Result<(), &'static str> {
        // Of course this is also the case if a mutation does occur. In this
        // case the call to `to_mut()` returns a reference to the same data as
        // before.
        let slice = vec![-1, 0, 1];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }
use std::borrow::Cow;

fn process_input(input: &str) -> Cow<str> {
    if input.len() > 10 { // 假设需要修改长字符串
        Cow::Owned(input.to_uppercase()) // 触发克隆
    } else {
        Cow::Borrowed(input) // 保持借用
    }
}

let short = process_input("hello"); // Cow::Borrowed("hello")
let long = process_input("this is a long string"); // Cow::Owned("THIS IS A LONG STRING")
posted @ 2025-05-13 17:30  店里最会撒谎白玉汤  阅读(43)  评论(0)    收藏  举报