guest@blog.cmj.tw: ~/posts $

Rust 103


learn more rust-lang

繼續紀錄學習 Rust 的一些經驗

String vs str

在使用字串或者動態資料的時候,會需要思考要使用 String 還是 str。其中的差別主要在於

  1. 可否變動與 2) 存放的空間。根據 文件 描述:String 一定是一個合法的 UTF-8 編碼 (如果要儲存非 UTF-8 就改用 OsString )、永遠使用 heap 來儲存資料。

同理: str 是內部使用型態、跟 String 一樣永遠儲存一個合法的 UTF-8 字串、習慣上會使用 &str 來借用 (borrowed),使用 &'static str 就表示字串會直接存在執行檔內。

Trait

另一個有趣的設計是 rust 的 Trait 。在 rust 設計中 trait 定義了一個介面 (類似其他語言的 interface)、定義各種函數介面。

使用上,可以視為是函數的參數之一 pub fn notify(item: &impl Summary) { ... }、也可以寫成 pub fn notify<T: Summary>(item: &T) { ... } 用作型態限制。而型態限制也可寫為 pub fn notify<T>(item: &T) where T: Summary { ... }where 將型態限制放置函數後, 讓程式碼更加簡潔與減少雜訊。

同樣可以將 trait 當作是函數的回傳值。例如 fn returns_summarizable() -> impl Summary { ... } 回傳一個 trait 實作,但這樣的設計卻不能在一個函數,回傳兩種不同實作的 trait。

Dynamically Sized and Sized

在 rust 編譯中,需要知道變數的詳細細節、像是記憶體的使用量等。例如以下是一個有問題的程式碼: x: str = "abc",在 rust 的世界中 x 的大小無法在編譯時間確定,因此才需要改用 &str 定義 x 的型態。因此針對 trait 而言,則需要使用 &dyn TraitBox<dyn Trait> 或者 Rc<dyn Trait> 定義,就可以在編譯時間得到記憶體的使用大小。

因此,當寫成 fn generic<T>(t: T) { ... } 的時候,其實是被視為 fn generic<T: Sized>(t: T){ ... } 。而實作上也可以主動寫成 fn generic<T: ?Sized>(t: T){ ... } 將型態 T 宣告為可能、可能沒有 Sized

Clone vs Copy

在 Rust 中 Clone Copy 都是代表複製的 trait,差別在於:

  • Copy 是隱含 (implicit) 並且做位元操作的複製
  • Clone 是明確

Box vs Rc vs Arc

在某些實作下會需要將物件存放在 Heap 、這時候 Rust 有提供多種選擇。最常見的情況使用 Box::new 將建立的物件放在 heap 下。

如果需要將這個記憶體物件分享給多個以上的擁有者 (ownership)、可以使用 Rc::new Arc::new 。 差別是 Arc 保證 thread-safe、所以可以用在 multi-treading 的情境下。

  • Box 適用在單一 ownershipt 的狀況
  • Rc 適用在多個 ownershipt
  • Arc 同 Rc、但保證 thread-safe
  • Cell 提供內部可變 (interior mutability)、但只提供 Copy
  • RefCell 一樣提供內部可變、但不受限於 Copy
  • Mutex 跟 RwLock 同時提供互斥 (mutual-exclusion) 機制