本文 AI 產出,尚未審核

Rust 課程:函數與控制流 ── 函數定義與呼叫

簡介

在任何程式語言中,**函數(function)**都是組織程式邏輯、提升可讀性與可維護性的核心工具。Rust 作為一門強調安全與效能的系統程式語言,對函數的設計也保留了高度的表達力,同時加入所有權、借用等概念,使得函數的行為更加可預測。

本單元將帶你從最基本的函數宣告與呼叫,逐步了解參數傳遞、回傳值、可變參數與匿名函式(closure),並透過實務範例說明 「函數」在 Rust 專案中的實際應用。完成本章後,你將能夠自信地撰寫乾淨、效能佳的函式,為後續的控制流與錯誤處理奠定穩固基礎。


核心概念

1. 基本函數語法

在 Rust 中,函數使用 fn 關鍵字宣告,語法如下:

fn 函式名稱(參數1: 型別, 參數2: 型別) -> 回傳型別 {
    // 函式本體
}
  • 函式名稱 必須遵循 snake_case 命名慣例。
  • 若沒有回傳值,-> 回傳型別 可以省略,等同於回傳單位型別 ()
  • 函式本體必須以分號 ; 結束的最後一個表達式作為回傳值(不需要 return),但若使用 return,則會立即結束函式。

範例 1:最簡單的「Hello, world!」函式

fn hello() {
    println!("Hello, world!");
}

呼叫方式:

fn main() {
    hello(); // 輸出: Hello, world!
}

2. 參數傳遞與所有權

Rust 的參數傳遞遵循 所有權(ownership)借用(borrowing) 規則。傳遞 String 這類擁有所有權的型別時,預設會 移動(move) 所有權;若想保留原始值,需使用參考(reference)& 或可變參考 &mut

範例 2:所有權移動與借用

fn take_ownership(s: String) {
    println!("取得所有權: {}", s);
    // s 在此函式結束後被釋放
}

fn borrow_string(s: &String) {
    println!("借用值: {}", s);
    // s 仍然屬於呼叫端
}

fn main() {
    let msg = String::from("Rust");
    take_ownership(msg);
    // println!("{}", msg); // ❌ 編譯錯誤:msg 已被移動

    let msg2 = String::from("程式設計");
    borrow_string(&msg2);
    println!("仍可使用: {}", msg2); // 正常
}

3. 回傳值與表達式

函式的最後一個表達式(不加分號)即為回傳值。若回傳多個值,可使用 元組(tuple)結構體(struct)

範例 3:回傳元組與結構體

// 回傳元組
fn min_max(a: i32, b: i32) -> (i32, i32) {
    if a < b { (a, b) } else { (b, a) }
}

// 使用結構體回傳
struct Point {
    x: f64,
    y: f64,
}

fn origin() -> Point {
    Point { x: 0.0, y: 0.0 }
}

fn main() {
    let (min, max) = min_max(10, 5);
    println!("最小值: {}, 最大值: {}", min, max); // 5, 10

    let p = origin();
    println!("原點座標: ({}, {})", p.x, p.y);
}

4. 可變參數與 mut

若函式需要修改傳入的參數,必須將參數標記為 可變參考 &mut,或直接接受可變所有權的變數(mut 變數本身)。

範例 4:使用可變參考改變向量內容

fn push_number(vec: &mut Vec<i32>, n: i32) {
    vec.push(n);
}

fn main() {
    let mut numbers = vec![1, 2, 3];
    push_number(&mut numbers, 4);
    println!("{:?}", numbers); // [1, 2, 3, 4]
}

5. 匿名函式(Closure)

Closure 是可以捕獲環境變數的匿名函式,語法類似 |參數| { 程式碼 }。它在函式作為參數或回傳值時特別有用。

範例 5:將 Closure 作為參數傳遞

fn apply<F>(f: F) -> i32
where
    F: Fn(i32) -> i32,
{
    f(10)
}

fn main() {
    let double = |x| x * 2;
    let result = apply(double);
    println!("結果: {}", result); // 20
}

常見陷阱與最佳實踐

陷阱 說明 解決方式
所有權不小心被移動 StringVec 等傳入函式後,呼叫端無法再使用。 使用 &&mut 參考,或在函式內部 clone()(視需求而定)。
忘記在最後一行加分號 若最後一行加了分號,會變成 (),導致回傳值不符。 確認回傳表達式 不加分號,或使用明確的 return
Closure 捕獲方式不明 默認捕獲方式可能是 move,導致變數所有權被移走。 明確指定 move 或使用 &&mut 捕獲,根據需求選擇。
過度使用全域變數 會破壞函式的純粹性與可測試性。 儘量把需要的資料以參數傳入,或使用結構體封裝。
未使用 Result 處理錯誤 直接 panic! 會使程式在生產環境崩潰。 讓函式回傳 Result<T, E>,在呼叫端妥善處理。

最佳實踐

  • 保持函式短小:單一職責原則(SRP),每個函式只做一件事。
  • 使用型別別名:對於複雜的回傳型別(如 Result<(i32, i32), MyError>),可用 type 讓簽名更易讀。
  • 文件化:使用 /// 註解產生 Rustdoc,說明參數意圖與回傳值。
  • 單元測試:在 #[cfg(test)] 模組中為每個公開函式寫測試,確保行為正確。

實際應用場景

  1. 資料處理管線
    在 ETL(Extract‑Transform‑Load)流程中,常會把每一步抽成獨立函式:read_csv() -> Vec<Record>filter_records(&mut records)write_json(&records)。這樣的分層讓程式碼易於平行化與錯誤追蹤。

  2. 遊戲開發的系統
    例如「計算角色傷害」的函式 fn calculate_damage(attacker: &Character, defender: &Character) -> i32,利用所有權與借用確保不會意外改變角色狀態,同時可以在測試中直接驗證數值。

  3. 嵌入式系統的 ISR(Interrupt Service Routine)
    ISR 必須是 fn,且不能捕獲環境(必須是 extern "C"),因此在 Rust 中會寫成 fn timer_isr(),裡面只呼叫已經安全封裝好的函式,避免在中斷上下文中產生未定義行為。

  4. Web 服務的路由處理
    使用框架如 actix-web 時,路由處理器本質上是函式或 Closure,例如:

    async fn hello_world() -> impl Responder {
        HttpResponse::Ok().body("Hello, Rust!")
    }
    

    這裡的 async fn 仍然遵循相同的所有權與回傳規則,只是回傳的是 Future


總結

  • 函式是 Rust 程式的基本組件,掌握 fn 的宣告、參數傳遞、回傳機制是撰寫安全、效能程式的第一步。
  • 了解 所有權與借用 在函式中的行為,可避免常見的編譯錯誤與執行時資源問題。
  • 使用 元組、結構體、Closure 等靈活的回傳方式,讓函式能夠表達更複雜的邏輯。
  • 最佳實踐(短小、單一職責、文件化、測試)與 常見陷阱(所有權移動、分號誤用、Closure 捕獲)相輔相成,幫助你寫出易讀、可維護的程式碼。

透過本章的範例與概念,你已具備在 Rust 中 定義與呼叫函式 的能力。接下來,建議將這些函式組合成更大的模組,並在「控制流」單元中學習條件判斷、迴圈與模式匹配,進一步提升程式的表達力與彈性。祝你在 Rust 的旅程中寫出更安全、更快的程式!