深入解析Rust中的智能指针
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>智能指针</li><ul class="second_class_ul"><li>Box</li><li>Rc</li><li>Arc</li><li>RefCell</li><li>Mutex</li><li>RwLock</li><li>Weak</li></ul><li>Cow写时Copy</li><ul class="second_class_ul"></ul><li>Pin</li><ul class="second_class_ul"><li>Pin与Unpin</li><li>自引用类型与Pin</li></ul></ul></div><p>Rust 中,智能指针是管理堆内存的核心工具,它们通过封装指针并添加额外功能(如所有权管理、引用计数等)来提供更安全的内存管理。</p><p class="maodian"></p><h2>智能指针</h2>
<p>智能指针本质是 “拥有数据所有权的结构体”,通过实现以下两个关键 trait 模拟指针行为:</p>
<ul><li><code>Deref</code> trait:允许智能指针像普通引用一样被解引用(如<code>*ptr</code>),简化使用。</li><li><code>Drop</code> trait:定义智能指针离开作用域时的 “清理逻辑”(如释放堆内存、减少引用计数),实现自动内存管理。</li></ul>
<p><code>常见的智能指针</code>:</p>
<table><thead><tr><th>智能指针</th><th>特点</th><th>所有权规则</th></tr></thead><tbody><tr><td><code>Box<T></code></td><td>将数据分配在堆上</td><td>独占所有权(不可复制)</td></tr><tr><td><code>Rc<T></code></td><td>引用计数共享</td><td>多个所有者共享数据,只读,单线程共享</td></tr><tr><td><code>Arc<T></code></td><td>原子引用计数</td><td>多线程共享、线程安全</td></tr><tr><td><code>RefCell<T></code></td><td>内部可变性</td><td>运行时借用检查,单线程中可变共享</td></tr><tr><td><code>Mutex<T></code></td><td>互斥锁封装</td><td>多线程中安全的可变共享</td></tr><tr><td><code>RwLock<T></code></td><td>读写锁封装</td><td>多读单写共享</td></tr></tbody></table>
<p class="maodian"></p><h3>Box</h3>
<p><code>Box<T></code>(“盒子”)是最基础的智能指针,用于将数据存储在堆上,而<code>Box</code>自身(指针)存储在栈上:</p>
<ul><li>独占所有权:一个<code>Box</code>拥有堆数据的唯一所有权,转移<code>Box</code>会转移所有权。</li><li>自动释放:当<code>Box</code>离开作用域时,会调用<code>Drop</code>释放堆上的数据。</li></ul>
<p>使用场景:</p>
<ul><li>编译时大小不确定的类型(如递归类型)</li><li>转移大量数据时避免栈复制(直接转移<code>Box</code>指针,而非堆数据)</li><li>实现 trait 对象(<code>dyn Trait</code>),如集合中存储多种类型数据时</li></ul>
<div class="jb51code"><pre class="brush:plain;">#
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);</pre></div>
<p>常用方法</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Box::new(value)</code></td><td>创建一个堆上分配的对象</td></tr><tr><td><code>*box</code></td><td>解引用,访问内部值</td></tr><tr><td><code>Box::leak(box)</code></td><td>将 Box 转为 <code>'static</code> 引用(泄露内存)</td></tr><tr><td><code>Box::into_raw(box)</code></td><td>转为裸指针(不再自动释放)</td></tr><tr><td><code>Box::from_raw(ptr)</code></td><td>从裸指针恢复(恢复自动释放)</td></tr></tbody></table>
<p>作为trait对象:</p>
<div class="jb51code"><pre class="brush:plain;">// 定义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(正方形)
}
}</pre></div>
<p class="maodian"></p><h3>Rc</h3>
<p><code>Rc<T></code>(Reference Counted,引用计数)用于单线程中多个所有者共享同一份堆数据。它会在堆上维护一个 “引用计数”,当计数归零时自动释放数据。</p>
<ul><li>共享所有权:通过<code>Rc::clone(&rc)</code>创建新引用,引用计数 +1;每个引用离开作用域时计数 -1。</li><li>单线程限制:<code>Rc<T></code>的引用计数操作不是原子的,线程不安全,不能用于多线程。</li><li>只读访问:<code>Rc<T></code>只能提供不可变引用(避免数据竞争)。</li></ul>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Rc::new(value)</code></td><td>创建一个引用计数智能指针</td></tr><tr><td><code>Rc::clone(&rc)</code></td><td>增加引用计数(轻量)</td></tr><tr><td><code>Rc::strong_count(&rc)</code></td><td>获取当前强引用计数</td></tr><tr><td><code>Rc::weak_count(&rc)</code></td><td>获取当前弱引用计数</td></tr><tr><td><code>Rc::downgrade(&rc)</code></td><td>获取弱引用(不增加强计数)</td></tr></tbody></table>
<p>查看引用计数:</p>
<div class="jb51code"><pre class="brush:plain;">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 离开作用域后才释放堆内存</pre></div>
<p class="maodian"></p><h3>Arc</h3>
<p><code>Arc<T></code>(Atomic Rc)是<code>Rc<T></code>的线程安全版本,其引用计数操作通过原子指令实现,可用于多线程环境。</p>
<ul><li>跨线程共享:允许在多个线程中共享数据(需配合<code>Send</code>/<code>Sync</code> trait)。</li><li>原子操作:计数增减是原子的,避免多线程竞争问题(但性能略低于<code>Rc<T></code>)。</li></ul>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Arc::new(value)</code></td><td>创建智能指针</td></tr><tr><td><code>Arc::clone(&arc)</code></td><td>增加引用计数(原子操作)</td></tr><tr><td><code>Arc::strong_count(&arc)</code></td><td>当前强引用计数</td></tr><tr><td><code>Arc::downgrade(&arc)</code></td><td>获取弱引用</td></tr></tbody></table>
<p>多线程引用计数:</p>
<div class="jb51code"><pre class="brush:plain;">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
}</pre></div>
<p class="maodian"></p><h3>RefCell</h3>
<p><code>RefCell<T></code>用于编译期不满足借用规则,但运行时可安全修改数据的场景。它实现了 “内部可变性”(Interior Mutability):允许通过不可变引用修改数据,借用规则的检查推迟到运行时(违反时触发<code>panic</code>)。</p>
<ul><li>运行时检查:通过<code>borrow()</code>(不可变借用)和<code>borrow_mut()</code>(可变借用)获取内部数据的引用,运行时确保 “同一时间最多一个可变引用,或多个不可变引用”。</li><li>单线程限制:<code>RefCell<T></code>非线程安全,不能跨线程使用。</li></ul>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>RefCell::new(value)</code></td><td>创建一个内部可变容器</td></tr><tr><td><code>borrow()</code></td><td>不可变借用(运行时检查)</td></tr><tr><td><code>borrow_mut()</code></td><td>可变借用(运行时检查)</td></tr><tr><td><code>.try_borrow()</code><br />/ <code>.try_borrow_mut()</code></td><td>尝试借用,返回 <code>Result</code><br />避免 panic</td></tr></tbody></table>
<p>在Rc中嵌套使用</p>
<div class="jb51code"><pre class="brush:plain;">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
}</pre></div>
<p class="maodian"></p><h3>Mutex</h3>
<p>多线程并发编程的核心同步原语之一,用于在多个线程之间安全地共享和修改数据;<code>Mutex<T></code> 本身不提供共享所有权,一般需要将其包裹在 <code>Arc<T></code>中,在在多个线程间共享:</p>
<ul><li>多线程可变共享;</li><li>确保同一时间只有一个线程访问。</li></ul>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Mutex::new(value)</code></td><td>创建互斥锁</td></tr><tr><td><code>lock()</code></td><td>获取锁(阻塞)</td></tr><tr><td><code>try_lock()</code></td><td>尝试获取锁(立即返回 <code>Result</code>)</td></tr><tr><td><code>into_inner()</code></td><td>取出内部值(消耗锁)</td></tr></tbody></table>
<p>与Arc一起在多线程中使用:</p>
<div class="jb51code"><pre class="brush:plain;">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());
}</pre></div>
<p class="maodian"></p><h3>RwLock</h3>
<p>允许多个线程同时读取共享数据,但写入时必须独占访问,从而在保证线程安全的同时提升并发性能。</p>
<ul><li>高并发读场景;</li><li>多线程下支持多个读取者或一个写入者。<ul><li>多个读锁可同时存在;</li><li>写锁独占;</li><li>若写锁被持有,读锁将阻塞。</li></ul></li></ul>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>RwLock::new(value)</code></td><td>创建读写锁</td></tr><tr><td><code>read()</code></td><td>获取只读锁(可同时多个)</td></tr><tr><td><code>write()</code></td><td>获取写锁(独占)</td></tr><tr><td><code>try_read()</code><br />/ <code>try_write()</code></td><td>尝试非阻塞获取</td></tr></tbody></table>
<p>多读少写场景:</p>
<div class="jb51code"><pre class="brush:plain;">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(); }</pre></div>
<p class="maodian"></p><h3>Weak</h3>
<p><code>Weak<T></code>是<code>Rc<T></code>/<code>Arc<T></code>的弱引用,不增加强引用计数,用于打破循环引用,避免内存泄漏。</p>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Weak::new()</code></td><td>创建空的弱引用</td></tr><tr><td><code>Rc::downgrade(&rc)</code></td><td>从<code>Rc<T></code>创建<code>Weak<T></code>(弱引用)</td></tr><tr><td><code>weak.upgrade()</code></td><td>将<code>Weak<T></code>转为<code>Option<Rc<T>></code>(强引用),若数据已释放则返回<code>None</code></td></tr><tr><td><code>Weak::strong_count(&weak)</code></td><td>获取关联<code>Rc<T></code>的强引用计数</td></tr><tr><td><code>Weak::weak_count(&weak)</code></td><td>获取弱引用计数</td></tr></tbody></table>
<p class="maodian"></p><h2>Cow写时Copy</h2>
<p>Clone-on-Write(写时克隆)是一个枚举类型,用于在“可能需要修改借用数据”时,避免不必要的复制。</p>
<ul><li>如果只读,就直接借用(零拷贝)。</li><li>如果要改,就克隆一份(拥有所有权后修改)。</li></ul>
<p><code>定义</code>:Cow要么借用&T,要么拥有T(<code>T</code> 必须实现 <code>ToOwned</code>)。</p>
<div class="jb51code"><pre class="brush:plain;">enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
</pre></div>
<p><code>常用方法</code>:</p>
<table><thead><tr><th>方法</th><th>说明</th></tr></thead><tbody><tr><td><code>Cow::Borrowed(&T)</code></td><td>从借用创建</td></tr><tr><td><code>Cow::Owned(T)</code></td><td>从拥有值创建</td></tr><tr><td><code>.to_mut()</code></td><td>若为借用则克隆,返回可变引用</td></tr><tr><td><code>.into_owned()</code></td><td>获取拥有所有权的值(可能克隆)</td></tr><tr><td><code>.is_borrowed()</code><br />/ <code>.is_owned()</code></td><td>判断当前状态</td></tr><tr><td><code>.as_ref()</code></td><td>获取不可变引用</td></tr></tbody></table>
<p>写时复制示例:</p>
<div class="jb51code"><pre class="brush:plain;">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")
}</pre></div>
<p class="maodian"></p><h2>Pin</h2>
<p>Rust 的所有权系统保证了内存安全,但默认允许将值从一个内存位置移动到另一个位置(例如赋值或函数返回时)。 Pin用于防止内存中对象被移动(pinned in place); 即可以“钉住”一个值,使它在被销毁前一直位于同一内存地址 。</p>
<table><thead><tr><th>方法 / 操作</th><th>说明</th></tr></thead><tbody><tr><td><code>Pin::new(pointer)</code></td><td>安全创建<code>Pin<P></code>,要求<code>P</code>指向的类型<code>T</code>实现<code>Unpin</code>(可安全移动)。</td></tr><tr><td><code>Pin::new_unchecked(pointer)</code></td><td>不安全创建<code>Pin<P></code>,不要求<code>T: Unpin</code>,但需开发者保证数据不会被移动(否则会导致未定义行为)。</td></tr><tr><td><code>pin.as_ref()</code></td><td>获取<code>Pin<&T></code>(不可变引用的 Pin)。</td></tr><tr><td><code>pin.as_mut()</code></td><td>获取<code>Pin<&mut T></code>(可变引用的 Pin)。</td></tr><tr><td><code>Pin::into_inner(pin)</code></td><td>消费<code>Pin<P></code>,返回内部的指针<code>P</code>(仅当<code>T: Unpin</code>时安全,否则可能导致移动)。</td></tr><tr><td><code>pin.get_mut()</code></td><td>获取内部指针的<code>&mut P</code>(仅当<code>T: Unpin</code>时允许,否则编译错误)。</td></tr><tr><td><code>pin.get_ref()</code></td><td>获取 <code>&T</code></td></tr><tr><td><code>unsafe fn get_unchecked_mut()</code></td><td>获取 <code>&mut T</code>,不检查移动安全</td></tr></tbody></table>
<p class="maodian"></p><h3>Pin与Unpin</h3>
<p><code>Pin<T></code>的出现就是为了强制数据在内存中 “固定”,确保其地址不会改变</p>
<ul><li><code>Pin<P></code>:一个包装器类型,其中<code>P</code>是一个指针类型(如<code>Box<T></code>、<code>&mut T</code>、<code>Arc<T></code>等)。<code>Pin<P></code>保证:被<code>P</code>指向的数据不会被移动(除非数据实现了<code>Unpin</code>)。</li><li><code>Unpin</code> trait:标记 trait,表明 “该类型的数据可以安全移动,即使被<code>Pin</code>包装”。大多数类型(如<code>i32</code>、<code>String</code>、<code>Vec<T></code>等)默认自动实现<code>Unpin</code>,无需手动处理;而需要固定的类型(如自引用类型)则不实现<code>Unpin</code>,必须通过<code>Pin</code>确保不被移动。<ul><li>对于 <code>T: !Unpin</code>(不实现<code>Unpin</code>):<code>Pin<P></code>会严格限制操作,不允许通过<code>Pin</code>获取能导致数据移动的接口(如<code>&mut T</code>),确保数据地址不变。</li></ul></li></ul>
<p>Rust 中大多数类型默认都实现了 <code>Unpin</code>,这意味着它们可以被安全地移动(move)。而<code>PhantomPinned</code> 是标准库 <code>std::marker</code> 模块提供的一个标记类型(marker type;本身是一个零大小的结构体(ZST),没有字段,也不占用内存),其主要作用是阻止包含它的类型自动实现 <code>Unpin</code> trait。</p>
<p class="maodian"></p><h3>自引用类型与Pin</h3>
<p>自引用类型(如包含自身引用的结构体)是<code>Pin</code>的典型应用场景。没有<code>Pin</code>时,移动会导致悬垂引用;用<code>Pin</code>固定后,地址不变,引用安全。</p>
<div class="jb51code"><pre class="brush:plain;">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();
}</pre></div>
<p>到此这篇关于Rust中的智能指针的文章就介绍到这了,更多相关Rust智能指针内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Rust 智能指针的使用详解</li><li>rust中智能指针的实现</li><li>Rust之智能指针的用法</li><li>Rust 智能指针实现方法</li><li>rust智能指针的具体使用</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]