[為你自己學 Rust] 哈囉,Rust!
據說,在很早很早以前,有某位美國貝爾實驗室的大大在他們內部的技術文件的範例程式碼裡提到了 Hello World
這一串字,從那之後許多程式語言的第一堂課都跟風的在畫面上印出 Hello World
,所以我們也來試試來印個 Hello Rust
吧!
不過在這之前,得先把軟體以及開發環境給搞定。Rust 的安裝還算簡單,在 Rust 的官網上可以找到最適合各位的安裝方式跟版本,這我就交給大家動手做了。並不是不想浪費篇幅來寫如何安裝,而是一來 Rust 的安裝真的不難,各作業系統都有支援,我預設各位是有一點工作經驗的工程師,這應該難不倒大家;二來就以往寫文章或寫書的經驗,每次軟體改版,改的不一定是程式語法本身,倒是很常改安裝方式。
所以,就請大家照著 Rust 的官網文件進行安裝吧,本書在撰寫的當下的 Rust 版本是 1.72.0
。雖然我的作業系統是 Mac,但只要不要用到平台特定的語法,本書的範例程式碼應該在其它作業系統也能正常運作。
參考資料:https://www.rust-lang.org/tools/install
在 Windows 上安裝 Rust 本身不難,只要從 Rust 官網下載安裝包一步步照著做就好,但後續要進行編譯的話,需要額外的建置工具,這在微軟的網站上也有相關的說明。
但如果你只是想試試看還不想安裝只想先體驗一下手感,Rust 有提供線上的遊樂園讓你試玩:
你可以像上面這樣寫一小段程式碼,然後按下 RUN
按鈕,看看會發生什麼事。
要如何檢查自己電腦裡是不是已經安裝正確版本的 Rust 呢?以 Mac / Linux 來說是在終端機裡,而 Windows 則是使用命令提示字元或 PowerShell 視窗中輸入以下指令,檢查是不是有安裝最新版本:
$ rustc --version
rustc 1.71.1 (eb26296b5 2023-08-03)
提示:之後的範例程式中如果看到開頭的
$
,是表示這是一個終端機指令而不是程式語法,那個$
符號請不要跟著輸入,不然會發生錯誤訊息。
如果你像我一樣之前曾經安裝過 Rust,但並不是最新版本,可使用 Rust 內建的工具包 rustup
來昇級 Rust 的版本:
$ rustup update
等它跑完應該就可以有最新版本的可以用了:
$ rustc --version
rustc 1.72.0 (5680fa18f 2023-08-23)
開發工具
將來會不會有更厲害、更好用的工具我不確定,就以目前的來說,我會使用 Visual Studio Code(以下簡稱 VSCode)再搭配擴充程式 rust-analyzer
來編寫 Rust 程式,手感挺不錯的。
專門在開發 IDE 的公司 JetBrains 最近有推出一款 Rust 專屬的 IDE 叫做 RustRover,不過要等 2024 年才會正式上市。
起手式
假設你已經順利安裝好 Rust 跟 VSCode,我們就跟風一下,來寫個 Hello Rust
的程式!
跟 JavaScript 程式的附檔名是 .js
、Python 的附檔名是 .py
一樣,Rust 程式的附檔名是 .rs
。你可以在你容易找的到的地方(例如桌面)建立一個名為 hello.rs
的檔案,用 VSCode 打開它之後,跟我一樣輸入以下程式碼:
fn main() {
print!("Hello Rust!");
}
這裡有兩件事要注意一下。
首先,這個檔案要叫什麼名字無所謂,但函數的名稱要叫做 main
,它是整個程式的進入點,如果沒有的話待會編譯的過程會出錯。
其次,print!
最後面的那個驚嘆號 !
不要漏了寫。曾經寫過其它程式語言的話,你大概會猜這個 print!
就像 console.log
一樣是個函數(Function),但其實這東西在 Rust 裡稱之為 Marco(你也可翻譯巨集),它並不是函數。沒關係,細節在後面的章節會再詳述,你現在可暫時把它當函數看待就好。
寫完也確定存檔之後,接著請 Rust 來「編譯(Compile)」這個原始檔:
$ rustc hello.rs
如果程式碼沒有打錯字而且指令也沒下錯的話,你在畫面上不會看到任何訊息,但你應該會發現這時候在同個目錄下會產生一個同名的檔案 hello
(如果在 Windows 作業系統則是產出 hello.exe
)。這是一個二進位(Binary)檔案,所以你用編輯器大概也打不開它。如果有發生錯誤,你可以觀察看看它給你錯誤訊息,通常可以找到問題在哪裡。
最後,我們來執行這個剛剛透過 rustc
編譯產出的檔案:
$ ./hello
Hello Rust!
前面的 ./
是指「執行在這個地方的 hello
程式」,然後你應該會在畫面上看到 Hello Rust!
字樣。
做到這裡要先恭喜你一下,你寫出第一個 Rust 程式了!
不要小看 Hello World,這個環節對不常接觸終端機的讀者來說還滿不友善的。如果卡關的話,歡迎丟個訊息給我,或是每週二晚上我們辦公室固定都有社群活動,你可以帶著你的問題來現場一起討論。
[你知道嗎] 其實不是 .rs
也可以
很多人以為附檔名就是一切,寫 Rust 程式就是要 .rs
檔,但其實 Rust 的原始檔本身是純文字格式,重點是檔案的內容而不是它叫什麼名字,不信的話,你可以把剛剛那個 hello.rs
改名成 not_rust.js
,然後再重做一次:
$ rustc not_rust.js
$ ./not_rust
單獨這樣編譯還是可以正常運作的。講是這樣講,但不要這樣拿石頭砸自己或是同伴的腳。建議還是使用 .rs
附檔名,編輯器才會提供給你正確的語法提示以及語法高亮。
建立專案(純手工版)
以 Hello World 等級的複雜度來說,單一個 .rs
檔案就能搞定。不過我相信大家學 Rust 不會只想學到 Hello World 等級而已,專案越做越複雜,也許就得開始拆分模組,或是使用外部的套件,這就不是單一個 .rs
檔案就能搞定的。
在 Rust 要建立一個所謂的「專案」也不複雜,最陽春的專案其實只要一個 .toml
設定檔就行了。TOML
是 Tom's Obvious, Minimal Language(湯姆的淺顯的、極簡的語言)幾個字的縮寫,其中 Tom 就是這個設定檔格式的作者。TOML 的格式寫起來會分段落,並且使用 Key = Value 的方式來撰寫。
接下來請你同樣在一個你找的到的地方,建立一個名為 Cargo.toml
的檔案,檔案內容如下:
[package]
name = "hello_rust"
version = "0.0.1"
這個檔名規定要叫做 Cargo
,Cargo 這個字是輪船、飛機等大型交通工具裝載的貨物的意思,也就是我們常看到的那些貨櫃,這個名字滿貼切的。這樣就是最最最陽春的 Rust 專案,在 [package]
段落裡,name
指的是這個專案的名稱,而version
就是這個專案的版本,這兩個欄位是一定要填寫的。
接著可以執行這個指令:
$ cargo run
這時候你應該會看到以下的錯誤訊息:
error: failed to parse manifest at `/tmp/hello-rust/Cargo.toml`
Caused by:
no targets specified in the manifest
either src/lib.rs, src/main.rs, a [lib] section, or [[bin]] section must be present
不用怕,這段錯誤訊息其實是跟你抱怨說它少了一些檔案,不知道怎麼進行下一步,因為它預期在 src
目錄裡應該要有一個 lib.rs
或 main.rs
檔案,但我們現在並沒有這個檔案。既然沒有,那我們就做給它,我就手工建立一個 src
目錄,並且在裡面建立一個名為 main.rs
的檔案,檔案內容如下:
fn main() {
println!("Hello Rust from Cargo!")
}
這個 println!
跟前面的 print!
都可以把字印出來,但 println!
會多印一個換行。這時候整個目錄的結構差不多長這樣:
如果你的 VSCode 有安裝 rust-analyzer
擴充套件的話,你可能會發現它自動幫你產生一些檔案跟目錄,但先不用管它。我們再執行一次剛才失敗的指令:
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.07s
Running `target/debug/hello_rust`
Hello Rust from Cargo!
最後一行就成功印出 Hello Rust form Cargo!
字樣了。再仔細觀察上面的訊息,會發現在過程中會幫你把編譯的檔案放在 target/debug/
目錄裡,並幫你生成一個 hello_rust
的執行檔。
這個 cargo run
指令的功用,就是會幫你進行編譯並且直接執行編譯好的執行檔。如果只是想單純編譯的話,可以只用 cargo build
指令就好。
同時你可能有注意到上面的訊息裡有個 unoptimized
的字樣,表示這個指令並不會進行最佳化編譯。如果想要有最佳化效果,不管是 cargo run
或 cargo build
都可以,只要在後面加上 --release
參數就行了:
$ cargo build --release
Compiling hello_rust v0.0.1 (/private/tmp/hello-rust)
Finished release [optimized] target(s) in 0.10s
$ cargo run --release
Finished release [optimized] target(s) in 0.00s
Running `target/release/hello_rust`
Hello Rust from Cargo!
它編譯出來的產物會放在 target/release
目錄裡。
在專案開發的過程中我們可能常常會不斷重複進行修改原始碼 + 編譯的動作,預設的編譯參數雖然不是最佳化(unoptimized),但編譯的速度比較快,等到要正式發佈的時候再加上 --release
參數即可。
透過 cargo run
或 cargo build
建立出來的產出物會放在 target
目錄裡,如果不想要了,直接用開檔案總管把整個 target
目錄丟掉就行了,或想敲指令耍一下帥:
$ cargo clean
就會把 target
目錄刪掉了。
建立專案(自動版)
前面這個手動編寫 Cargo.toml
檔案,還要自己建立 src
目錄跟 main.rs
檔案,雖然也不麻煩,但只要一個 cargo
指令就可以直幫我們產生上面的內容:
$ cargo new hey-rust
Created binary (application) `hey-rust` package
這個指令會幫你建立一個 hey-rust
目錄,點開看裡面的內容就會發現這跟我們剛剛手工打造的結構是一樣的。仔細觀察會發現還會幫你加一個 .gitignore
,並且把 target
目錄排除在 Git 版控之外。
同樣也是 cargo
系列的指令,使用 cargo init hey-rust
也會幫你做出跟 cargo new hey-rust
一樣的內容,然後如果 cargo init
不加參數的話,刪不會幫你建立一個新的目錄,而是在當下目錄幫你建立這些相關的檔案,大家就視實際情況使用。
如果你曾經開發過前端相關的專案,到這裡應該會有點即視感,這波操作就跟 npm init
有點像。其實是的,雖然不完全一樣,但你可以把 Rust 裡的 cargo
指令當成是 JS 裡的 npm
指令看待。
安裝套件
如果要使用別人寫好的套件,可以使用 cargo add
指令加上套件名稱,就整個會幫你自動安裝到好,例如我們來安裝一個產生亂數的套件 rand
:
$ cargo add rand
Updating crates.io index
Adding rand v0.8.5 to dependencies.
Features:
+ alloc
+ getrandom
...略...
- serde1
- simd_support
- small_rng
Updating crates.io index
這樣就裝好了。這時你打開 Cargo.toml
看一下,會發現在底下的 [dependencies]
段落多了一些東西:
[dependencies]
rand = "0.8.5"
問題來了,這個指令怎麼知道它要去哪裡找套件?JavaScript 的 npm
有 https://www.npmjs.com/,Ruby 的 gem
指令有 https://rubygems.org/,而 Rust 的 cargo
有 https://crates.io/ 網站:
Crate 英文是裝東西的板條箱(就如它的 Logo),這名字也挺貼切的。
除了 Cargo.toml
檔案多了一些檔案,同時目錄下也有一個 Cargo.lock
檔案,你可以拿 JavaScript 的 package.json
跟 package.lock
來類比 Cargo.toml
以及 Cargo.lock
。
其實早期要安裝套件會比較辛苦一點,必須手動在 Cargo.toml
檔案裡加上 rand = "0.8.5"
的設定,然後再執行 cargo run
或 cargo build
指令進行編譯,後來 Rust 在 1.62.0 版本開始加入了 cargo add
指令,讓安裝的過程順手不少。
參考資料:https://blog.rust-lang.org/2022/06/30/Rust-1.62.0.html
套件裝到哪裡去了?
如果你是前端工程師,大概知道 npm install
指令會把相關的套件裝一份到 node_modules
裡,但仔細看剛剛建立的專案,裡面好像沒看到像是安裝好的 rand
套件?
不像 npm 的專案是每個專案都裝一套,Rust 的套件會統一安裝在個人使用者的 Home 目錄底下,在 Mac / Linux 作業系統會在 ~/.cargo/
,Windows 作業系統則是安裝在 C:\Users\你的使用者名稱\.cargo
。
Space vs Tab,你是哪一派?
各位寫程式的時候,你是用空白鍵還是 Tab 鍵來進行縮排?如果是空白鍵的話,你是用 2 個空白鍵、4 個空白鍵還是 8 個空白鍵?這一直就是吵不完的話題,每個程式語言的慣例也都不太一樣。
根據 Rust 官方手冊的 Coding Style 建議:
- Use spaces, not tabs.
- Each level of indentation must be four spaces
好啦,大家不用吵啦,官方建議用「4 個空白鍵」。然而在 JavaScript 有 prettier
套件,Golang 有 gofmt
或 go fmt
指令,在 Rust 也有 rustfmt
:
$ rustfmt src/main.rs
這個指令會自動依照 Rust 官方建議的方式幫你調整檔案的縮排(4 個空白鍵)。當然,如果你不認同 Rust 的官方建議風格,你就是覺得 3 個空白鍵比較酷(?),你可以在專案底下放個 rustfmt.toml
設定檔,裡面這樣寫:
tab_spaces = 3
這樣以後格式化出來的結果就是用 3 個空白鍵了。更多關於 rustfmt.toml
的設定內容可參考文件說明。
參考資料:https://rust-lang.github.io/rustfmt/
如果你是一個 Cargo 專案,也可以直接這樣做:
$ cargo fmt
這也會幫你做類似的事情。