本文 AI 產出,尚未審核

Rust – 函式與控制流

迴圈(loopwhilefor


簡介

在任何程式語言中,迴圈都是實作重複工作、處理集合資料或等待條件變化的核心工具。Rust 以安全、零成本抽象為設計目標,提供了三種主要的迴圈語法:loopwhilefor。雖然它們在語法上看起來相似,但背後的語意與最佳使用時機卻大不相同。掌握這三種迴圈的細節,能讓你寫出 可讀性高、效能佳且不會因為忘記手動 break 而產生無限迴圈 的程式。

本篇文章針對 初學者到中級開發者,從概念說明、實作範例、常見陷阱到實務應用,逐步帶你深入了解 Rust 的迴圈機制,並提供在真實專案中常見的使用情境。


核心概念

1. loop – 無條件永遠迴圈

loop 是 Rust 最原始的迴圈形式,會一直執行直到程式內部使用 break 明確跳出。它的特點是 不需要額外的條件判斷,因此常用於:

  • 等待外部事件(例如訊號、鍵盤輸入)
  • 實作自訂的迭代器(配合 break 回傳值)
  • 需要手動控制迴圈次數(例如使用 counter 變數)
// 範例 1:使用 loop 產生 0..=9 的數字,並在第 10 次時結束
let mut i = 0;
loop {
    println!("i = {}", i);
    i += 1;
    if i > 9 {
        // 使用 break 離開迴圈
        break;
    }
}

註解break 可以帶回一個值,讓 loop 成為表達式。

// 範例 2:loop 作為表達式回傳結果
let result = loop {
    let guess = get_user_input();
    if guess == 42 {
        break guess; // 把 guess 作為 loop 的結果回傳
    }
};
println!("正確答案是 {}", result);

2. while – 條件式迴圈

while 會在每一次迭代前先評估布林條件,條件為 true 時才會執行迴圈體。它最適合用於 「只要條件成立就持續」 的情境,例如:

  • 讀取檔案直到 EOF
  • 持續輪詢某個狀態
// 範例 3:從標準輸入讀取,直到使用者輸入空字串
use std::io::{self, Write};

let mut line = String::new();
while {
    print!("請輸入文字(直接 Enter 結束): ");
    io::stdout().flush().unwrap();
    line.clear();
    io::stdin().read_line(&mut line).unwrap();
    !line.trim().is_empty()
} {
    println!("你輸入的是: {}", line.trim());
}
println!("結束輸入");

技巧while 也可以與 break 搭配,讓程式在特定條件下提前結束。

// 範例 4:使用 while + break 取得第一個符合條件的數字
let mut n = 1;
while n < 100 {
    if n % 7 == 0 && n % 5 == 0 {
        break; // 找到第一個同時被 5 與 7 整除的數字
    }
    n += 1;
}
println!("第一個同時被 5 與 7 整除的數字是 {}", n);

3. for – 迭代式迴圈(最常用)

for迭代器(Iterator) 為基礎,遍歷任何實作了 IntoIterator 的集合。它自動管理索引與邊界檢查,是 最安全、最具表現力 的迴圈方式。常見的使用情境包括:

  • 遍歷陣列、向量、字串切片
  • 使用 range 產生連續數字
  • 自訂迭代器(如 std::fs::read_dir
// 範例 5:遍歷向量並計算總和
let numbers = vec![2, 4, 6, 8, 10];
let mut sum = 0;
for n in &numbers {          // 使用 & 取得不可變參考,避免搬移所有權
    sum += n;
}
println!("總和 = {}", sum);
// 範例 6:使用 range 與 enumerate 同時取得索引與值
for (idx, value) in (0..5).enumerate() {
    println!("第 {} 次迭代,value = {}", idx, value);
}
// 範例 7:遍歷目錄中的檔案(示範自訂迭代器)
use std::fs;

for entry in fs::read_dir("./src").unwrap() {
    let entry = entry.unwrap();
    println!("檔案名稱: {}", entry.file_name().to_string_lossy());
}

重點for 迴圈在編譯期會將迭代器展開成等價的 while let 結構,不會產生額外的 runtime 開銷


常見陷阱與最佳實踐

陷阱 說明 解決方式
忘記 break loop 中忘記寫 break,導致程式卡在無限迴圈。 在開發階段加入 debug_assert! 或使用 loop { /* ... */ break; } 的結構。
迭代器搬移所有權 for item in vec 會把 vec 的所有權搬走,之後無法再使用。 使用 for item in &vec(不可變參考)或 for item in vec.iter()
範圍錯誤 for i in 0..n 產生 [0, n),容易忘記包含上界。 若需要包含上界,改用 0..=n;或使用 while 搭配手動條件。
while 中改變條件變數 在迴圈體內部忘記更新條件變數,造成無限迴圈。 確保每次迭代都有 明確且可預測 的變數變化。
過度使用 loop 把所有迴圈都寫成 loop,失去語意清晰度。 優先考慮 for(集合遍歷)或 while(條件迭代),僅在需要手動控制時才使用 loop

最佳實踐

  1. 盡量使用 for:它最能表達「遍歷」的意圖,同時保證安全性。
  2. 使用標籤 ('label) 與 break/continue:在多層迴圈中需要跳出特定層級時,使用標籤可以避免混亂。
    'outer: for i in 0..5 {
        for j in 0..5 {
            if i + j > 6 {
                break 'outer; // 直接跳出外層迴圈
            }
        }
    }
    
  3. 利用迭代器的高階方法:如 filtermapfold,往往可以把 for 迴圈寫成更簡潔的表達式。
  4. while 中使用 while let:配合 OptionResult,可以寫出「只要有值就持續」的迴圈,避免手動檢查 is_some()
    let mut iter = some_vec.iter();
    while let Some(v) = iter.next() {
        println!("{}", v);
    }
    

實際應用場景

  1. 網路伺服器的事件迴圈
    使用 loop 搭配 select!(或 tokio::select!)持續等待 I/O 事件,直到收到關閉訊號才 break

  2. 資料流的批次處理
    讀取大型檔案時,while let Some(line) = lines.next() 可以逐行處理,避免一次載入全部記憶體。

  3. 遊戲開發中的帧更新
    loop 內部執行 update()render(),每次迭代結束後檢查是否收到退出指令。

  4. 統計分析
    for 搭配 enumeratezip,同時遍歷多個向量,計算相關係數或交叉統計。

  5. 自訂迭代器
    實作 Iterator trait 時,next() 方法內部往往使用 loop 來跳過不符合條件的元素,直到找到下一個可返回的值。


總結

  • loop:最原始、無條件的迴圈,適合需要手動控制跳出的情境;可作為表達式回傳值。
  • while:先判斷條件再執行,適合「只要條件成立就持續」的場景,常與 breakwhile let 結合。
  • for:基於迭代器的高階迴圈,安全、零成本,幾乎是所有集合遍歷的首選。

在實務開發中,選擇最能表達意圖的迴圈類型,配合標籤、迭代器方法與適當的錯誤處理,能寫出既簡潔又可靠的 Rust 程式。希望本篇文章能幫助你在日常開發裡,快速判斷何時使用 loopwhilefor,並避免常見的陷阱,寫出高品質的程式碼。祝你在 Rust 的旅程中,迴圈不再是難題,而是得心應手的工具!