因為 Rust 是編譯型語言,在今天的主題之前需要先了解一下編譯(Compile)和建構(Build)。
編譯型語言需要先把源碼(人看的)轉換成機器碼(機器看的)才能直接執行。而編譯和建構會負責處理這個過程,兩者在含義上略有差異。
指的是將一種語言寫成的程式碼轉換成機器可以直接執行的機器碼或位元組碼的過程,主要任務包含:
指的是將一個軟體專案的所有源碼、資源、庫等整合在一起,生成最終的可執行檔案或可部署的軟體包的過程,所以包含但不限於編譯,主要步驟包含:
簡單的說,編譯專門指把源碼轉換成機器碼:建構則包含編譯、鏈接與打包等多個步驟。
接著今天來介紹安裝完 Rust 相關的環境後的 3 個主要的工具:rustup
、rustc
和 cargo
,並且不免俗的來 hello world 一下。
rustup
是用來管理 Rust 工具鍊的工具,所謂的工具鍊是指一組協同工作來完成軟體開發的各個階段的工具,從編寫程式碼、套件管理到編譯、測試等,以 Nodejs 來比喻就像是 nvm
,可以利用它來做版本切換或安裝不同版本的工具鍊,像是 Rust 的不同版本(stable、beta等)。我們在上一篇已經有用到一些 rustup
的指令。
rustc
則是 Rust 的官方編譯器,是用來將 Rust 程式碼編譯成可執行文件或庫的核心工具,雖然也有一些實驗性專案想要發展其他編譯器,但因為可能存在兼容性或性能問題還沒成為主流,現階段來說很單純 rustc
是最穩定、最可靠的選擇。
rusts
最主要的職責包含:
編譯:
將Rust 源代碼編譯成二進制可執行文件或靜態、動態庫。
保證記憶體安全:
編譯器在編譯過程中會進行嚴格的所有權和借用檢查,確保記憶體安全,避免 memory leak 或空指標的狀況進而造成程式執行中出現非預期行為或崩潰等等。
記憶體安全簡單來說指的是:
- 程式不會嘗試存取或修改不屬於它的記憶體區域。
- 程式不會重複釋放同一個記憶體區域。
- 程式不會使用已經被釋放的記憶體。
錯誤檢查:
提供詳細的錯誤和警告信息以及提示,幫助開發者識別和修復問題。
Rust 的記憶體安全基本上是建立在編譯過程嚴格的檢查,代表 Rust 有很大部分的價值是建立在 rustc
這個編譯器上,很明顯它是 Rust 的核心部分。
rustc
最簡易的編譯指令如下:
$ rustc main.rs
執行後預設會產生一個可以執行的檔案,在 Lilux 和 masOS 中為main
;在 Windows 中為 main.exe
。
rustc
提供了豐富的設定選項,讓開發者能夠精準地控制編譯過程。這些設定會影響編譯器的行為,例如:
這些設定可以從命令行參數與配置文件設定,不過初階的部分其實我們都用不到這些,就不多做研究。
最後 cargo
是 Rust 的套件管理工具,類似 Node.js 的 npm
,除此之外,相較於rustc
,在實際開發中我們更多時候會使用 cargo,因為它可以幫助我們簡化編譯和管理依賴,而且 cargo 可以快速初始化專案、管理套件、執行編譯和測試等功能。
以下直接用一個 hello world 的例子說明。
它提供了一組指令 cargo new project_name
,這個指令會根據 project_name
建立一個資料夾並且在這個資料夾內幫我們快速把專案初始化,我們首先建立專案 hello_world
並且移動路徑。
$ cargo new hello_world && cd hello_world
接著我們檢查一下他建立哪些東西,
.
├── .git
├── .gitignore
├── Cargo.toml
└── src
└── main.rs
會發現它連 git 相關設定都幫我們設定好了。Cargo.toml
則是包含套件管理以及編譯、打包設定等等都在這個檔案裡,不過目前東西不多因為我們沒有裝額外套件也沒有要做特別的設定,就用預設值就好。main.rs
目前唯一的 Rust 檔案,副檔名是 rs
,是整個專案預設的進入點,Rust 也提供了靈活的方式來自定義程序的進入點,不過也是比較進階才會有的需求,目前也是先知道沒有一定只能用main.rs
當進入點就好。
接下來我們看一下 main.rs
寫了什麼:
$ cat src/main.rs
fn main() {
println!("Hello, world!");
}
太好了程式碼有基本款,所以我們可以先不用寫程式碼繼續往下測試XD
為了測試程式是否正常執行我們需要先執行建構,可以在專案的路徑用cargo build
產生一個target
資料夾,裡面有建構完的資料,其中對應我們程式碼的檔案是./target/debug/hello_world
,檔案名稱會用專案資料夾名稱命名,我們可以在 terminal 直接執行它。
當我們執行cargo build
的時候,背後有好幾個動作:
rustc
做的,只是多包裝了一層幫開發者簡化操作、設定等,而且 cargo 還可以根據不同的平台配置,自動生成適合該平台的可執行文件。cargo
還有幫我們整合好一個指令 cargo run
,它會先把程式編譯打包後自動執行,也就是我們剛才那兩個指令整成一個。
$ cargo run
這樣一個指令就會先完成編譯後執行,在開發的時候很方便,到這邊我們第一次成功執行了 Rust 程式,可喜可賀。
Compiling hello_world v0.1.0 (path/to/hello_world)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.53s
Running `target/debug/hello_world`
Hello, world!
除此之外 cargo
也整合了很多實用的指令,從測試、文件到發佈等,之後如果有用到再詳細介紹。
cargo test
cargo doc
cargo publish
剛好最近工作上剛開始引入 TypeScript,在用 TypeScript 建立新專案的時候,整個編譯、打包的過程因為各種設定、選擇五花八門,花了不少時間。
像是需要先根據自己的需求找到適合的打包工具(rollup, webpack…)、編譯器(tsc)或轉譯器(Babel)的排列組合,再完成各個工具的設定(config file),然後有些設定可能又會互相影響或排斥不相容,最後還要自己到package.json
定義 build
、run
指令等等,過程非常繁瑣複雜,還可能因為一些組合踩到一些坑。
相較之下 Rust 的編譯器、打包工具沒什麼選擇,cargo
也整的很乾淨了,預設值就很夠用,即使是新手也可以節省很多設定的時間,至少對我來說 Rust 在專案設定的開發者體驗是比較舒服的,不過也不排除是專案複雜度的差異啦😂