ETH官方钱包

前往
大廳
主題 達人專欄

Rust:令人沉迷於自虐的程式語言

解凍豬腳 | 2024-06-28 19:15:03 | 巴幣 5502 | 人氣 1142

 
本篇文章有不少程式碼,如果這裡的排版讓你感到閱讀困難,請服用 HackMD 好讀版



開個新坑吧。

這系列來分享一個最近幾年變得特別熱門的程式語言:Rust。

其實 Rust 語言早在 2010 年就已經問世了,最早是基於「作為著重記憶體安全性和性能的系統級語言」而生,由 Mozilla 社群(就是開發、維護 Firefox 瀏覽器的那個 Mozilla)所主導。

多年來,許多程式的安全性漏洞都是基於記憶體管理不當而發生,這樣的議題隨著資訊技術的發達而越來越受到開發者重視,再加上近年 Rust 的生態日趨完善,它的優勢也就逐漸被開發者注意並走入大眾視野。直到 2020 年代,Android、Windows、Linux 的開發人員甚至都開始嘗試使用 Rust 撰寫這些專案的部分程式碼,雖然要完全替換是不太可能,但若要說 Rust 是 C++ 的下一代接班人,肯定也不為過。



? 為什麼學習 Rust?

若要問起為什麼我會想學 Rust,原因很單純:聽說它很難。

本來我只是想要看看一門程式語言可以自虐到什麼地步,等到實際用它投入開發的時候,才真正體悟到為何它如此龜毛,卻又能夠如此受到歡迎──若要用簡單的描述概括,它無論是語言本身的設計還是編譯器的規則,都可以說是卯足全力阻止你寫出爛程式碼,迫使你寫出考慮周密而穩定的程式。在接觸 Rust 幾個月以後,身為完美主義者的我也成為那眾多愛上 Rust 的開發者之一了。

我曾接觸過各種不同類型的語言,其中 Rust 語言對我既有的觀念帶來的顛覆是以前未曾感受過的。即便你沒有使用 Rust 作為主要語言的打算,我也會強烈推薦你投入時間學習它、瞭解它。僅僅是試圖用它重寫一些簡單的專案,你也會因此開始注意許多以前你從來不在乎的細節,學習 Rust 所帶來的收益會很容易反映在你所有的專案上。

為了不讓篇幅過長,關於「Rust 有多嚴格」這件事情,以及這些設計帶來的好處,就等之後的章節讓我一點一點透過實際的例子來體現吧。

如果你還沒學過 Rust 也不用因為聽說很困難而卻步。得益於 ChatGPT 等 AI 模型的技術發展,現在我們可以利用這些語言模型作為學習 Rust 時的輔助工具,這語言也就沒有想像中那麼變態了。



? 準備你的開發環境和專案

不免俗的,我們總要從最簡單的部分開始,就像我們學習其他任何的程式語言一樣。

首先到 Rust-lang 官方網站安裝 rustup-init,你會因此得到兩樣東西:
  • Rustup:用來管理你的 Rust 工具鏈(像是 Rust 編譯器)版本
  • Cargo:用來管理你的專案和依賴項目

對於大部分的使用者來說,rustup 除了查看或升級 Rust 版本以外,沒有其他作用了。在使用 Rust 開發專案的時候,使用最多的應該會是 cargo 工具。

安裝好以後,首先打開任一 terminal(例如 CMD 或 PowerShell),用 cd 指令到達你想要建立專案的地方,接著執行命令:
cargo init test_project

這樣你就在資料夾底下建立了一個名字叫做「test_project」的專案。如果系統沒有辦法找到 cargo,那有可能是你的 terminal 沒有讀取到安裝檔剛更新上去的環境變數,通常只要把帶有 terminal 的程式(例如 Windows Terminal 或 VS Code)整個重開就能解決了,再不行的話可以試試重開機。

建立好專案以後,使用 VS Code 的「Open Folder(開啟資料夾)」把剛才創立的 test_project 資料夾打開,這時候使用 [Ctrl] + [~] 快捷鍵打開 terminal,你應該會看見 terminal 的當前資料夾是落在「x:/xxxx/test_project」這個目錄底下,而不是「x:/xxxx」或「x:/xxxx/test_project/src」。

接著,使用快捷鍵 Ctrl+Shift+X 或是從左邊的選項找到 extensions 頁籤,在搜尋欄搜尋並安裝這兩樣東西:
  • Even Better TOML
  • rust-analyzer

前者能夠給 cargo.toml 依照格式上色,提高可讀性,後者能夠即時對程式碼靜態分析,找出編譯錯誤或警告,對 Rust 專案來說是不可或缺的工具。安裝好這些東西以後,你基本上已經把環境都設定好了。

在你的專案資料夾底下,預設會有這些東西:
  • src:你的程式碼
  • target:編譯出來的執行檔或過程遺留下來的暫存檔,這些檔案留著可以改善下次該專案的編譯速度,不需要的時候可以刪除
  • .gitignore:給 git 工具使用的忽略清單,預設忽略 target 資料夾
  • Cargo.lock:用來給 Cargo 工具檢查依賴函式庫的校驗資訊,因為這是給自動化工具而不是人類用的,切勿更動
  • Cargo.toml:專案的附加資訊,包括專案依賴的函式庫名稱、版本、啟用的功能等

根據我的經驗和理解,因為 Rust 的專案是基於 LLVM 來編譯,過程會採用很多複雜的最佳化策略,所以多次編譯下來產生出來的暫存檔很大,編譯速度也普遍比較慢。因此,如果可以的話,我會建議盡量把專案放在 SSD 上面,且不要把它和 Dropbox 之類的雲端硬碟同步,不然你的雲端空間大概很快就爆了。假如是想要創建一個能推到 GitHub repo 的專案,你可以先在 GitHub 上面創設一個 repo,用 git 工具把它 clone 下來,然後再從 terminal 進入剛剛 clone 下來的 repo 資料夾,執行「cargo init」來創建專案。



? Hello, world!

接下來就可以測試你的專案了。沒有意外的話,Cargo 會自動幫你產生一個 Hello, world! 的專案:
// src/main.rs
fn main() {
    println!("Hello, world!");
}

如果想要編譯並執行你的程式,在 terminal 執行:
cargo run

如果想要編譯成執行檔,則用 build 命令:
cargo build

你的執行檔就會出現在 target/debug/ 底下。

不過,單單呼叫 cargo build 的話,編譯器是不會套用所有優化策略的。如果程式已經確定完成了、需要實際發行或投入使用,你可以加上 release tag,讓編譯器知道你需要把成品最佳化:
cargo build --release

經過最佳化編譯的執行檔就會出現在 target/release/ 底下了。



? Rust 語言的巨集

既然基本的流程我們已經搞懂,就可以回頭來專注在程式碼上面。這個時候遇到的第一個問題是:「為什麼 println 後面要加上驚嘆號?」

這就要說到 Rust 本身的特性和它的巨集功能。
(註:macro,簡體中文圈一般習慣稱之為「宏」而不是「巨集」。由於簡體中文的資料比較多,你也許會時常看見「宏」這個稱呼)

如果你曾寫過 C 語言的話,應該對於 #define 有些印象:
#define MAX(x, y) (x)>(y) ? (x):(y)

我們可以把某些簡單的行為寫成巨集而不是函式,編譯器會在編譯時自動地把這些巨集和其內容視為等價的語法。

也就是說,當我們在 C 語言當中這麼寫:
#include <stdio.h>
#define MAX(x, y) (x)>(y) ? (x):(y)

int main() {
    int a = 3;
    int b = 5;
    printf("The bigger one has value: %d\n", MAX(a, b));
    return 0;
}

編譯器會先暗自把 main 的內容轉換為:
int main() {
    int a = 3;
    int b = 5;
    printf("The bigger one has value: %d\n", (a)>(b) ? (a):(b));
    return 0;
}

然後才開始編譯流程。實際上這個行為比較接近文本的替換而不是函數的呼叫,在某些情況下具有性能優勢。

通常情況下,print 函數裡可能會有好幾個不同的參數,且參數的數量多寡並不一定。然而,Rust 的原則正是希望「凡事都能在編譯期確定」,所以 Rust 的函數在設計之初,就不能像其他語言一樣把函數直接寫成可變數量參數的形式。若是單單為了一個 print 而動用 array、vector 之類的複雜型態來實現這個功能,則更是本末倒置。

為了讓程式碼具備強大的擴充性,Rust 引入了強大的巨集功能,這所謂的巨集就像剛才提到 C 語言的 #define 一樣,而 Rust 正是使用巨集功能來實現基本的 println。在 Rust 語法當中,名稱尾端的驚嘆號表示這是一個巨集的名稱,也就是說,你呼叫的是一個名為 println 的巨集,而不是名為 println 的函數。

比方說,我們想自行定義一個 add!(x, y) 的巨集,使用起來就會像這樣:
macro_rules! add {
    ($x:expr, $y:expr) => {
        $x + $y
    };
}

fn main() {
    let result = add!(123, 456);
    println!("Result: {result}");
}

在範例程式碼當中,編譯器就會在編譯時自動把 add!(123, 456) 展開,變成 123+456,然後才繼續編譯。實際上 macro 可以做到的事情遠遠比這個更多(包括定義結構體、函數,甚至是更複雜的型態轉換),而且也同樣可以透過 rust-analyzer 即時找出會引起編譯錯誤的問題。關於這部分,就等之後的章節再獨立拿出來談吧。

至此,你已經準備好了開發環境,也學會了編譯 Rust 的 Hello, world! 範例程式。



HackMD 好讀版:https://hackmd.io/@upk1997/rust-hello-world

縮圖素材原作者:Karen Rustad T?lva(CC0 1.0)
送禮物贊助創作者 !
10
留言

創作回應

? 愛德莉雅.萊茵斯提爾向創作者進行贊助 ?
豬腳辛苦了諾~ヽ(●′?`●)?
2024-06-28 19:42:54
解凍豬腳
謝謝小精靈 https://im.bahamut.com.tw/sticker/827/07.png
2024-06-28 20:25:12
人類新手
RUST現在支援的套件多嗎?
2024-06-28 19:58:09
解凍豬腳
我目前涉獵比較多的是 web 服務相關
web 服務有 axum、Rocket、Actix Web 這三大後端框架可以選
近年因為流行 wasm,甚至有出現使用 Rust 寫前端的嘗試(Yew)

至於 GUI 框架有聽說 Tauri 不錯的樣子
不過因為個人專案還有其他的東西待開發,我還沒試過
2024-06-28 20:37:58
雞塊
推推 最近也剛好在學 rust 
2024-06-29 03:08:09
解凍豬腳
其實這系列算是我發在場外的 Rust 串,修飾之後重發
如果有需要的話可以看這裡:
https://forum.gamer.com.tw/C.php?bsn=60076&snA=7929963
2024-06-29 11:10:38
子魄
推一個,最近前端js跟ts寫多了,想摸摸其他的
2024-06-29 16:23:15
解凍豬腳
要用 Rust 寫前端的話
Yew 現在社群龐大但 1.0 版本還沒出,需要時間觀察
這方面可以先連同 WASM 的應用一起關注看看
2024-06-29 16:28:27
熊大
為了不要讓別人發現我看不懂,先按讚好了
2024-06-30 18:19:47
解凍豬腳
[e12]
2024-07-02 17:56:07

更多創作