ข้ามไปยังเนื้อหา

Control Flow

ใน Rust if คือ expression — ประเมินออกมาเป็นค่า คุณจึงใช้ if ทางขวามือของ let binding ได้ ทุก branch ต้องผลิตค่าที่มี type เดียวกัน

fn main() {
let score = 75;
let grade = if score >= 90 { "A" } else if score >= 75 { "B" } else { "C" };
println!("grade = {}", grade);
}

ไม่จำเป็นต้องมี parentheses รอบ condition และต้องมี curly brace รอบ body เสมอ

Rust ไม่มีแนวคิด “truthy” เลย — ไม่มีกฎภาษาไหนที่เปลี่ยนตัวเลขที่ไม่ใช่ศูนย์, string ที่ไม่ว่าง หรือ pointer ที่ไม่ null ให้เป็น true condition ของ if ต้องเป็น bool จริงๆ ไม่งั้นคอมไพเลอร์ปฏิเสธก่อนที่จะพิจารณาด้วยซ้ำว่า branch return อะไร:

// @expect-error E0308
fn main() {
let x = 5;
if x {
println!("nonzero");
}
}
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if x {
| ^ expected `bool`, found integer
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.

ทางแก้เสมอคือ comparison จริง เช่น if x != 0 นี่คือ E0308 ตัวเดียวกับที่คุณเจอมาแล้วสำหรับ assignment และ return ที่ mismatch — condition ของ if ก็แค่อีกจุดหนึ่งที่คอมไพเลอร์เช็ค type กับสิ่งที่คาดหวัง

แยกจาก condition, body ของ if และ else คือสอง expression ที่ต้องผลิต type เดียวกัน เพราะ if ทั้งก้อนต้องแปลงเป็น type เดียวเมื่อถูกใช้เป็นค่า:

// @expect-error E0308
fn main() {
let x = 5;
let y = if x > 0 { "positive" } else { 0 };
println!("{}", y);
}
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:4:44
|
4 | let y = if x > 0 { "positive" } else { 0 };
| ---------- ^ expected `&str`, found integer
| |
| expected because of this
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.

if ที่ใช้เป็น statement เท่านั้น (ค่าถูกทิ้ง) ไม่มีข้อจำกัดนี้ — แต่ละ branch เป็น () ได้อย่างอิสระจากกัน ซึ่งคือรูปแบบของตัวอย่างถัดไปพอดี

เมื่อไม่ต้องการผลลัพธ์ ใช้ if แบบ statement:

fn main() {
let n = 7;
if n % 2 == 0 {
println!("even");
} else {
println!("odd");
}
}

loop รัน body ของตัวเองไม่หยุดจนกว่าจะพบ break อย่างชัดเจน ต่างจากภาษาอื่น loop ของ Rust สามารถ return ค่า ผ่าน break ได้

fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 5 {
break counter * 2; // the loop evaluates to this value
}
};
println!("result = {}", result); // 10
}

เมื่อ loop ซ้อนกัน break หรือ continue เปล่าจะมีผลต่อ loop ชั้นในสุดเท่านั้น Label (เขียน 'name: ก่อน loop) ให้คุณเล็งไปที่ loop ชั้นนอกได้โดยตรง:

fn main() {
let mut found = None;
'outer: for i in 0..5 {
for j in 0..5 {
if i * j == 6 {
found = Some((i, j));
break 'outer;
}
}
}
println!("found = {:?}", found);
}

break 'outer; ออกจากทั้งสอง loop ทันทีเมื่อเจอค่าที่ตรงกัน — ถ้าไม่มี label break เปล่าจะหยุดแค่ for ชั้นในเท่านั้น loop ชั้นนอกจะยังทำงานต่อ

while วนซ้ำตราบเท่าที่ condition เป็น true ไม่สามารถ return ค่าผ่าน break ได้

fn main() {
let mut n = 1;
while n < 4 {
print!("{} ", n);
n += 1;
}
println!(); // newline
}

while let รวม while กับ pattern matching เข้าด้วยกัน: วนซ้ำไปเรื่อยๆ ตราบเท่าที่ค่านั้น match กับ pattern ที่กำหนด และหยุดทันทีที่ไม่ match — รูปแบบที่พบบ่อยสำหรับการดึงข้อมูลออกจาก collection จนหมด:

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

.pop() return Option<i32> — Some(value) ตราบเท่าที่ Vec ยังมี element เหลือ, None เมื่อว่างแล้ว loop condition คือ pattern match นั่นเอง: มันรัน body ตราบเท่าที่ pattern match กับ Some(_) และออกทันทีที่ .pop() return None

for คือ loop ที่ใช้บ่อยที่สุดใน Rust และวนผ่าน type ใดก็ได้ที่ implement Iterator trait รวมถึง range และ array

fn main() {
// Exclusive range: 1, 2, 3
for i in 1..4 {
print!("{} ", i);
}
println!();
// Inclusive range: 1, 2, 3, 4, 5
for i in 1..=5 {
print!("{} ", i);
}
println!();
// Iterating over an array
let fruits = ["apple", "banana", "cherry"];
for fruit in fruits {
print!("{} ", fruit);
}
println!();
}

ควรใช้ for แทน while ที่มี index manual เสมอเมื่อเป็นไปได้ เพราะปลอดภัยกว่าและแสดงเจตนาได้ชัดเจนกว่า

match เทียบค่ากับชุด pattern แล้วรัน arm แรกที่ตรง — pattern เป็นได้ทั้งเลขตรงๆ หรือ range แบบ inclusive พลังเต็มของ match (enum, struct, guard, binding) อยู่ในโมดูล Structs, Enums & Matching นี่แค่พอให้อ่าน dispatch logic ง่ายๆ ได้:

fn main() {
let score = 82;
let grade = match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
_ => "F",
};
println!("grade = {}", grade);
}

ต่างจาก chain ของ if/else if, match บนชุด pattern ที่ exhaustive จะถูกคอมไพเลอร์ตรวจสอบความครบถ้วนให้ — arm _ => "F" ท้ายสุดตรงนี้คือสิ่งที่ทำให้ match นี้ exhaustive โมดูลถัดไปจะพูดถึงว่าเกิดอะไรขึ้น (และคอมไพเลอร์บอกอะไร) เมื่อคุณไม่ใส่มัน

let else bind pattern เหมือน let ปกติ แต่ถ้า pattern ไม่ match block else จะรันแทน — และ block นั้นต้อง diverge (return, break, continue, หรือ panic!) เพราะส่วนที่เหลือของฟังก์ชันสมมติว่า pattern match สำเร็จแล้ว:

fn parse_or_default(input: &str) -> i32 {
let Ok(n) = input.parse::<i32>() else {
return 0;
};
n * 2
}
fn main() {
println!("{}", parse_or_default("21")); // 42
println!("{}", parse_or_default("nope")); // 0
}

เทียบกับ while let ด้านบน: ทั้งคู่ดึงค่าออกจาก pattern แต่ while let วนซ้ำตราบเท่า pattern ยัง match ในขณะที่ let else รันครั้งเดียวและต้องการให้ pattern match ทันที พร้อม escape hatch ชัดเจนเมื่อไม่ match

ตั้งแต่ edition 2024 คุณรวม let pattern match หลายตัวกับ boolean condition ด้วย && ได้โดยตรงภายใน if (หรือ while) — ไม่ต้อง nest if let เลย:

fn main() {
let a: Option<i32> = Some(5);
if let Some(x) = a && x > 0 {
println!("positive: {}", x);
} else {
println!("not positive");
}
}

นี่เป็น feature เฉพาะ edition 2024 การคอมไพล์ if let Some(x) = a && x > 0 ตัวเดียวกันเป๊ะภายใต้ edition 2021 ถูกปฏิเสธทันที ด้วยข้อความที่ระบุ edition requirement ตรงๆ:

error: let chains are only allowed in Rust 2024 or later
--> src/main.rs:3:8
|
3 | if let Some(x) = a && x > 0 {
| ^^^^^^^^^^^^^^^

ก่อนมี let chains logic เดียวกันต้องใช้ if let ซ้อนกันพร้อม condition เพิ่มข้างใน (if let Some(x) = a { if x > 0 { ... } }) หรือใช้ matches! ผสม — ทั้งคู่ nest ลึกกว่ารุ่น 2024 ด้านบนอย่างเห็นได้ชัด

fn main() {
// if as an expression
let score = 75;
let grade = if score >= 90 { "A" } else if score >= 75 { "B" } else { "C" };
println!("grade = {}", grade);
// loop returning a value via break
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 5 {
break counter * 2;
}
};
println!("loop result = {}", result);
// while loop
let mut n = 1;
while n < 4 {
print!("{} ", n);
n += 1;
}
println!();
// for with a range
for i in 1..=3 {
print!("{} ", i);
}
println!();
// for over an array
let nums = [10, 20, 30];
for val in nums {
print!("{} ", val);
}
println!();
}
flowchart TD
  Start{"what are you
looping over?"}
  Start -->|"a collection or range"| For["for item in collection"]
  Start -->|"a condition, no known count"| While["while condition"]
  Start -->|"a pattern that may stop matching"| WhileLet["while let Some(x) = ..."]
  Start -->|"unknown iterations,
need a value out of the loop"| Loop["loop { ... break value; }"]
  For --> Body["loop body"]
  While --> Body
  WhileLet --> Body
  Loop --> Body
  Body -->|"nested loop, need to exit an outer one"| Label["label the outer loop:
'outer: for/while/loop"]
เลือก loop construct

ทุก control-flow construct ในบทนี้จริงๆ กำลังตอบคำถามเดียว: “จาก condition หรือ pattern นี้ block โค้ดไหนจะรันต่อ และมันผลิตค่าที่ฉันสนใจไหม?” if และ match ตอบครั้งเดียว จาก condition หรือ pattern แล้วผลิตค่าออกมาตรงๆ ได้ loop, while, for ตอบซ้ำๆ — loop เท่านั้นที่ส่งค่ากลับออกมาได้ (ผ่าน break value) เพราะ while กับ for ถูกสร้างมาให้หยุดเมื่อ condition เป็นเท็จ ไม่ใช่เมื่อการคำนวณ เสร็จ ซึ่งเป็นเหตุผลที่ต่างกันในการหยุด

การทำให้ if และ match เป็น expression เต็มรูปแบบ (แทนที่จะเป็น statement เหมือน C หรือ Java) ตัดโค้ด boilerplate หมวด “ประกาศตัวแปรด้านบน กำหนดค่าในทุก branch ด้านล่าง” ออกไปทั้งหมด — แลกกับกฎที่เข้มงวดขึ้น: ทุก branch ต้องเห็นพ้อง type เดียวกัน ซึ่งคือสิ่งที่คอมไพเลอร์บังคับด้วย E0308 เมื่อไม่ตรงกัน Let chains แลก if let ... && condition ที่แบนราบและ nest น้อยกว่า กับการเป็น feature เฉพาะ edition 2024 — โค้ดที่ต้องคอมไพล์บน edition เก่าด้วย (หรือ MSRV ที่มาก่อน 2024 stabilize) ยังใช้มันไม่ได้

  • “ค่าที่ไม่ใช่ bool ใช้เป็น condition ได้ เหมือน 0/1 ใน C หรือ truthy/falsy ใน Python และ JavaScript” Rust ไม่มี truthiness coercion เลย — condition ของ if ต้องเป็น bool อยู่แล้ว จบ ซึ่งคือ E0308 ที่แสดงด้านบนพอดี
  • “while let กับ let else ทำสิ่งเดียวกัน” while let วนซ้ำ body ตราบเท่า pattern ยัง match let else match pattern ครั้งเดียวและ diverge ทันที (return/break/panic!) ถ้าไม่ match — มันมีไว้สำหรับ “เอาค่านี้มา ไม่งั้นออกไปเลย” ไม่ใช่สำหรับ loop
  • “Let chains เป็นแค่ syntax sugar ไม่มีข้อจำกัด edition จริง” มันถูก gate ไว้ที่ edition 2024 โดยเฉพาะ — source เดียวกันเป๊ะคอมไพล์บน 2021 ได้ก็ต่อเมื่อเอา && ที่ chain อยู่ใน if let ออกก่อน นี่คือขอบเขต compatibility จริง ไม่ใช่แค่ทางเลือกด้าน style

Loop ที่ AI เขียนบางครั้งไม่มี break ที่ไปถึงได้เลย — block loop { ... } ที่ทุก exit path ตั้งใจให้เป็น return แต่ branch หนึ่งถูก refactor แล้ว return กลายเป็นสิ่งที่ไม่ได้ออกจาก loop จริงๆ อย่างเงียบๆ ทิ้ง infinite loop ที่โผล่มาเฉพาะตอนโหลดจริงเท่านั้น

🐛 หา bug ในโค้ดนี้

fn wait_for_ready(check: impl Fn() -> bool) {
loop {
if check() {
println!("ready!");
}
}
}
เฉลย

loop นี้ไม่มี break เลยไม่ว่าที่ไหน — เมื่อ check() return true มัน print "ready!" แล้ววนกลับไปเช็คใหม่ทันที ตลอดไป print "ready!" ทุกรอบแทนที่จะหยุด ทางแก้คือใส่ break ใน branch ที่ตั้งใจให้จบการรอ: if check() { println!("ready!"); break; } ทุก loop ที่เขียนเองควรถูกตรวจว่ามี break ที่ไปถึงได้จริงในทุก path ที่เกี่ยวข้อง ไม่ใช่แค่มีอยู่ที่ไหนสักที่ใน source

สิ่งที่จำเป็นสำหรับการใช้ if ของ Rust เป็น expression ทางขวาของ let คืออะไร?
loop ใน Rust return ค่าอย่างไร?
ความแตกต่างระหว่าง 1..4 และ 1..=4 คืออะไร?
loop construct ใดที่ idiomatic ที่สุดใน Rust เมื่อวนผ่าน collection?
โค้ดนี้ compile ไหม?
fn main() {
    let x = 5;
    if x {
        println!("nonzero");
    }
}
break 'outer; ทำอะไรใน loop ที่ซ้อนกันที่มี label 'outer: for ... { for ... { ... } }?
block else ของ let else ต้องเป็นอย่างไร?
let Ok(n) = input.parse::<i32>() else {
    // ต้องมีอะไรตรงนี้?
};