一位 JavaScript 鐵桿粉眼中的 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/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0vhi0B6XjbUoElEtR1e85Q