深入解析Rust中的智能指針
Rust 中,智能指針是管理堆內(nèi)存的核心工具,它們通過封裝指針并添加額外功能(如所有權(quán)管理、引用計數(shù)等)來提供更安全的內(nèi)存管理。
智能指針
智能指針本質(zhì)是 “擁有數(shù)據(jù)所有權(quán)的結(jié)構(gòu)體”,通過實現(xiàn)以下兩個關(guān)鍵 trait 模擬指針行為:
Dereftrait:允許智能指針像普通引用一樣被解引用(如*ptr),簡化使用。Droptrait:定義智能指針離開作用域時的 “清理邏輯”(如釋放堆內(nèi)存、減少引用計數(shù)),實現(xiàn)自動內(nèi)存管理。
常見的智能指針:
| 智能指針 | 特點 | 所有權(quán)規(guī)則 |
|---|---|---|
Box<T> | 將數(shù)據(jù)分配在堆上 | 獨占所有權(quán)(不可復(fù)制) |
Rc<T> | 引用計數(shù)共享 | 多個所有者共享數(shù)據(jù),只讀,單線程共享 |
Arc<T> | 原子引用計數(shù) | 多線程共享、線程安全 |
RefCell<T> | 內(nèi)部可變性 | 運行時借用檢查,單線程中可變共享 |
Mutex<T> | 互斥鎖封裝 | 多線程中安全的可變共享 |
RwLock<T> | 讀寫鎖封裝 | 多讀單寫共享 |
Box
Box<T>(“盒子”)是最基礎(chǔ)的智能指針,用于將數(shù)據(jù)存儲在堆上,而Box自身(指針)存儲在棧上:
- 獨占所有權(quán):一個
Box擁有堆數(shù)據(jù)的唯一所有權(quán),轉(zhuǎn)移Box會轉(zhuǎn)移所有權(quán)。 - 自動釋放:當(dāng)
Box離開作用域時,會調(diào)用Drop釋放堆上的數(shù)據(jù)。
使用場景:
- 編譯時大小不確定的類型(如遞歸類型)
- 轉(zhuǎn)移大量數(shù)據(jù)時避免棧復(fù)制(直接轉(zhuǎn)移
Box指針,而非堆數(shù)據(jù)) - 實現(xiàn) trait 對象(
dyn Trait),如集合中存儲多種類型數(shù)據(jù)時
#[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) | 創(chuàng)建一個堆上分配的對象 |
*box | 解引用,訪問內(nèi)部值 |
Box::leak(box) | 將 Box 轉(zhuǎn)為 'static 引用(泄露內(nèi)存) |
Box::into_raw(box) | 轉(zhuǎn)為裸指針(不再自動釋放) |
Box::from_raw(ptr) | 從裸指針恢復(fù)(恢復(fù)自動釋放) |
作為trait對象:
// 定義trait
trait Shape {
fn area(&self) -> f64;
}
// 實現(xiàn)trait的結(jié)構(gòu)體
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實現(xiàn)
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
// 動態(tài)調(diào)用area方法(運行時確定具體類型)
for shape in shapes {
println!("面積:{:.2}", shape.area());
// 輸出:3.14(圓)、4.00(正方形)
}
}Rc
Rc<T>(Reference Counted,引用計數(shù))用于單線程中多個所有者共享同一份堆數(shù)據(jù)。它會在堆上維護一個 “引用計數(shù)”,當(dāng)計數(shù)歸零時自動釋放數(shù)據(jù)。
- 共享所有權(quán):通過
Rc::clone(&rc)創(chuàng)建新引用,引用計數(shù) +1;每個引用離開作用域時計數(shù) -1。 - 單線程限制:
Rc<T>的引用計數(shù)操作不是原子的,線程不安全,不能用于多線程。 - 只讀訪問:
Rc<T>只能提供不可變引用(避免數(shù)據(jù)競爭)。
常用方法:
| 方法 | 說明 |
|---|---|
Rc::new(value) | 創(chuàng)建一個引用計數(shù)智能指針 |
Rc::clone(&rc) | 增加引用計數(shù)(輕量) |
Rc::strong_count(&rc) | 獲取當(dāng)前強引用計數(shù) |
Rc::weak_count(&rc) | 獲取當(dāng)前弱引用計數(shù) |
Rc::downgrade(&rc) | 獲取弱引用(不增加強計數(shù)) |
查看引用計數(shù):
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 離開作用域后才釋放堆內(nèi)存Arc
Arc<T>(Atomic Rc)是Rc<T>的線程安全版本,其引用計數(shù)操作通過原子指令實現(xiàn),可用于多線程環(huán)境。
- 跨線程共享:允許在多個線程中共享數(shù)據(jù)(需配合
Send/Synctrait)。 - 原子操作:計數(shù)增減是原子的,避免多線程競爭問題(但性能略低于
Rc<T>)。
常用方法:
| 方法 | 說明 |
|---|---|
Arc::new(value) | 創(chuàng)建智能指針 |
Arc::clone(&arc) | 增加引用計數(shù)(原子操作) |
Arc::strong_count(&arc) | 當(dāng)前強引用計數(shù) |
Arc::downgrade(&arc) | 獲取弱引用 |
多線程引用計數(shù):
use std::sync::Arc;
use std::thread;
pub fn arc_test() {
let data = Arc::new(100); // 堆上的數(shù)據(jù),原子引用計數(shù)=1
let mut handles = vec![];
// 創(chuàng)建3個線程共享data
for i in 0..3 {
let d = Arc::clone(&data); // 計數(shù)+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)); // 原子引用計數(shù)=1
}RefCell
RefCell<T>用于編譯期不滿足借用規(guī)則,但運行時可安全修改數(shù)據(jù)的場景。它實現(xiàn)了 “內(nèi)部可變性”(Interior Mutability):允許通過不可變引用修改數(shù)據(jù),借用規(guī)則的檢查推遲到運行時(違反時觸發(fā)panic)。
- 運行時檢查:通過
borrow()(不可變借用)和borrow_mut()(可變借用)獲取內(nèi)部數(shù)據(jù)的引用,運行時確保 “同一時間最多一個可變引用,或多個不可變引用”。 - 單線程限制:
RefCell<T>非線程安全,不能跨線程使用。
常用方法:
| 方法 | 說明 |
|---|---|
RefCell::new(value) | 創(chuàng)建一個內(nèi)部可變?nèi)萜?/td> |
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修改數(shù)據(jù)
*b.borrow_mut() += 5; // b修改數(shù)據(jù)
println!("{}", shared_data.borrow()); // 輸出15
}Mutex
多線程并發(fā)編程的核心同步原語之一,用于在多個線程之間安全地共享和修改數(shù)據(jù);Mutex<T> 本身不提供共享所有權(quán),一般需要將其包裹在 Arc<T>中,在在多個線程間共享:
- 多線程可變共享;
- 確保同一時間只有一個線程訪問。
常用方法:
| 方法 | 說明 |
|---|---|
Mutex::new(value) | 創(chuàng)建互斥鎖 |
lock() | 獲取鎖(阻塞) |
try_lock() | 嘗試獲取鎖(立即返回 Result) |
into_inner() | 取出內(nèi)部值(消耗鎖) |
與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
允許多個線程同時讀取共享數(shù)據(jù),但寫入時必須獨占訪問,從而在保證線程安全的同時提升并發(fā)性能。
- 高并發(fā)讀場景;
- 多線程下支持多個讀取者或一個寫入者。
- 多個讀鎖可同時存在;
- 寫鎖獨占;
- 若寫鎖被持有,讀鎖將阻塞。
常用方法:
| 方法 | 說明 |
|---|---|
RwLock::new(value) | 創(chuàng)建讀寫鎖 |
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>的弱引用,不增加強引用計數(shù),用于打破循環(huán)引用,避免內(nèi)存泄漏。
常用方法:
| 方法 | 說明 |
|---|---|
Weak::new() | 創(chuàng)建空的弱引用 |
Rc::downgrade(&rc) | 從Rc<T>創(chuàng)建Weak<T>(弱引用) |
weak.upgrade() | 將Weak<T>轉(zhuǎn)為Option<Rc<T>>(強引用),若數(shù)據(jù)已釋放則返回None |
Weak::strong_count(&weak) | 獲取關(guān)聯(lián)Rc<T>的強引用計數(shù) |
Weak::weak_count(&weak) | 獲取弱引用計數(shù) |
Cow寫時Copy
Clone-on-Write(寫時克隆)是一個枚舉類型,用于在“可能需要修改借用數(shù)據(jù)”時,避免不必要的復(fù)制。
- 如果只讀,就直接借用(零拷貝)。
- 如果要改,就克隆一份(擁有所有權(quán)后修改)。
定義:Cow要么借用&T,要么擁有T(T 必須實現(xiàn) ToOwned)。
enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
常用方法:
| 方法 | 說明 |
|---|---|
Cow::Borrowed(&T) | 從借用創(chuàng)建 |
Cow::Owned(T) | 從擁有值創(chuàng)建 |
.to_mut() | 若為借用則克隆,返回可變引用 |
.into_owned() | 獲取擁有所有權(quán)的值(可能克?。?/td> |
.is_borrowed()/ .is_owned() | 判斷當(dāng)前狀態(tài) |
.as_ref() | 獲取不可變引用 |
寫時復(fù)制示例:
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")
// 調(diào)用 to_mut() 會檢測當(dāng)前是否為借用
let data = cow.to_mut(); // 克隆一份(從 Borrowed -> Owned)
data.push_str(" modified");
println!("After: {:?}", cow); // Owned("immutable data modified")
}Pin
Rust 的所有權(quán)系統(tǒng)保證了內(nèi)存安全,但默認允許將值從一個內(nèi)存位置移動到另一個位置(例如賦值或函數(shù)返回時)。 Pin用于防止內(nèi)存中對象被移動(pinned in place); 即可以“釘住”一個值,使它在被銷毀前一直位于同一內(nèi)存地址 。
| 方法 / 操作 | 說明 |
|---|---|
Pin::new(pointer) | 安全創(chuàng)建Pin<P>,要求P指向的類型T實現(xiàn)Unpin(可安全移動)。 |
Pin::new_unchecked(pointer) | 不安全創(chuàng)建Pin<P>,不要求T: Unpin,但需開發(fā)者保證數(shù)據(jù)不會被移動(否則會導(dǎo)致未定義行為)。 |
pin.as_ref() | 獲取Pin<&T>(不可變引用的 Pin)。 |
pin.as_mut() | 獲取Pin<&mut T>(可變引用的 Pin)。 |
Pin::into_inner(pin) | 消費Pin<P>,返回內(nèi)部的指針P(僅當(dāng)T: Unpin時安全,否則可能導(dǎo)致移動)。 |
pin.get_mut() | 獲取內(nèi)部指針的&mut P(僅當(dāng)T: Unpin時允許,否則編譯錯誤)。 |
pin.get_ref() | 獲取 &T |
unsafe fn get_unchecked_mut() | 獲取 &mut T,不檢查移動安全 |
Pin與Unpin
Pin<T>的出現(xiàn)就是為了強制數(shù)據(jù)在內(nèi)存中 “固定”,確保其地址不會改變
Pin<P>:一個包裝器類型,其中P是一個指針類型(如Box<T>、&mut T、Arc<T>等)。Pin<P>保證:被P指向的數(shù)據(jù)不會被移動(除非數(shù)據(jù)實現(xiàn)了Unpin)。Unpintrait:標記 trait,表明 “該類型的數(shù)據(jù)可以安全移動,即使被Pin包裝”。大多數(shù)類型(如i32、String、Vec<T>等)默認自動實現(xiàn)Unpin,無需手動處理;而需要固定的類型(如自引用類型)則不實現(xiàn)Unpin,必須通過Pin確保不被移動。- 對于
T: !Unpin(不實現(xiàn)Unpin):Pin<P>會嚴格限制操作,不允許通過Pin獲取能導(dǎo)致數(shù)據(jù)移動的接口(如&mut T),確保數(shù)據(jù)地址不變。
- 對于
Rust 中大多數(shù)類型默認都實現(xiàn)了 Unpin,這意味著它們可以被安全地移動(move)。而PhantomPinned 是標準庫 std::marker 模塊提供的一個標記類型(marker type;本身是一個零大小的結(jié)構(gòu)體(ZST),沒有字段,也不占用內(nèi)存),其主要作用是阻止包含它的類型自動實現(xiàn) Unpin trait。
自引用類型與Pin
自引用類型(如包含自身引用的結(jié)構(gòu)體)是Pin的典型應(yīng)用場景。沒有Pin時,移動會導(dǎo)致懸垂引用;用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();
}到此這篇關(guān)于Rust中的智能指針的文章就介紹到這了,更多相關(guān)Rust智能指針內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
rust解決嵌套——Option類型的map和and_then方法的使用
這篇文章主要介紹了rust解決嵌套——Option類型的map和and_then方法,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02
Windows系統(tǒng)下安裝Rust環(huán)境超詳細教程
這篇文章主要介紹了如何在Windows系統(tǒng)上安裝mingw64和Rust,mingw64是一個輕便的C語言編譯環(huán)境,可以替代Rust默認使用的Visual?Studio,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2025-02-02
使用環(huán)境變量實現(xiàn)Rust程序中的不區(qū)分大小寫搜索方式
本文介紹了如何在Rust中實現(xiàn)不區(qū)分大小寫的搜索功能,并通過測試驅(qū)動開發(fā)(TDD)方法逐步實現(xiàn)該功能,通過修改運行函數(shù)和獲取環(huán)境變量,程序可以根據(jù)環(huán)境變量控制搜索模式2025-02-02
rust中間件actix_web在項目中的使用實戰(zhàn)
這篇文章主要介紹了rust中間件在項目中的使用實戰(zhàn),包括自定義中間件,日志中間件,Default?headers,用戶會話,錯誤處理的用法實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01

