Rust 的 Arc 智慧指標利用原子參照計數,允許多執行緒安全地分享資料。Clone 特性複製 Arc 時,原子地增加參照計數,確保所有執行緒都能正確追蹤資料的使用情況。Drop 特性則在 Arc 離開作用域時減少參照計數,當計數歸零,內部資料才會被釋放,避免記憶體洩漏。RawVec 型別則提供底層的動態陣列操作,需要自行管理記憶體分配和釋放。其 new 方法建立一個空的 RawVecgrow 方法則負責擴充,drop 特性則確保在 RawVec 釋放時,其佔用的記憶體會被正確歸還給系統,避免記憶體洩漏。實作這些特性的關鍵在於理解 Rust 的所有權系統和生命週期,並使用不安全的程式碼區塊來直接操作記憶體。

實作 Arc 的 Clone 和 Drop 特性

在 Rust 中,Arc (Atomic Reference Counting) 是一個提供執行緒安全的參照計數智慧指標的型別。它允許多個執行緒安全地分享同一份資料。為了實作 ArcCloneDrop 特性,我們需要定義這兩個 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,我們需要定義兩個重要的方法:newgrownew 方法用於建立一個空的 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 有兩個欄位:ptrcap,分別代表指標和容量。

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 來封裝 RawVecVec 有兩個欄位:buflen,分別代表緩衝區和長度。

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;
    }
}

這個實作提供了基本的向量功能,包括 newlenpush 方法。注意,這個實作並不完整,缺少一些重要的功能,如 popinsertremove 等。

實作自定義向量(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)是兩種不同的資料結構。向量是一種可變長度的集合,而切片則是一種固定長度的集合。為了實作向量和切片之間的轉換,我們需要實作 DerefDerefMut 特徵。

從底層實作到高階應用的全面檢視顯示,Rust 的 Arc 智慧指標以及 Vec 動態陣列的設計與實作,充分體現了 Rust 所有權系統和生命週期管理的精髓。透過原子參照計數和巧妙的記憶體管理策略,Arc 確保了多執行緒環境下的資料安全分享,避免了資料競爭和懸空指標等常見問題。而 Vec 則在兼顧效能的同時,提供了動態調整大小的能力,滿足了各種應用場景的需求。然而,值得注意的是,unsafe 程式碼塊的使用凸顯了底層操作的複雜性和潛在風險,開發者需要對記憶體管理機制有深入的理解才能避免錯誤。展望未來,隨著 Rust 語言的持續發展,預計將出現更多根據 ArcVec 的高階抽象和工具,進一步簡化開發流程並提升程式碼安全性。對於追求極致效能和安全性的系統級程式設計而言,Rust 的這些特性無疑具有極高的價值,值得深入研究和應用。