一位 JavaScript 鐵桿粉眼中的 Rust!

語言: CN / TW / HK

關注「 Rust程式設計指北 」,一起學習 Rust,給未來投資

作者 | Harvard

譯者 | 彎月    責編 | 歐陽姝黎

出品 | CSDN(ID:CSDNnews)

以下為譯文:

我使用 Rust 編寫了一些小工具,而且覺得很有樂趣。我的日常工作需要大量使用 JavaScript,而 Rust 給我一種非常熟悉的感覺,因此我決定嘗試一下Rust。但與此同時,使用 Rust 完成真正有意義的工作需要重新思考程式碼的結構和合理性。編譯器是最公正無私的,然而反覆修改程式碼,直到最終通過編譯也是一種樂趣。

在這篇文章中,我將分享我在 Rust 之旅中的一些想法,以及作為 JavaScript 鐵桿粉,我對 Rust 的看法。

好訊息

現代 Rust“看起來”與現代 JavaScript 非常相似。你可以使用 let 宣告變數,而且函式看上去也很相似,由於 TypeScript 的流行,我對 Rust 的型別也不陌生,還有 async/await,總的來說,我對 Rust 有一種莫名的熟悉感。

壞訊息

問題的核心不是語法,而是 Rust 對程式內部結構的推理方式。高階語言中包含大量抽象,因此你不必擔心計算機的工作方式。這非常合情合理,如果你的目標是開車趕到辦公室,那麼只需要知道如何駕駛汽車,而不必搞懂內燃機的內部結構。相比之下,在低階語言中,你會得到螺栓和螺絲釘,為了開車去超市,你必須成為一名汽車修理工。

每種方法都有各自的優缺點,因此,它們的主要問題領域也不同。而 Rust 的目標是中間地帶。你既可以使用 Rust 訪問基礎設施,也可以使用清晰易懂的高階抽象。但是,開發人員勢必會為此付出代價:你必須學習一種新的程式推理方式。

記憶體管理

計算機程式依賴記憶體中的讀寫。記憶體讀寫是不可避免的,就像巧婦難為無米之炊。做飯的時候,我們必須將食材都買回來,然後洗乾淨切好,再按照正確的順序將它們放入鍋中,吃完飯後還需要收拾廚房裡的爛攤子。

而高階語言提供了垃圾收集器,就像我們的父母,他們會耐心地幫你打掃衛生,而你則無需弄髒雙手。然而,Rust 的記憶體管理是:“嗯……真正的廚師會清理自己的垃圾”。這也並非全無道理,因為垃圾收集器本身就有很深奧的問題,而且會帶來意料之外的結果。但與此同時,Rust 借鑑了其他語言的過往經驗,並強制程式設計師管理好記憶體。

作用域

在 Rust 中,變數只能在某個作用域內使用。如果這個作用域不再有效,則這塊記憶體就會返回給系統。編譯器會向在程式碼中注入一段程式碼來確保這一點。這在 Rust 中是鐵一樣的定律。

下面,我們來看一個示例。

這段程式碼有兩個作用域。一個外層作用域來自 main,還有一個內層作用域。Rust 的所有權如下:

  1. main 擁有 a 和 b;

  2. a 想要使用內層作用域,所以 main 將 a 的所有權轉移到內層;

  3. 內層作用域處理a,然後完成;

  4. Rust的隱藏程式碼丟棄 a 的作用域;

  5. main 處理 b,然後完成;

  6. Rust 丟棄 b 的作用域。

請注意,所有權都會還給系統,而不是作用域的起源。a 的所有權不會返回給main 作用域。

等一等,這種做法聽起來很危險。如果遇到如下程式碼,該怎麼辦?

在這段程式碼中,main 作用域想再次使用 a,但是我們說當內層作用域結束時,Rust 已經刪除了 a。

程式執行到這裡的時候,不會崩潰嗎?

沒錯,程式會崩潰。

編譯器

Rust 編譯器會徹查一切,並評估程式是否可以安全執行。只有通過所有的檢查,它才會生成可執行檔案。

在上面這個例子中,編譯器拒絕提供二進位制檔案。

程式設計師的職責是瞭解 Rust 的法則,並遵守這些法則。包括這門語言所有的細節,所有的怪癖,所有的假設。否則,Rust 編譯器就會衝我們大吼大叫。另一方面,Rust 團隊一直在努力通過建立大量語法糖和清晰的錯誤訊息,幫助我們理解錯誤。而且,Rust 還有非常完善的文件和一個偉大的社群。

Rust的角色扮演遊戲

在 Rust 大陸中,變數是玩家。玩家必須屬於某個職業:法師、牧師、結構體。此外,每個玩家可能擁有不同的裝備。當然,你可以擁有兩個牧師,一個拿著權杖,一個拿著魔杖。

還記得上述程式碼中的dbg!()嗎?這是一個巨集,相當於 JavaScript 的 console.log。下面,我們來建立一個有型別的變數,並輸出日誌。

我們建立了一個 struct,本質上是一個型別。然後我們又建立了一個該型別的物件。最後,我們輸出該物件。

以上,Noob 型別的 player 連除錯資訊都沒有……

關鍵在於,我們手動建立的變數都是從 1 級開始的,沒有裝備。這裡需要裝備(用 Rust的術語說,就是 traits)。

我們來修改一下。

這一次可以了。唯一的不同就在於開頭的第一行。我們為 Noob 配備了 Debug 特性。現在,我們的player就有資格輸出日誌了。巨大的進步!

Rust 擁有大量的裝備,比其他語言更普遍。而且,你還可以自己設計並打造新裝備。

有些 trait 可以由編譯器為我們自動生成。而有些則需要自己實現。你想給你的法師打造一個盔甲?沒問題,當然可以,但是你必須提供實際的程式碼。

trait 在 Rust 的結構中根深蒂固。我們再來看一看上述那個報錯的例子。仔細閱讀錯誤訊息,我們會注意到,編譯器向我們解釋,必須“移動”變數的所有權,因為字串沒有實現 trait:Copy。

Copy trait 意味著你可以獲取一段記憶體,然後 memcpy 到其他地方,直接對位元組進行操作。

那麼,既然字串沒有 Copy trait,我們可不可以要求編譯器提供一個?抱歉,不行。Copy 的級別太低,字串無法安全地使用這個trait。編譯器知道這一點,所以不提供。當然,如果故事就此結束,Rust 就不會成為一門非常實用的語言了。實際上,字串有一個更明確的 trait,可以完成相同的工作:Clone。而字串也具有 Clone  trait,因此我們只能用 Clone 來代替 Copy。

我們來稍微調整一下程式碼,像下面這樣:

在這段程式碼中,編譯器看到我們想在內層作用域中使用a,而且它看到我們可以使用 clone 來完成操作。所以,

  1. a 的所有權歸 main;

  2. a.clone 在建立後,被借用到內層作用域;

  3. 內層作用域執行操作,然後完成;

  4. Rust 丟棄 a.clone 的作用域;

  5. main 可以使用 a,因為 a 的所有權始終歸它所有。

當然,這不是唯一解決這個問題的方法,但我們可以通過這個例子初步探索一下所有權和trait。

總結

文字介紹的內容對於 Rust 學習來說,不過是冰山一角。根據我的個人經歷,Rust 的學習曲線很陡峭,但整個學習過程很有趣,而且物有所值!我會繼續努力學習下去!

原文連結:

https://blogs.harvard.edu/kapolos/rust-from-a-javascript-perspective/

宣告:本文由CSDN翻譯,轉載請註明來源。

推薦閱讀

覺得不錯,點個贊吧

掃碼關注「 Rust程式設計指北