seansie's blog

Ch3:控制流與解構術 — 邏輯的對稱之美

· Sean Sie

條件判斷:不僅是判斷,還是表達式

在 Rust,if-else 不是單純的語句(Statement),它是一種可以回傳值的表達式(Expression)。這意味著你可以直接把 if 的結果賦值給變數。

let condition = true;  
let x = if condition { "成功" } else { "失敗" };

⚡ 進階講解

  1. 分號是關鍵:括號內最後一行如果不加分號,它就是回傳值。如果加了分號,它就變成語句,回傳的是空元組 ()。
  2. 型態必須一致:Rust 是強型態語言,if 分支回傳字串,else 分支就不能回傳整數。編譯器必須在編譯時期就知道 x 的精確型態。
  3. 取代三元運算子:Rust 沒有 a ? b : c 這種語法,因為 if 表達式已經完美取代了它,且可讀性更高。

迴圈:三種武器

Rust 提供了三種迴圈,每一種都有其獨特的應用場景。

1. while:最傳統的守衛

當你需要先判斷條件再執行時使用。但在 Rust 中,while 比較少用來遍歷陣列,因為 for 更安全且更快(少了邊界檢查的開銷)。

2. loop:無限的可能

loop 是最純粹的無限迴圈。它最酷的地方在於可以配合 break 回傳值。這在需要重試某些操作(如網路連接或獲取鎖)直到成功為止的場景非常強大。

let mut counter = 0;  
let result = loop {  
    counter += 1;  
    if counter == 10 {  
        break counter * 2; // 跳出迴圈並帶走數值 20  
    }  
};

💡 祕技:迴圈標籤 (Loop Labels)

當你有巢狀迴圈時,可以用標籤來指名要跳出哪一層:

'outer: loop {  
    loop {  
        if some_condition { break 'outer; } // 直接殺出重圍跳出最外層  
    }  
}

3. for:最推薦的戰力

這是 Rust 最常用的迴圈,通常搭配迭代器。它不會發生「索引越界」的慘劇。

  • 1..3:左閉右開,產生 1, 2。
  • 1..=3:全閉區間,產生 1, 2, 3。

解構 (Destructuring):九字箴言

解構是 Rust 的精華,心法只有一句:「怎麼建立,就怎麼解開。」 當你定義了一個結構,解構就是把裡面的組件拆散開來,直接賦值給新變數。

資料型態 建立方式 解構方式 核心概念
Array [1, 2, 3] let [x, y, z] = arr; 順序與數量必須精確匹配
Tuple (1, "a") let (x, y) = t; 支援不同型態的一口氣拆解
Struct Point { x, y } let Point { x, y } = p; 依據欄位名稱拆解,順序不重要

⚡ 花式解構技巧

  • ..:代表「剩下的我不管」。例如 let [first, ..] = big_array;
  • _:忽略特定位置的變數,不想取名時好用。
  • @綁定運算子。這能在解構的同時進行條件判斷並存入變數。
  let msg = 15;  
  match msg {  
      id @ 10..=20 => println!("ID 在範圍內:{}", id),  
      _ => println!("在範圍外"),  
  }

Match:窮舉性判斷

match 是 Rust 的殺手級功能。它不僅是 switch-case 的強化版,還具有**窮舉性(Exhaustiveness)**檢查。編譯器會盯著你,確保你考慮了所有的可能性。

match score {  
    90..=100 => 'S',  
    80..90 => 'A',  
    _ => 'F', // 萬用牌,處理剩下的所有情況  
}

⚡ 進階:Match Guard (守衛條件)

你可以在匹配分支中加入 if 條件,這讓邏輯判斷更有彈性:

match pair {  
    (x, y) if x == y => println!("兩數相等"),  
    (x, y) if x + y == 0 => println!("互為相反數"),  
    _ => println!("平凡的組合"),  
}

if let / while let:精簡的藝術

當你只關心 Enum 中的某一種情況時,寫 match 顯得太囉唆,這時就是 if let 的主場。

// 如果是 Some,就解構出內部的 x,否則直接跳過  
if let Some(x) = foo_opt {  
    println!("拿到了:{}", x);  
}

while let:連續解構

這常用在不斷從容器中取出資料直到空的為止:

let mut stack = vec![1, 2, 3];  
while let Some(top) = stack.pop() {  
    println!("彈出:{}", top);  
}

問號運算子 (?):錯誤處理的電梯

Rust 不使用 try-catch 拋出例外,而是回傳 Result。? 運算子就像是一部自動電梯,幫你處理錯誤的向上傳播(Propagation)。

let file = File::open("test.txt")?; // 如果失敗,直接從當前函數 Return Err;如果成功,直接解開賦值。

這能讓你的代碼保持平整,避免出現地獄般的巢狀 match。

函數 (Function)

  • fn 宣告。參數必須標註型態,這讓 Rust 能在編譯期進行嚴格檢查。
  • 表達式回傳:最後一行不加分號直接回傳。
  • ! (Never Type):這是一個特殊的型態,代表「這個函數永遠不會結束」。常見於無限迴圈的伺服器、REPL 或者會觸發 panic! 的崩潰函數。
  fn dead_end() -> ! {  
      panic!("此路不通");  
  }

const fn:編譯期執行

如果你在函數前加上 const,這代表這個函數可以在編譯期間就被執行並算出結果。這對於效能優化非常有幫助,因為它把執行期的工作提前到了編譯期。

const fn add(a: i32, b: i32) -> i32 { a + b }  
const RESULT: i32 = add(5, 10); // 編譯完這行就是 15,執行時零開銷