Rust语言

关注公众号 jb51net

关闭
首页 > 软件编程 > Rust语言 > Rust智能指针

深入解析Rust中的智能指针

作者:alwaysrun

本文介绍了Rust中的智能指针,Box、Rc、Arc、RefCell、Mutex、RwLock对应的功能、场景与常用方法,并提供了组合使用方式,以及Cow、Pin的使用方式,感兴趣的朋友跟随小编一起看看吧

Rust 中,智能指针是管理堆内存的核心工具,它们通过封装指针并添加额外功能(如所有权管理、引用计数等)来提供更安全的内存管理。

智能指针

智能指针本质是 “拥有数据所有权的结构体”,通过实现以下两个关键 trait 模拟指针行为:

常见的智能指针

智能指针特点所有权规则
Box<T>将数据分配在堆上独占所有权(不可复制)
Rc<T>引用计数共享多个所有者共享数据,只读,单线程共享
Arc<T>原子引用计数多线程共享、线程安全
RefCell<T>内部可变性运行时借用检查,单线程中可变共享
Mutex<T>互斥锁封装多线程中安全的可变共享
RwLock<T>读写锁封装多读单写共享

Box

Box<T>(“盒子”)是最基础的智能指针,用于将数据存储在堆上,而Box自身(指针)存储在栈上:

使用场景:

#[derive(Debug)]
enum MyList {
    Cons(i32, Box<MyList>), // 必须为Box,此处为递归,大小不确定
    Nil,
}
use MyList::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Nil))));
println!("Recursive list: {:?}", list);

常用方法

方法说明
Box::new(value)创建一个堆上分配的对象
*box解引用,访问内部值
Box::leak(box)将 Box 转为 'static 引用(泄露内存)
Box::into_raw(box)转为裸指针(不再自动释放)
Box::from_raw(ptr)从裸指针恢复(恢复自动释放)

作为trait对象:

// 定义trait
trait Shape {
    fn area(&self) -> f64;
}
// 实现trait的结构体
struct Circle { radius: f64 }
impl Shape for Circle {
    fn area(&self) -> f64 { std::f64::consts::PI * self.radius.powf(2.0) }
}
struct Square { side: f64 }
impl Shape for Square {
    fn area(&self) -> f64 { self.side.powf(2.0) }
}
fn main() {
    // 用Box<dyn Shape>存储不同类型的Shape实现
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 1.0 }),
        Box::new(Square { side: 2.0 }),
    ];
    // 动态调用area方法(运行时确定具体类型)
    for shape in shapes {
        println!("面积:{:.2}", shape.area()); 
        // 输出:3.14(圆)、4.00(正方形)
    }
}

Rc

Rc<T>(Reference Counted,引用计数)用于单线程中多个所有者共享同一份堆数据。它会在堆上维护一个 “引用计数”,当计数归零时自动释放数据。

常用方法

方法说明
Rc::new(value)创建一个引用计数智能指针
Rc::clone(&rc)增加引用计数(轻量)
Rc::strong_count(&rc)获取当前强引用计数
Rc::weak_count(&rc)获取当前弱引用计数
Rc::downgrade(&rc)获取弱引用(不增加强计数)

查看引用计数:

use std::rc::Rc;
fn main() {
    let a = Rc::new(String::from("hello"));
    let b = Rc::clone(&a);
    let c = Rc::clone(&a);
    println!("count = {}", Rc::strong_count(&a)); // 输出 3
    println!("{}", b);
} // 所有 Rc 离开作用域后才释放堆内存

Arc

Arc<T>(Atomic Rc)是Rc<T>的线程安全版本,其引用计数操作通过原子指令实现,可用于多线程环境。

常用方法

方法说明
Arc::new(value)创建智能指针
Arc::clone(&arc)增加引用计数(原子操作)
Arc::strong_count(&arc)当前强引用计数
Arc::downgrade(&arc)获取弱引用

多线程引用计数:

use std::sync::Arc;
use std::thread;
pub fn arc_test() {
    let data = Arc::new(100); // 堆上的数据,原子引用计数=1
    let mut handles = vec![];
    // 创建3个线程共享data
    for i in 0..3 {
        let d = Arc::clone(&data); // 计数+1(原子操作)
        handles.push(thread::spawn(move || {
            println!("i: {}", d);
        }));
    }
    println!("before ref-count: {:?}", Arc::strong_count(&data));
    for h in handles {
        h.join().unwrap();
    }
    println!("after ref-count: {:?}", Arc::strong_count(&data)); // 原子引用计数=1
}

RefCell

RefCell<T>用于编译期不满足借用规则,但运行时可安全修改数据的场景。它实现了 “内部可变性”(Interior Mutability):允许通过不可变引用修改数据,借用规则的检查推迟到运行时(违反时触发panic)。

常用方法

方法说明
RefCell::new(value)创建一个内部可变容器
borrow()不可变借用(运行时检查)
borrow_mut()可变借用(运行时检查)
.try_borrow()
/ .try_borrow_mut()
尝试借用,返回 Result
避免 panic

在Rc中嵌套使用

use std::rc::Rc;
use std::cell::RefCell;
pub fn refcell_test() {
    let shared_data = Rc::new(RefCell::new(0)); // 堆上的0,可共享且修改
    let a = Rc::clone(&shared_data);
    let b = Rc::clone(&shared_data);
    *a.borrow_mut() += 10; // a修改数据
    *b.borrow_mut() += 5; // b修改数据
    println!("{}", shared_data.borrow()); // 输出15
}

Mutex

多线程并发编程的核心同步原语之一,用于在多个线程之间安全地共享和修改数据;Mutex<T> 本身不提供共享所有权,一般需要将其包裹在 Arc<T>中,在在多个线程间共享:

常用方法

方法说明
Mutex::new(value)创建互斥锁
lock()获取锁(阻塞)
try_lock()尝试获取锁(立即返回 Result
into_inner()取出内部值(消耗锁)

与Arc一起在多线程中使用:

use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];
    for _ in 0..5 {
        let c = Arc::clone(&counter);
        handles.push(thread::spawn(move || {
            let mut num = c.lock().unwrap();
            *num += 1;
        }));
    }
    for h in handles {
        h.join().unwrap();
    }
    println!("Result: {}", *counter.lock().unwrap());
}

RwLock

允许多个线程同时读取共享数据,但写入时必须独占访问,从而在保证线程安全的同时提升并发性能。

常用方法

方法说明
RwLock::new(value)创建读写锁
read()获取只读锁(可同时多个)
write()获取写锁(独占)
try_read()
/ try_write()
尝试非阻塞获取

多读少写场景:

use std::sync::RwLock;
use std::thread;
let data = RwLock::new(0);
// 启动一个写线程
let w_handle = thread::spawn(move || {
    let mut w = data.write().unwrap();
    thread::sleep(std::time::Duration::from_millis(100));
    *w = 42;
});
// 启动多个读线程
let mut r_handles = vec![];
for _ in 0..3 {
    let r_data = data.clone();
    let handle = thread::spawn(move || {
        let r = r_data.read().unwrap(); // 会被写线程阻塞,直到写完成
        println!("Read: {}", *r);
    });
    r_handles.push(handle);
}
w_handle.join().unwrap();
for h in r_handles { h.join().unwrap(); }

Weak

Weak<T>Rc<T>/Arc<T>的弱引用,不增加强引用计数,用于打破循环引用,避免内存泄漏。

常用方法

方法说明
Weak::new()创建空的弱引用
Rc::downgrade(&rc)Rc<T>创建Weak<T>(弱引用)
weak.upgrade()Weak<T>转为Option<Rc<T>>(强引用),若数据已释放则返回None
Weak::strong_count(&weak)获取关联Rc<T>的强引用计数
Weak::weak_count(&weak)获取弱引用计数

Cow写时Copy

Clone-on-Write(写时克隆)是一个枚举类型,用于在“可能需要修改借用数据”时,避免不必要的复制。

定义:Cow要么借用&T,要么拥有T(T 必须实现 ToOwned)。

enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
    Borrowed(&'a B),
    Owned(<B as ToOwned>::Owned),
}

常用方法

方法说明
Cow::Borrowed(&T)从借用创建
Cow::Owned(T)从拥有值创建
.to_mut()若为借用则克隆,返回可变引用
.into_owned()获取拥有所有权的值(可能克隆)
.is_borrowed()
/ .is_owned()
判断当前状态
.as_ref()获取不可变引用

写时复制示例:

use std::borrow::Cow;
fn main() {
    let s = "immutable data".to_string();
    let mut cow = Cow::Borrowed(s.as_str()); // 借用 &str
    println!("Before: {:?}", cow); // Borrowed("immutable data")
    // 调用 to_mut() 会检测当前是否为借用
    let data = cow.to_mut(); // 克隆一份(从 Borrowed -> Owned)
    data.push_str(" modified");
    println!("After: {:?}", cow);  // Owned("immutable data modified")
}

Pin

Rust 的所有权系统保证了内存安全,但默认允许将值从一个内存位置移动到另一个位置(例如赋值或函数返回时)。 Pin用于防止内存中对象被移动(pinned in place); 即可以“钉住”一个值,使它在被销毁前一直位于同一内存地址 。

方法 / 操作说明
Pin::new(pointer)安全创建Pin<P>,要求P指向的类型T实现Unpin(可安全移动)。
Pin::new_unchecked(pointer)不安全创建Pin<P>,不要求T: Unpin,但需开发者保证数据不会被移动(否则会导致未定义行为)。
pin.as_ref()获取Pin<&T>(不可变引用的 Pin)。
pin.as_mut()获取Pin<&mut T>(可变引用的 Pin)。
Pin::into_inner(pin)消费Pin<P>,返回内部的指针P(仅当T: Unpin时安全,否则可能导致移动)。
pin.get_mut()获取内部指针的&mut P(仅当T: Unpin时允许,否则编译错误)。
pin.get_ref()获取 &T
unsafe fn get_unchecked_mut()获取 &mut T,不检查移动安全

Pin与Unpin

Pin<T>的出现就是为了强制数据在内存中 “固定”,确保其地址不会改变

Rust 中大多数类型默认都实现了 Unpin,这意味着它们可以被安全地移动(move)。而PhantomPinned 是标准库 std::marker 模块提供的一个标记类型(marker type;本身是一个零大小的结构体(ZST),没有字段,也不占用内存),其主要作用是阻止包含它的类型自动实现 Unpin trait。

自引用类型与Pin

自引用类型(如包含自身引用的结构体)是Pin的典型应用场景。没有Pin时,移动会导致悬垂引用;用Pin固定后,地址不变,引用安全。

use std::pin::Pin;
struct SelfRef {
    data: String,
    ptr: *const String,
}
impl SelfRef {
    fn new(txt: &str) -> Pin<Box<SelfRef>> {
        let mut boxed = Box::pin(SelfRef {
            data: String::from(txt),
            ptr: std::ptr::null(),
        });
        let ptr = &boxed.data as *const String;
        unsafe {
            let mut_ref = Pin::as_mut(&mut boxed);
            Pin::get_unchecked_mut(mut_ref).ptr = ptr;
        }
        boxed
    }
    fn show(&self) {
        unsafe {
            println!("data = {}, ptr = {}", self.data, &*self.ptr);
        }
    }
}
fn main() {
    let pinned = SelfRef::new("hello");
    pinned.show();
}

到此这篇关于Rust中的智能指针的文章就介绍到这了,更多相关Rust智能指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文