跳至主要内容

寫在最前面

為你自己學 Rust

嘿,各位好!

我平常的工作主要是教學、企業訓練以及網站專案開發,所以這二十多年來大部份的技術堆疊、工作圈以及生活圈都是網站開發相關,認識的工程師朋友也大多是網站前、後端的工程師。

也許各位不一定認同我以下的看法,但我個人一直認為,一位優秀的網站工程師,不管是前端、後端還是整碗全部端走的全端,手上除了平常開發網站用的程式語言外,口袋裡應該也要準備另一款像是 C 語言這種相對比較低階、比較接近系統層級的程式語言,雖然不一定派的上用場,但我相信一定可以在學習的過程中對程式語言或其執行環境有更深一層的了解。

在眾多程式語言裡面,我其實觀望 Rust 好一陣子。有找了一些書來看,但一直沒花時間在上面或是找個專案實際來練練手。我很清楚自己的個性跟學習方式,如果不認真做大概過一陣子就又不把它當一回事,最後只能跟別人打打嘴砲但對它還是只有一知半解。

所以,我決定試著以一個「網站工程師」的角度來學習 Rust,並且把它寫成書,讓跟我有差不多背景的人也可以循著差不多的軌跡來學習 Rust,因此我也會把本書的讀者設定為已經至少有半年以上工作經驗的網站工程師。

關於 Rust

跟其它大家比較常聽到的程式語言相比,Rust 誕生於 2010 年,算是相對比較年輕的程式語言。也因為比較年輕,所以在設計上可以避開那些前輩們曾經踩過的坑,還可以順便向它們借鑑一些做的不錯的地方。

雖然 Rust 連續好幾年都被 Stack Overflow 網站票選為最多人喜愛(most loved)的程式語言,但就以目前(2023 年)的就業市場來說,Rust 相關的職缺還是少的可憐。

我一直認為「好東西不一定需要流行」(反之,流行的也不一定是好東西),雖然以目前程式語言的使用率來說它還算不算前段班,但因為 Rust 的效能很好而且天生的「安全性」,所以但不少人拿它對某些工具或程式語言進行「重製」,不少大公司也用它在寫一些服務或 DevOps 工具。這裡的提到「安全性」並不是大家常聽到的網路資訊安全,這部份就讓我在後面的章節再跟各位介紹。

Rust 的作者是 Graydon Hoare,這原本只是他在 Mozilla 任職時候的自己做的小玩具,後來公司支持他還給他一些人手繼續開發這個專案,在 2010 年正式發表。一開始 Rust 是使用 OCaml 程式語言開發,但等它長大之後,就用 Rust 自己來改寫自己,所以現在大家看到 Rust 的原始碼都是用 Rust 寫的。這種稱為「自我托管(Self-hosting)」的做法很讚也很酷,這不只表示該語言或編譯器的功能已經足夠成熟,可以完全自給自足地運行和進行進一步的開發,同時也可讓 Rust 的學習者透過研究原始碼就能了解實作原理,在本書的中後段你可能會發現我會試著拿 Rust 的原始碼跟大家解釋某些功能的原理,而且你應該都能看的懂。如果後續想要對 Rust 的原始碼進行一些修改或貢獻也是比較容易做的到的。舉個相對的例子,其它由 C 語言撰寫出來的程式語言(例如 Python、Ruby、PHP),如果想要了解或甚至修改原本的實作,可能還得先花點時間學一下 C 語言才行。

就我自己的學習經驗,跟其它程式語言相比,Rust 的學習曲線相對的不太平緩。我寫程式也二十多年了,一開始我還覺得 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 可就不是這麼回事了,例如這樣寫:

let a = 1;
a = 2;
println!("{}", a);

這看起來很理所當然應該要印出 2,但執行之後 Rust 卻跳個錯誤訊息給你看:

[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、Python 或 Ruby 的陣列來操作應該會再次遇到挫折,硬要說的話,大家平常比較熟悉的「陣列」,在 Rust 比較接近 Vector 的設計;大部份的程式語言只要用單引號或雙引號就能使用字串(String Literal),但在 Rust 裡稍微複雜一些,"Hello Rust" 還是字串,只是它操作起來會跟你想像的不一樣。我想這些細節就留到後面章節再詳述,各位讀者可再慢慢體會這其中的差異。

學 Rust 可以幹嘛?

想想看當年只是拿來寫檢查表單哪個欄位漏了寫的 JavaScript,在 Node 出來之後竟然能寫系統程式了,React 出來之後甚至還能寫原生的手機程式,在 Electron 出來之後現在連桌面應用程式都能寫了。

所以,學 Rust 可以幹嘛?我想這個問題沒有答案。以現在來說,常聽到的就是寫一些 CLI(Command-Line Interface)工具,如果覺得 Electron 打包的應用程式太肥,可以改用 Rust 陣營的 Tauri。對網站前端工程師來說,Rust 也可以編寫出 WebAssembly 處理那些效能吃緊的運算。對網站後端工程師來說,Rust 的效能很不錯,拿來當 API Server 是個不錯的選擇。目前 Rust 也有幾套寫的不錯的後端框架,但如果要跟 Laravel、Django 或 Ruby on Rails 之類的相比還有一段不小的距離。

正也因為它的效能很好,所以有些程式語言或框架會用它改寫來提昇效能,像是程式語言 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 這樣設計,這些計算機概論之類的基礎知識我也會用一些篇幅介紹來填補這塊空缺。

閱讀順序

沒有強制規定一定要照著章節順序閱讀,你想要跳著看、躺著看或倒立著看也都可以。不過因為我在撰寫本書的時候是一邊學習、一邊整理出我自己的學習路線,所以如果你跟我一樣也是 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

同樣也為了聚焦重點,我會視情況刪除把訊息裡的執行指令編譯檔名檔案名稱及行數或是不必要的提示,簡化之後看起來大概會像這樣:

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`

所以如果你發現程式執行之後的錯誤訊息跟我書上的不太一樣,不用太擔心。

關於我

我是高見龍,是個愛寫程式而且希望可以寫一輩子程式的電腦阿宅!高見龍這個看起來有點像武俠小說的名字並不是筆名,這是我父母給我的本名,我很喜歡這個名字。

我從 1998 年開始寫各式各樣的網站應用程式,2009 年的時候因為朋友的介紹,我開始接觸了開源相關的社群活動,發現這個圈子好多傻子,都很無私的貢獻自己的時間跟精力在開源專案跟技術社群上,發現了新玩具就巴不得趕快跟大家分享。我實在搞不懂這樣有什麼好處,所以我就加入大家,跟著一起當笨蛋,想看看他們到底在幹嘛。搞到最後,我光是參加社群活動還不過癮,甚至還自己辦活動,就是想認識更多跟我一樣的傻子。

以下是我的 Blog 跟社群帳號,歡迎追蹤或加好友:

Rust 新手上路,如果有寫錯的地方,還請多多包涵並不吝指正。接下來就請大家準備跟著我一起上車吧 :)