Rust 和 C++ 的記憶體管理機制有所不同,直接影響到參照和指標的使用方式。Rust 強調安全性與所有權,透過編譯時期的借用檢查器來防止懸空指標和資料競爭等問題。C++ 則更為彈性,但也需要開發者自行管理記憶體,容易出現錯誤。Rust 的參照預設唯讀,需要 mut 關鍵字才能修改,而 C++ 則相反。Rust 的 Box 型別提供了一種在堆積上分配記憶體的方式,並透過智慧型指標管理其生命週期。

Rust 和 C++ 中的參照和指標

Rust 和 C++ 是兩種不同的程式設計語言,它們在參照和指標的使用上有所不同。在 Rust 中,預設情況下參照是唯讀的,而可寫的型別需要特別標記(使用 mut)。相反,C++ 中的預設情況下是可寫的,而唯讀型別需要特別標記(使用 const)。

Rust 中的參照

在 Rust 中,參照是一種指向某個值的別名。參照可以指向堆積疊或堆積上的值。Rust 的參照可以分為兩種:不可變參照(&T)和可變參照(&mut T)。不可變參照只能讀取值,而不能修改它;可變參照則可以讀取和修改值。

Rust 中的指標

Rust 中的指標是一種指向某個值的記憶體位置的指標。指標可以指向堆積疊或堆積上的值。Rust 的指標可以分為兩種:原始指標(*const T)和可變指標(*mut T)。原始指標只能讀取值,而不能修改它;可變指標則可以讀取和修改值。

Box 和堆積疊

Rust 的 Box 型別是一種智慧指標,它可以將值放在堆積上。當你建立一個 Box 時,Rust 會在堆積上分配記憶體,並傳回一個指向該記憶體位置的指標。你可以使用 Box 來建立一個指向堆積上值的參照。

Deref 和 DerefMut

Rust 的 DerefDerefMut 特徵允許你定義如何將某個型別轉換為另一個型別的參照。Deref 特徵用於不可變參照,而 DerefMut 特徵用於可變參照。當你實作了這些特徵時,你可以使用 * 運算子來解參照值。

AsRef 和 AsMut

Rust 的 AsRefAsMut 特徵允許你定義如何將某個型別轉換為另一個型別的參照。AsRef 特徵用於不可變參照,而 AsMut 特徵用於可變參照。與 DerefDerefMut 不同,AsRefAsMut 需要明確呼叫 as_ref()as_mut() 方法來進行轉換。

Fat Pointer Types

Rust 有兩種內建的 fat pointer 型別:slice 和 trait 物件。slice 是一種指向某個連續集合子集的參照,它包含一個指標和一個長度欄位。trait 物件是一種指向某個實作特定 trait 的值的參照,它包含一個指標和一個 vtable。

Slice

slice 是一種指向某個連續集合子集的參照。它包含一個指標和一個長度欄位。slice 可以指向陣列或向量的子集。

Trait 物件

trait 物件是一種指向某個實作特定 trait 的值的參照。它包含一個指標和一個 vtable。trait 物件可以用於多型性和動態分派。

Trait 物件與指標特性

在 Rust 中,Trait 物件是一種特殊的指標型別,代表實作特定 Trait 的物件。它由一個指向實際物件的指標和一個指向該物件的虛擬表(vtable)的指標組成,虛擬表中包含了 Trait 中定義的方法的實作。

例如,定義一個 Calculate Trait,包含 addmul 方法:

trait Calculate {
    fn add(&self, l: u64, r: u64) -> u64;
    fn mul(&self, l: u64, r: u64) -> u64;
}

然後,定義一個 Modulo Struct 並實作 Calculate Trait:

struct Modulo(pub u64);

impl Calculate for Modulo {
    fn add(&self, l: u64, r: u64) -> u64 {
        (l + r) % self.0
    }

    fn mul(&self, l: u64, r: u64) -> u64 {
        (l * r) % self.0
    }
}

現在,可以將 Modulo 例項轉換為 &dyn Calculate Trait 物件:

let mod3 = Modulo(3);
let tobj: &dyn Calculate = &mod3;
let result = tobj.add(2, 2);
assert_eq!(result, 1);

Trait 物件的記憶體佈局如圖 1-6 所示。

更多指標特性

除了 DerefDerefMut 之外,Rust 還提供了其他指標特性,例如 AsRefAsMut。這些特性允許將某些型別轉換為參照。

此外,還有 Pointer 特性,可以將指標值格式化為輸出。這對於低階別除錯很有用。

Borrow 和 BorrowMut 特性

BorrowBorrowMut 特性分別提供了 borrowborrow_mut 方法,允許將某些型別借用為參照。這些特性與 AsRefAsMut 相似,但具有不同的意圖。

例如,HashMap::get 方法使用 Borrow 特性,以便可以使用鍵值查詢條目,而不需要鍵值的所有權。

ToOwned 特性

ToOwned 特性提供了 to_owned 方法,可以將某些型別轉換為其所有權版本。這對於需要處理參照和移動值的情況很有用。

Cow 型別

Cow 型別是一個列舉,可以持有所有權資料或借用資料的參照。它的名稱來自「clone-on-write」,表示只有當資料需要修改時才會建立所有權副本。

智慧指標型別

Rust 標準函式庫提供了多種智慧指標型別,例如 RcRefCell。這些型別提供了不同語義和保證,可以用於控制指標行為。

例如,Rc 型別是一個參照計數指標,可以用於實作分享所有權。然而,它也引入了迴圈參照和記憶體洩漏的風險。

另一方面,RefCell 型別提供了內部可變性,可以允許在只讀參照上修改資料。然而,它也引入了額外的儲存開銷和執行時借用檢查。

智慧型指標與同步技術

在多執行緒環境中,傳統的指標型別可能無法滿足需求,因為它們不具備內建的同步機制。為瞭解決這個問題,Rust 提供了幾種智慧型指標型別,包括 RcRefCellArcMutexRwLock

Rc 和 RefCell

Rc 是一個參照計數的智慧型指標,允許多個所有者分享同一份資料。然而,Rc 本身不支援可變借用,因此需要結合 RefCell 來實作內部可變性。RefCell 提供了在執行時檢查借用規則的機制,允許在安全的情況下進行可變借用。

Arc 和 Mutex

ArcRc 的執行緒安全版本,使用原子計數器確保參照計數的正確性。在多執行緒環境中,Arc 可以安全地分享資料。然而,Arc 本身不支援可變借用,因此需要結合 Mutex 來實作同步存取。Mutex 保證只有一個執行緒可以存取資料,從而確保資料的一致性。

RwLock

RwLock 是一個讀寫鎖,允許多個讀者並發存取資料,而寫者則需要獨佔存取權。這使得 RwLock 特別適合於讀多寫少的場景。

從底層實作到高階應用的全面檢視顯示,Rust 的所有權和借用系統與 C++ 的指標管理機制相比,提供了更強的記憶體安全性和編譯時錯誤檢測能力。Rust 的 BoxRcArc 等智慧型指標以及 RefCellMutexRwLock 等同步機制,在不同場景下提供了更精細的記憶體管理控制,有效避免了懸空指標、資料競爭等常見的 C++ 程式錯誤。然而,Rust 的所有權和借用規則也增加了程式碼的複雜度,需要開發者深入理解其運作機制才能有效運用。

Rust 的多種指標特性,如 DerefDerefMutAsRefAsMutBorrowBorrowMut,允許開發者更靈活地操作參照和指標,實作更精細的型別轉換和借用控制。Fat Pointer 型別,如 Slice 和 Trait 物件,則為處理動態大小資料和多型提供了高效的解決方案。然而,這些特性的多樣性也增加了學習曲線,需要開發者投入更多時間才能掌握。

展望未來,Rust 的所有權和借用系統將持續影響程式語言的設計方向,推動更安全的記憶體管理模式的發展。隨著 Rust 生態系統的日漸成熟,預計會有更多工具和資源出現,降低開發者的學習門檻,並促進 Rust 在更多領域的應用。玄貓認為,Rust 的嚴謹性和安全性使其在系統程式設計、嵌入式開發等對效能和可靠性要求極高的領域具有顯著優勢,值得長期關注和投入。