Rust – 函式與控制流
迴圈(loop、while、for)
簡介
在任何程式語言中,迴圈都是實作重複工作、處理集合資料或等待條件變化的核心工具。Rust 以安全、零成本抽象為設計目標,提供了三種主要的迴圈語法:loop、while 與 for。雖然它們在語法上看起來相似,但背後的語意與最佳使用時機卻大不相同。掌握這三種迴圈的細節,能讓你寫出 可讀性高、效能佳且不會因為忘記手動 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。 |
最佳實踐:
- 盡量使用
for:它最能表達「遍歷」的意圖,同時保證安全性。 - 使用標籤 (
'label) 與break/continue:在多層迴圈中需要跳出特定層級時,使用標籤可以避免混亂。'outer: for i in 0..5 { for j in 0..5 { if i + j > 6 { break 'outer; // 直接跳出外層迴圈 } } } - 利用迭代器的高階方法:如
filter、map、fold,往往可以把for迴圈寫成更簡潔的表達式。 - 在
while中使用while let:配合Option或Result,可以寫出「只要有值就持續」的迴圈,避免手動檢查is_some()。let mut iter = some_vec.iter(); while let Some(v) = iter.next() { println!("{}", v); }
實際應用場景
網路伺服器的事件迴圈
使用loop搭配select!(或tokio::select!)持續等待 I/O 事件,直到收到關閉訊號才break。資料流的批次處理
讀取大型檔案時,while let Some(line) = lines.next()可以逐行處理,避免一次載入全部記憶體。遊戲開發中的帧更新
loop內部執行update()、render(),每次迭代結束後檢查是否收到退出指令。統計分析
for搭配enumerate或zip,同時遍歷多個向量,計算相關係數或交叉統計。自訂迭代器
實作Iteratortrait 時,next()方法內部往往使用loop來跳過不符合條件的元素,直到找到下一個可返回的值。
總結
loop:最原始、無條件的迴圈,適合需要手動控制跳出的情境;可作為表達式回傳值。while:先判斷條件再執行,適合「只要條件成立就持續」的場景,常與break、while let結合。for:基於迭代器的高階迴圈,安全、零成本,幾乎是所有集合遍歷的首選。
在實務開發中,選擇最能表達意圖的迴圈類型,配合標籤、迭代器方法與適當的錯誤處理,能寫出既簡潔又可靠的 Rust 程式。希望本篇文章能幫助你在日常開發裡,快速判斷何時使用 loop、while 或 for,並避免常見的陷阱,寫出高品質的程式碼。祝你在 Rust 的旅程中,迴圈不再是難題,而是得心應手的工具!