iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0
Software Development

螃蟹幼幼班:Rust 入門指南系列 第 2

Day2 - Hello world! Hello cargo!

  • 分享至 

  • xImage
  •  

事前補充

因為 Rust 是編譯型語言,在今天的主題之前需要先了解一下編譯(Compile)和建構(Build)。
編譯型語言需要先把源碼(人看的)轉換成機器碼(機器看的)才能直接執行。而編譯和建構會負責處理這個過程,兩者在含義上略有差異。

編譯

指的是將一種語言寫成的程式碼轉換成機器可以直接執行的機器碼或位元組碼的過程,主要任務包含:

  • 語法檢查:確保程式碼符合語言的語法規則。
  • 類型檢查:確認變數、函數的類型是否匹配。
  • 優化:改善程式碼的執行效率。
  • 生成目標碼:將程式碼轉換成目標平台(如x86、ARM)可以執行的機器碼。

建構

指的是將一個軟體專案的所有源碼、資源、庫等整合在一起,生成最終的可執行檔案或可部署的軟體包的過程,所以包含但不限於編譯,主要步驟包含:

  • 編譯:將源碼編譯成目標碼。
  • 鏈接:將編譯生成的目標碼與庫文件鏈接在一起,生成可執行檔案。
  • 打包:將可執行檔案、資源、配置文件等打包成一個可部署的軟體包。

簡單的說,編譯專門指把源碼轉換成機器碼:建構則包含編譯、鏈接與打包等多個步驟。

回到主題

接著今天來介紹安裝完 Rust 相關的環境後的 3 個主要的工具:rustuprustccargo,並且不免俗的來 hello world 一下。

rustup

rustup 是用來管理 Rust 工具鍊的工具,所謂的工具鍊是指一組協同工作來完成軟體開發的各個階段的工具,從編寫程式碼、套件管理到編譯、測試等,以 Nodejs 來比喻就像是 nvm ,可以利用它來做版本切換或安裝不同版本的工具鍊,像是 Rust 的不同版本(stable、beta等)。我們在上一篇已經有用到一些 rustup 的指令。

rustc

rustc 則是 Rust 的官方編譯器,是用來將 Rust 程式碼編譯成可執行文件或庫的核心工具,雖然也有一些實驗性專案想要發展其他編譯器,但因為可能存在兼容性或性能問題還沒成為主流,現階段來說很單純 rustc 是最穩定、最可靠的選擇。

rusts 最主要的職責包含:

  • 編譯:
    將Rust 源代碼編譯成二進制可執行文件或靜態、動態庫。

  • 保證記憶體安全:
    編譯器在編譯過程中會進行嚴格的所有權和借用檢查,確保記憶體安全,避免 memory leak 或空指標的狀況進而造成程式執行中出現非預期行為或崩潰等等。

    記憶體安全簡單來說指的是:

    • 程式不會嘗試存取或修改不屬於它的記憶體區域。
    • 程式不會重複釋放同一個記憶體區域。
    • 程式不會使用已經被釋放的記憶體。
  • 錯誤檢查:
    提供詳細的錯誤和警告信息以及提示,幫助開發者識別和修復問題。

Rust 的記憶體安全基本上是建立在編譯過程嚴格的檢查,代表 Rust 有很大部分的價值是建立在 rustc這個編譯器上,很明顯它是 Rust 的核心部分。

rustc 最簡易的編譯指令如下:

$ rustc main.rs

執行後預設會產生一個可以執行的檔案,在 Lilux 和 masOS 中為main;在 Windows 中為 main.exe

rustc 提供了豐富的設定選項,讓開發者能夠精準地控制編譯過程。這些設定會影響編譯器的行為,例如:

  • 輸出格式:生成可執行文件、動態庫、靜態庫等。
  • 優化等級:控制編譯器對程式碼進行優化的程度。
  • 目標平台:指定編譯的目標平台(例如 x86_64、arm)。
  • 特性啟用與禁用:控制編譯器是否支持特定的語言特性。

這些設定可以從命令行參數與配置文件設定,不過初階的部分其實我們都用不到這些,就不多做研究。

cargo

最後 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的時候,背後有好幾個動作:

  • 分析 Cargo.toml:cargo 會根據這個文件來確定哪些檔案需要編譯,以及需要鏈接哪些庫。
  • 下載依賴:如果專案有外部依賴,cargo 會自動下載並安裝這些依賴。
  • 呼叫 rustc: cargo 會將 rustc 作為子程序調用,並傳遞給它一系列參數。這些參數包含了編譯所需的源文件、輸出目錄、優化級別等資訊。
  • rustc 編譯:接收到這些參數後,rustc 會開始進行編譯工作,包括前述的細節。
  • 鏈接:如果是生成可執行文件,rustc 還會將生成的目標碼與標準庫和其他鏈接的庫進行鏈接,生成最終的可執行文件。
    所以底層編譯其實還是由 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定義 buildrun指令等等,過程非常繁瑣複雜,還可能因為一些組合踩到一些坑。
相較之下 Rust 的編譯器、打包工具沒什麼選擇,cargo也整的很乾淨了,預設值就很夠用,即使是新手也可以節省很多設定的時間,至少對我來說 Rust 在專案設定的開發者體驗是比較舒服的,不過也不排除是專案複雜度的差異啦😂


上一篇
Day1 - 序言:Why Rust
下一篇
Day3 - 從 Echo Function 接觸基本概念
系列文
螃蟹幼幼班:Rust 入門指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言