Rust 如何幫助你防範 bug


#1

在討論區上看到這篇文章,很簡要的看過前面一點點,還在消化當中。適合給剛剛入 Rust 門的人們看看:https://polyfloyd.net/post/how-rust-helps-you-prevent-bugs/

  • 不使用 NULL 指標,進而避免取用 NULL 記憶體而觸發記憶體錯誤
    這邊 Rust 用了兩種通用的型別來解決一般的需求:

    • Option 型別:區分「有資料 (Some)」和「沒有資料 (None)」
    • Result 型別:「正確 (Ok)」或「錯誤 (Err)」

    當然有些人會認為這樣的設計造成不必要的困擾,但是為了達成程式的完備性,這似乎是必要的?這樣的設計,強迫 programmer 去處理錯誤而無法忽視 (當然你也可以直接 .unwrap() 它),但這樣就糟蹋了型別系統能為自己帶來的完備性。另方面這讓我我想到 OCaml 的某些 API 會刻意分成 exception 組和 non-exception 組,以取得一個 list 的 tail 來說好了,分成 tl() 和 tl_exn(),這兩個 function 的 signature 當然是不一樣的。當你非常確定 tail 運算不會造成錯誤,或者你想在別的地方處理這個問題 (在 exception block 內, duh),就使用 *_exn() 那一組。似乎是更好的設計呢!

    (附上 OCaml 的 API https://ocaml.janestreet.com/ocaml-core/109.22.00/doc/core/List.html)

    ps: 調查了一下,看起來 Rust 並沒有 exception-catch 的設計,但是在 unsafe block 中有做一些 guard 和 cleanup 的保護 (unsure)。

  • 強迫初始化變數 – 出自明顯的原因,防範工程師使用未初始化的變數,因而使用到未定義 (undefined) 的值。關於程式語言中未定義的東西,很麻煩的一點是很多時候它並不會觸發程式錯誤,直到程式上線了之後才出現問題。比如說在 stack frame 裏面,未初始化的 automatic variable 實際上具有不定值,全賴於剛剛誰用了那塊記憶體。

  • Traits – 基本上就是 Java Interface 的抽像層概念,把介面和實作分開,更加活用。(我不知道為什麼這個放在這邊?也許有人有答案)

  • 引用 (references)

    • Ownership 的概念:任何資源的 owner 有責任去釋放他佔有的資源
    • 保證安全的引用:不允許兩個以上對同一個變數的可變引用 (mutable reference)。在 C++ 中,變數的引用是不加限制的,也就是工程師自己必須清楚變數的狀態。如果 function A() 和 function B() 同時引用同一個變數,則 function B() 對變數所做的改變就會反應在 function A() 所引用的同一個變數上 – 就像是不需要 deference 的 pointer。在這邊這項工作是由 Rust compiler 自己去推算並對程式設下限制,所以 compiler 本身完全掌控變數的可變性,並阻擋人類寫的不合理的程式。
    • 生命周期 – 在 C/C++ 中常常發生的一個錯誤,指標指向的東西已經不存在了,但指標本身還存在而且被拿來使用,然後就發生了記憶體存取錯誤。在這邊,compiler 會保證每個指標都必須指向一個真正存在的記憶體;如果 compiler 無法推算的話,就必須由人手動指定變數的生命周期。
  • Concurrency – 在 rust 中,對 reference 的管理也是跨 thread 的,也就是不會產生 race condition。標準函式庫裡提供了一些可以用於共享資料的 container 物件,如 immutable 的 Arc,和 mutable 的 Mutex。(共通點似乎是都屬於 std::sync 模組)

  • 一些小撇步

    • 不要使用 unsafe block – 除非你是在整合 C library
    • 不要因為強迫初始化而先給變數一個沒有用的值,比如說 “” (空字串)
    • 不要用 .unwrap() – (哈,我說了吧!)

有意思的是,作者在最後提到了 Perl 6 的 subset,一個使用動態型別的語言,卻同樣可以降低 programming error 的特性。看了一下,的確蠻有意思的,令人想到 haskell 的 list comprehension。


#2

ps: 調查了一下,看起來 Rust 並沒有 exception-catch 的設計,但是在 unsafe block 中有做一些 guard 和 cleanup 的保護 (unsure)。

Rust 的設計跟 Erlang 類似,有 exception 的時候預設就是直接把 thread 結束掉。在跟 C 整合的時候可以選擇 catch unwind.