[為你自己學 Rust] 前言
嘿,各位好!
我平常的工作主要是教學、企業訓練以及網站專案開發,所以這二十多年來大部份的技術堆疊、工作圈以及生活圈都是網站開發相關,認識的工程師朋友也大多是網站前、後端的工程師。
也許各位不一定認同我這個看法,但我個人一直認為,一位優秀的網站工程師,不管是前端、後端還是整碗全部端走的全端,手上除了平常開發網站用的程式語言外,口袋裡應該也要準備另一款像是 C 語言這種相對比較低階、比較接近系統層級的程式語言,雖然不一定派的上用場,但我相信一定可以在學習的過程中對程式語言或其執行環境有更深一層的了解。
在眾多程式語言裡面,我其實觀望 Rust 好一陣子。找了一些書來看,但一直沒花時間在上面或是找個專案實際來練練手。我很清楚自己的個性跟學習方式,如果不認真做大概一陣子就又不把它當一回事,最後只能跟別人打打嘴砲但其實對它還是只有一知半解。
所以,我決定試著以一個「網站工程師」的角度來學習 Rust,並且把它寫成書,讓跟我有差不多背景的人也可以順著差不多的軌跡來學習 Rust,因此我也會把本書的讀者設定為已經至少有半年以上工作經驗的網站工程師。
關於 Rust
跟其它大家比較常聽到的程式語言相比,Rust 誕生於 2010 年,算是相對比較年輕的程式語言。也因為比較年輕,所以在設計上可以避開那些前輩們曾經踩過的坑,或是向它們借鑑一些做的不錯的地方。
雖然 Rust 連續好幾年都被 Stack Overflow 網站票選為最多人喜愛(most loved)的程式語言,但就以目前(2023 年)的就業市場來說,Rust 相關的職缺還是少的可憐。
我一直認為「好東西不一定需要流行」(反之,流行的也不一定是好東西),所以雖然以目前程式語言的使用率來說它還算不算前段班,不過因為 Rust 的效能很好而且天生的「安全性」,所以但不少人拿它對某些工具或程式語言進行「重製」,不少大公司也用它在寫一些服務或 DevOps 工具。
這裡的提到「安全性」並不是大家常聽到的資訊安全,這部份就讓我在後面的章節再跟各位介紹。
Rust 原本只是某位 Mozilla 員工 Graydon Hoare 的個人專案,後來公司支持他繼續開發這個專案。一開始 Rust 是使用 OCaml 程式語言開發,但等它長大之後,就用 Rust 自己來改寫自己,所以現在大家看到 Rust 的原始碼都是用 Rust 寫的。這種稱為「自我托管(Self-hosting)」的做法很讚也很酷,這不只表示該語言或編譯器的功能已經足夠成熟,可以完全自給自足地運行和進行進一步的開發,同時也可讓 Rust 的學習者透過研究原始碼就能了解實作原理。而且如果後續想要對 Rust 的原始碼進行一些修改或貢獻也是比較容易做的到的。舉個相對的例子,其它由 C 語言撰寫出來的程式語言(例如 Python、Ruby、PHP),如果想要了解或甚至修改原本的實作,可能得先花點時間熟悉 C 語言才行。
再跟其它程式語言比起來,Rust 的學習曲線相對的不太平緩,特別是進到「所有權(Ownership)」跟「生命週期(Lifetime)」之後突然感覺開始爬坡,所以如果 Rust 是你的第一款程式母言而且你想要透過 Rust 來學習如何寫程式的話,你應該會遇到不少的挫折。可以的話,我建議各位讀者先從其它比較容易學習的程式語言開始,像是 JavaScript、Python 或 Ruby 之類的腳本式程式語言(Scripting Language),先熟悉寫程式是怎麼一回事,再回頭來學習 Rust 會輕鬆一些些。
由於 JavaScript 算是相對普及的程式語言,所以在這本書裡,我會假設大家可能至少會寫一點 JavaScript 程式,或是看的懂 JavaScript 的程式碼。
為什麼這麼說?請大家看看以下這段 JavaScript 的程式碼:
let a = 1
a = 2
console.log(a) // 這會印出什麼?
這大概是比 Hello World 再進階一點點的程式碼了,就算你沒寫過 JavaScript,但凡只要學過其它程式語言的讀者應該都能猜出答案是 2
。
但這在 Rust 可就不是這麼回事了,例如這樣寫:
fn main() {
let a = 1;
a = 2;
println!("{}", a);
}
這看起來很理所當然的程式碼,但它卻跳個錯誤訊息給你看:
error[E0384]: cannot assign twice to immutable variable `a`
|
2 | let a = 1;
| -
| |
| first assignment to `a`
| help: consider making this binding mutable: `mut a`
3 | a = 2;
| ^^^^^ cannot assign twice to immutable variable
在 Rust 裡的變數預設是「不可變更(Immutable)」,如果試著想要改變變數 a
的值,便會出現上面這樣的錯誤訊息。
「蛤?怎麼這麼麻煩啊?」
其實預設為 Immutable 其實好處滿多的,但這對一般的網站工程師來說比較難想像。
另外像是 Rust 裡也有 Array
的設計,但如果你用像是 JavaScript 的陣列來操作就會再次遇到挫折,硬要說的話,大家平常比較熟悉的「陣列」,在 Rust 比較接近 Vector
的設計;大部份的程式語言只要用單引號或雙引號就能使用字串(String Literal),但在 Rust 裡稍微複雜一些,"Hello Rust"
還是字串,只是操作起來會跟你想像的不一樣。我想這些細節就留到後面章節再詳述,各位讀者再慢慢體會這其中的差異。
學 Rust 可以幹嘛?
想想看當年只是拿來寫檢查表單哪個欄位漏了填的 JavaScript,在 Node 出來之後竟然能寫後端了,React 出來之後甚至還能寫原生的手機程式,在 Electron 出來之後現在連桌面應用程式都能寫了。
所以,學 Rust 可以幹嘛?我想這個問題沒有標準答案。以現在來說,常聽到的就是寫一些 CLI(Command-Line Interface)工具,如果覺得 Electron 打包的應用程式太肥,可以改用 Rust 陣營的 Tauri。對網站前端工程師來說,Rust 也可以編寫出 WebAssembly 處理那些效能吃緊的運算。
同時也因為它的效能很好,所以有些程式語言或框架會用它改寫來提昇效能,像是程式語言 Ruby 有部份的核心程式碼改用 Rust 重寫,Deno 由原本的 Golang 也改由 Rust 改寫,Next.js 這個框架後來也改用 Rust 改寫,效能提昇不少。
之前有朋友問過為什麼不選擇 Golang 或其它程式語言,我常開玩笑的回答「因為 Rust 跟我最喜歡的程式語言 Ruby 的前面兩個字一樣」,但對我個人來說,學 Rust 主要的兩個目的,一個是可以學到更底層的知識,另一個就是只是好玩 :)
有哪些單位在使用 Rust
像是 Microsoft、Amazon、Dropbox、Cloudflare 以及 Figma 等的大型企業或知名服務都採用 Rust 進行系統程式開發,有興趣的話,在 Rust 的官網可以找到更多資料,但到底有哪些公司在用 Rust 老實說我根本不在意。
如同本書的標題「為你自己學 Rust」,學習不需要為公司、為長官,而是為你自己。只有你自己真心覺得學習它對自己有幫助,或是覺得有趣,這條路才會走的長遠。
關於本書
我讀過幾本 Rust 的書,都寫的很棒,但我一直在想,假如我想要把 Rust 也推坑給一般的網站開發者的話,可能需要填補的技術空缺還不小,所以我一直在想要如何下筆怎麼寫這本書。
我會假設各位讀者曾經寫過其它的程式語言(我預設是 JavaScript),所以當介紹到某些 Rust 的特性的時候,我偶爾會用「這個就跟 JavaScript 的陣列差不多概念」或是「Cargo 就像 JavaScript 世界的 npm 」之類的類比說法,讓大家更容易理解。
不過對只寫前端或是只平常只做資料 CRUD 的網站工程師來說,日常工作寫的也許就只是驗證表單或資料表的某個欄位是不是漏了填,突然要跳到相對較低階或底層的程式語言,例如像是記憶體的操作,或是 Stack 跟 Heap 有什麼不同應該會很不習慣或不知所以然,為了讓大家能更了解為什麼 Rust 這樣設計,這些計算機概論之類的基礎知識我也會用一些篇幅介紹來填補這塊空缺。
閱讀順序
其實沒有規定一定要照著順序往下看,跳著看也沒問題。不過因為我在撰寫本書的時候也是一邊學習、一邊整理出我自己的學習路線,所以如果你跟我一樣也是新手,不妨就照著章節的順序閱讀可能會比較容易上手。
程式碼慣例
在本書中使用的程式碼慣例,當你看到以下這種 $
符號開頭的:
$ cd /tmp/hello-rust
$ cargo run
這個不是程式碼,這通常表示這是一個系統指令,你應該是在終端機或命令提示字元的環境下輸入,而且不需要跟著打 $
符號。
另外,有時候為了聚焦在特定的情境,原本像這樣的範例:
fn main() {
let book = "為你自己學 Rust";
println!("{}", book);
}
為了簡化情境,各位可能會看到我把 main()
或是函數的外殼拿掉甚至刪掉不必要的程式碼,只留下重點的內容:
let book = "為你自己學 Rust";
println!("{}", book);
實際上當然該寫的還是得寫,只是這樣比較容易把重點放在要講解的內容上。同樣的,執行程式的時候如果發生錯誤,Rust 給的錯誤訊息有時候太「豐富」了,像這樣:
$ cargo run
Compiling hello-rust v0.1.0 (/private/tmp/hello-rust)
error: literal out of range for `u8`
--> src/main.rs:2:15
|
2 | let age: u8 = 1000;
| ^^^^
|
= note: the literal `1000` does not fit into the type `u8` whose range is `0..=255`
= note: `#[deny(overflowing_literals)]` on by default
error: could not compile `hello-rust` (bin "hello-rust") due to previous error
同樣為了聚焦重點,我會視情況移除錯誤訊息裡的編譯檔名、檔案名稱及行數或是不必要的提示移除,簡化之後看起來大概會像這樣:
$ cargo run
error: literal out of range for `u8`
2 | let age: u8 = 1000;
| ^^^^
|
= note: the literal `1000` does not fit into the type `u8` whose range is `0..=255`
所以如果你發現程式執行之後的錯誤訊息跟我書上的不太一樣,不用太擔心。
Rust 新手上路,如果有寫錯的地方,都歡迎不吝指正 :)