Rust 的 Arc
智慧指標利用原子參照計數,允許多執行緒安全地分享資料。Clone
特性複製 Arc
時,原子地增加參照計數,確保所有執行緒都能正確追蹤資料的使用情況。Drop
特性則在 Arc
離開作用域時減少參照計數,當計數歸零,內部資料才會被釋放,避免記憶體洩漏。RawVec
型別則提供底層的動態陣列操作,需要自行管理記憶體分配和釋放。其 new
方法建立一個空的 RawVec
,grow
方法則負責擴充,drop
特性則確保在 RawVec
釋放時,其佔用的記憶體會被正確歸還給系統,避免記憶體洩漏。實作這些特性的關鍵在於理解 Rust 的所有權系統和生命週期,並使用不安全的程式碼區塊來直接操作記憶體。
實作 Arc 的 Clone 和 Drop 特性
在 Rust 中,Arc
(Atomic Reference Counting) 是一個提供執行緒安全的參照計數智慧指標的型別。它允許多個執行緒安全地分享同一份資料。為了實作 Arc
的 Clone
和 Drop
特性,我們需要定義這兩個 trait 的實作。
Clone 特性
Clone
特性允許我們建立一個新的 Arc
例項,它指向相同的內部資料。以下是 Clone
的實作:
impl<T> Clone for Arc<T> {
fn clone(&self) -> Arc<T> {
// 更新參照計數
let inner = unsafe { self.ptr.as_ref() };
let old_rc = inner.rc.fetch_add(1, Ordering::Relaxed);
// 檢查是否會溢位
if old_rc >= isize::MAX as usize {
std::process::abort();
}
// 傳回新的 Arc 例項
Arc {
ptr: self.ptr,
phantom: PhantomData,
}
}
}
Drop 特性
Drop
特性定義了當 Arc
例項被丟棄時的行為。以下是 Drop
的實作:
impl<T> Drop for Arc<T> {
fn drop(&mut self) {
// 減少參照計數
let inner = unsafe { self.ptr.as_ref() };
if inner.rc.fetch_sub(1, Ordering::Release) != 1 {
return;
}
// 防止編譯器和 CPU 重新排序記憶體
atomic::fence(Ordering::Acquire);
// 釋放內部指標
unsafe { Box::from_raw(self.ptr.as_ptr()) };
}
}
測試 Arc
現在,我們可以在 main
函式中測試我們的 Arc
型別:
fn main() {
let arc = Arc::new(10);
let cloned_arc = arc.clone();
println!("arc: {}", *arc);
println!("cloned_arc: {}", *cloned_arc);
}
這個程式會建立一個新的 Arc
例項,然後克隆它。最後,它會列印預出兩個 Arc
例項的內容。
Rust 中的智慧指標和動態陣列
在 Rust 中,智慧指標是一種用於管理記憶體的方式。其中,Arc
是一個原子參照計數器,允許多個所有者分享同一份資料。下面是一個使用 Arc
的例子:
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let arc = Arc::new(Mutex::new(32));
let mut threads = vec![];
for _ in 0..10 {
let arc_clone = arc.clone();
let handle = thread::spawn(move || {
let mut num = arc_clone.lock().unwrap();
*num += 1;
println!("Thread {}: {}", thread::current().id(), *num);
});
threads.push(handle);
}
for handle in threads {
handle.join().unwrap();
}
println!("Final value: {}", *arc.lock().unwrap());
}
這個例子中,Arc
用於分享一個 Mutex
,這個 Mutex
裡面存放著一個整數。多個執行緒可以同時存取這個整數,並且對其進行加一操作。
接下來,我們來實作一個動態陣列。動態陣列是一種可以在執行時動態調整大小的陣列。在 Rust 中,我們可以使用 Vec
來實作動態陣列。下面是一個簡單的實作:
use std::ptr::{NonNull, write, read};
use std::mem;
use std::alloc::{alloc, dealloc, Layout};
struct RawVec<T> {
ptr: NonNull<T>,
cap: usize,
len: usize,
_marker: std::marker::PhantomData<T>,
}
impl<T> RawVec<T> {
fn new() -> Self {
RawVec {
ptr: NonNull::dangling(),
cap: 0,
len: 0,
_marker: std::marker::PhantomData,
}
}
fn push(&mut self, value: T) {
if self.len == self.cap {
self.cap *= 2;
let layout = Layout::array::<T>(self.cap).unwrap();
let new_ptr = unsafe { alloc(layout) } as *mut T;
for i in 0..self.len {
unsafe { write(new_ptr.add(i), read(self.ptr.as_ptr().add(i))) };
}
unsafe { dealloc(self.ptr.as_ptr(), Layout::array::<T>(self.len).unwrap()) };
self.ptr = NonNull::new(new_ptr).unwrap();
}
unsafe { write(self.ptr.as_ptr().add(self.len), value) };
self.len += 1;
}
fn get(&self, index: usize) -> Option<&T> {
if index < self.len {
Some(unsafe { &*self.ptr.as_ptr().add(index) })
} else {
None
}
}
}
fn main() {
let mut vec = RawVec::new();
vec.push(1);
vec.push(2);
vec.push(3);
println!("{:?}", vec.get(0)); // Some(1)
println!("{:?}", vec.get(1)); // Some(2)
println!("{:?}", vec.get(2)); // Some(3)
println!("{:?}", vec.get(3)); // None
}
這個例子中,我們實作了一個簡單的動態陣列 RawVec
,它可以在執行時動態調整大小。它使用 NonNull
來存放指標,cap
來存放容量,len
來存放當前長度。它提供了 push
方法來新增元素,get
方法來存取元素。
RawVec 實作:新建和成長
為了實作 RawVec
,我們需要定義兩個重要的方法:new
和 grow
。new
方法用於建立一個空的 RawVec
,而 grow
方法則用於在向量需要擴充時重新分配記憶體。
新建方法
impl<T> RawVec<T> {
fn new() -> Self {
assert!(mem::size_of::<T>() != 0, "TODO: implement ZST support");
RawVec {
ptr: NonNull::dangling(),
cap: 0,
_marker: PhantomData,
}
}
}
這個 new
方法首先檢查型別 T
的大小是否為 0,如果是,則觸發一個斷言錯誤,因為我們目前不支援零大小型別(ZST)。然後,它建立了一個新的 RawVec
例項,初始化指標為懸空(dangling),容量為 0,並使用 PhantomData
標記型別 T
。
成長方法
fn grow(&mut self) {
let (new_cap, new_layout) = if self.cap == 0 {
(1, Layout::array::<T>(1).unwrap())
} else {
let new_cap = 2 * self.cap;
let new_layout = Layout::array::<T>(new_cap).unwrap();
(new_cap, new_layout)
};
assert!(new_layout.size() <= isize::MAX as usize, "Allocation too large");
let new_ptr = if self.cap == 0 {
unsafe { alloc::alloc(new_layout) }
} else {
let old_layout = Layout::array::<T>(self.cap).unwrap();
let old_ptr = self.ptr.as_ptr() as *mut u8;
unsafe { alloc::realloc(old_ptr, old_layout, new_layout.size()) }
};
self.ptr = match NonNull::new(new_ptr as *mut T) {
Some(p) => p,
None => alloc::handle_alloc_error(new_layout),
};
self.cap = new_cap;
}
這個 grow
方法首先計算新的容量和佈局。如果當前的容量是 0,新的容量設定為 1,否則將容量加倍。然後,它檢查新的佈局大小是否超過 isize::MAX
,如果是,則觸發一個斷言錯誤。
接下來,它根據當前的容量是否為 0 分配或重新分配記憶體。如果容量為 0,則使用 alloc::alloc
分配新記憶體;否則,使用 alloc::realloc
重新分配記憶體。
最後,它更新 RawVec
的指標和容量。如果分配或重新分配記憶體失敗,則呼叫 alloc::handle_alloc_error
處理錯誤。
Drop 實作
impl<T> Drop for RawVec<T> {
fn drop(&mut self) {
if self.cap != 0 {
let layout = Layout::array::<T>(self.cap).unwrap();
let ptr = self.ptr.as_ptr() as *mut u8;
unsafe { alloc::dealloc(ptr, layout) }
}
}
}
這個 Drop
實作檢查 RawVec
的容量是否不為 0。如果不是,則計算佈局並取得指標,然後使用 alloc::dealloc
釋放記憶體。
這樣,我們就完成了 RawVec
的基本實作,包括新建和成長方法,以及 Drop 實作以確保記憶體的正確釋放。
實作 Rust 中的向量(Vector)資料結構
在 Rust 中,向量(Vector)是一種動態陣列,可以在執行時動態增加或減少大小。以下是實作向量的步驟。
RawVec 的實作
首先,我們需要實作 RawVec
來管理記憶體。RawVec
有兩個欄位:ptr
和 cap
,分別代表指標和容量。
pub struct RawVec<T> {
ptr: *mut T,
cap: usize,
}
RawVec 的方法
RawVec
需要實作 new
方法來初始化一個新的 RawVec
,以及 dealloc
方法來釋放記憶體。
impl<T> RawVec<T> {
pub fn new() -> Self {
RawVec { ptr: std::ptr::null_mut(), cap: 0 }
}
pub fn dealloc(&mut self) {
if self.cap != 0 {
let layout = std::alloc::Layout::array::<T>(self.cap).unwrap();
unsafe {
std::alloc::dealloc(self.ptr as *mut u8, layout);
}
}
}
}
Vec 的實作
現在,我們可以實作 Vec
來封裝 RawVec
。Vec
有兩個欄位:buf
和 len
,分別代表緩衝區和長度。
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,
}
Vec 的方法
Vec
需要實作 new
方法來初始化一個新的 Vec
,以及 len
方法來取得長度。
impl<T> Vec<T> {
pub fn new() -> Self {
Vec { buf: RawVec::new(), len: 0 }
}
pub fn len(&self) -> usize {
self.len
}
}
push 方法
push
方法會將元素新增到向量的末尾。如果向量已滿,則需要增加容量。
pub fn push(&mut self, elem: T) {
if self.len == self.buf.cap {
self.buf.grow();
}
unsafe {
std::ptr::write(self.buf.ptr.offset(self.len as isize), elem);
}
self.len += 1;
}
grow 方法
grow
方法會增加向量的容量。
impl<T> RawVec<T> {
pub fn grow(&mut self) {
let new_cap = if self.cap == 0 { 1 } else { self.cap * 2 };
let new_layout = std::alloc::Layout::array::<T>(new_cap).unwrap();
let new_ptr = unsafe { std::alloc::alloc(new_layout) as *mut T };
unsafe {
std::ptr::copy(self.ptr, new_ptr, self.len);
std::alloc::dealloc(self.ptr as *mut u8, std::alloc::Layout::array::<T>(self.cap).unwrap());
}
self.ptr = new_ptr;
self.cap = new_cap;
}
}
完整實作
以下是完整的 Vec
實作:
pub struct RawVec<T> {
ptr: *mut T,
cap: usize,
}
impl<T> RawVec<T> {
pub fn new() -> Self {
RawVec { ptr: std::ptr::null_mut(), cap: 0 }
}
pub fn dealloc(&mut self) {
if self.cap != 0 {
let layout = std::alloc::Layout::array::<T>(self.cap).unwrap();
unsafe {
std::alloc::dealloc(self.ptr as *mut u8, layout);
}
}
}
pub fn grow(&mut self) {
let new_cap = if self.cap == 0 { 1 } else { self.cap * 2 };
let new_layout = std::alloc::Layout::array::<T>(new_cap).unwrap();
let new_ptr = unsafe { std::alloc::alloc(new_layout) as *mut T };
unsafe {
std::ptr::copy(self.ptr, new_ptr, self.len);
std::alloc::dealloc(self.ptr as *mut u8, std::alloc::Layout::array::<T>(self.cap).unwrap());
}
self.ptr = new_ptr;
self.cap = new_cap;
}
}
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,
}
impl<T> Vec<T> {
pub fn new() -> Self {
Vec { buf: RawVec::new(), len: 0 }
}
pub fn len(&self) -> usize {
self.len
}
pub fn push(&mut self, elem: T) {
if self.len == self.buf.cap {
self.buf.grow();
}
unsafe {
std::ptr::write(self.buf.ptr.offset(self.len as isize), elem);
}
self.len += 1;
}
}
這個實作提供了基本的向量功能,包括 new
、len
和 push
方法。注意,這個實作並不完整,缺少一些重要的功能,如 pop
、insert
和 remove
等。
實作自定義向量(Vector)類別
在這個章節中,我們將實作一個自定義的向量(Vector)類別,包括其基本操作如新增元素、刪除元素和插入元素。
新增元素
新增元素的過程涉及到檢查向量是否已滿,如果滿了就需要擴大向量的容量。然後,使用不安全的塊(unsafe block)來直接寫入元素。
pub fn push(&mut self, elem: T) {
if self.len == self.cap() {
self.buf.grow();
}
unsafe {
std::ptr::write(self.ptr().add(self.len), elem);
}
self.len += 1;
}
刪除元素
刪除元素的過程需要考慮兩種情況:如果向量是空的,則傳回 None
;如果不是空的,則減少長度並傳回被刪除的元素。
pub fn pop(&mut self) -> Option<T> {
if self.len == 0 {
None
} else {
self.len -= 1;
unsafe {
Some(std::ptr::read(self.ptr().add(self.len)))
}
}
}
插入元素
插入元素的過程需要先檢查索引是否在合法範圍內,如果不在則報錯。然後,如果向量已滿,需要擴大容量。接著,將插入點後面的所有元素向後移動一位,然後插入新元素。
pub fn insert(&mut self, index: usize, elem: T) {
assert!(index <= self.len, "index is out of bounds");
if self.cap() == self.len {
self.buf.grow();
}
// 將插入點後面的所有元素向後移動一位
for i in (index..self.len).rev() {
unsafe {
std::ptr::write(self.ptr().add(i + 1), std::ptr::read(self.ptr().add(i)));
}
}
// 插入新元素
unsafe {
std::ptr::write(self.ptr().add(index), elem);
}
self.len += 1;
}
圖表翻譯:
graph LR A[新增元素] -->|檢查容量|> B[擴大容量] B --> C[寫入元素] C --> D[增加長度] E[刪除元素] -->|檢查長度|> F[傳回None] F --> G[減少長度] G --> H[傳回元素] I[插入元素] -->|檢查索引|> J[擴大容量] J --> K[移動元素] K --> L[插入新元素] L --> M[增加長度]
這個圖表描述了新增、刪除和插入元素的過程,包括檢查容量、擴大容量、寫入元素、減少長度、傳回元素、檢查索引、移動元素和插入新元素等步驟。
內容解密:
上述程式碼實作了基本的向量操作,包括新增、刪除和插入元素。新增元素的過程需要檢查容量,如果滿了就需要擴大容量。刪除元素的過程需要檢查長度,如果是空的就傳回 None
。插入元素的過程需要檢查索引,如果不在合法範圍內就報錯。然後,需要將插入點後面的所有元素向後移動一位,然後插入新元素。這些操作都是使用不安全的塊(unsafe block)來直接操作記憶體。
動態陣列(Vector)實作
動態陣列的基本概念
動態陣列是一種可以在執行時動態調整大小的陣列。它可以增加或減少元素,從而滿足程式執行中的需求。
動態陣列的實作
以下是動態陣列的實作:
pub struct Vec<T> {
ptr: *mut T,
len: usize,
cap: usize,
}
impl<T> Vec<T> {
pub fn new() -> Self {
Vec {
ptr: std::ptr::null_mut(),
len: 0,
cap: 0,
}
}
pub fn insert(&mut self, index: usize, elem: T) {
// 檢查索引是否超出界限
assert!(index <= self.len, "index out of bounds");
// 如果需要,增加容量
if self.len == self.cap {
self.cap *= 2;
let new_ptr = std::alloc::alloc(self.cap * std::mem::size_of::<T>());
std::ptr::copy(self.ptr, new_ptr, self.len);
self.ptr = new_ptr as *mut T;
}
// 移動元素
unsafe {
std::ptr::copy(self.ptr.add(index), self.ptr.add(index + 1), self.len - index);
}
// 寫入新元素
unsafe {
std::ptr::write(self.ptr.add(index), elem);
}
// 增加長度
self.len += 1;
}
pub fn remove(&mut self, index: usize) -> T {
// 檢查索引是否超出界限
assert!(index < self.len, "index out of bounds");
// 減少長度
self.len -= 1;
// 讀取被移除的元素
let result = unsafe { std::ptr::read(self.ptr.add(index)) };
// 移動元素
unsafe {
std::ptr::copy(self.ptr.add(index + 1), self.ptr.add(index), self.len - index);
}
// 傳回被移除的元素
result
}
}
動態陣列的使用
以下是動態陣列的使用示例:
fn main() {
let mut vec = Vec::new();
vec.insert(0, 1);
vec.insert(1, 2);
vec.insert(2, 3);
println!("Vector: {:?}", vec);
let removed = vec.remove(1);
println!("Removed: {}", removed);
println!("Vector: {:?}", vec);
}
實作向量(Vector)與切片(Slice)之間的轉換
在 Rust 中,向量(Vector)和切片(Slice)是兩種不同的資料結構。向量是一種可變長度的集合,而切片則是一種固定長度的集合。為了實作向量和切片之間的轉換,我們需要實作 Deref
和 DerefMut
特徵。
從底層實作到高階應用的全面檢視顯示,Rust 的 Arc
智慧指標以及 Vec
動態陣列的設計與實作,充分體現了 Rust 所有權系統和生命週期管理的精髓。透過原子參照計數和巧妙的記憶體管理策略,Arc
確保了多執行緒環境下的資料安全分享,避免了資料競爭和懸空指標等常見問題。而 Vec
則在兼顧效能的同時,提供了動態調整大小的能力,滿足了各種應用場景的需求。然而,值得注意的是,unsafe
程式碼塊的使用凸顯了底層操作的複雜性和潛在風險,開發者需要對記憶體管理機制有深入的理解才能避免錯誤。展望未來,隨著 Rust 語言的持續發展,預計將出現更多根據 Arc
和 Vec
的高階抽象和工具,進一步簡化開發流程並提升程式碼安全性。對於追求極致效能和安全性的系統級程式設計而言,Rust 的這些特性無疑具有極高的價值,值得深入研究和應用。