Functions
การประกาศฟังก์ชัน
หัวข้อที่มีชื่อว่า “การประกาศฟังก์ชัน”ใช้ fn ในการประกาศฟังก์ชัน ทุก parameter ต้องมี type annotation อย่างชัดเจน — คอมไพเลอร์ไม่อนุมาน type ของ parameter return type เขียนตามหลัง ->
fn add(a: i32, b: i32) -> i32 { a + b}ฟังก์ชันสามารถประกาศก่อนหรือหลังที่ถูกเรียกใช้ — Rust ไม่ต้องการ forward declaration
Statements vs Expressions
หัวข้อที่มีชื่อว่า “Statements vs Expressions”นี่คือความแตกต่างที่สำคัญที่สุดใน function body ของ Rust:
- Statement ทำและ ไม่ ผลิตค่า
let x = 5;คือ statement ที่ลงท้ายด้วย semicolon - Expression ประเมินเป็นค่า
5 + 3คือ expression block{ ... }ก็เป็น expression เช่นกัน
fn main() { // This is a statement — no value produced let y = 6;
// This is an expression block — produces 12 let z = { let x = 3; x * 4 // no semicolon — this is the block's value };
println!("y={}, z={}", y, z);}การเพิ่ม semicolon ที่บรรทัดสุดท้ายของ block จะ แปลง expression เป็น statement — block จะ return unit type () แทนที่จะเป็นค่าที่คำนวณได้
สิ่งที่คอมไพเลอร์บอก: Semicolon เดียวเปลี่ยน Return Type
หัวข้อที่มีชื่อว่า “สิ่งที่คอมไพเลอร์บอก: Semicolon เดียวเปลี่ยน Return Type”เพราะ function body เองก็คือ block กฎนี้จึงใช้กับ fn body ด้วย — และคอมไพเลอร์รายงานเป็น type mismatch กับ () ไม่ใช่กับ type ที่คุณคาดหวัง:
// @expect-error E0308fn square(n: i32) -> i32 { n * n;}
fn main() { println!("{}", square(4));}error[E0308]: mismatched types --> src/main.rs:2:22 |2 | fn square(n: i32) -> i32 { | ------ ^^^ expected `i32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression3 | n * n; | - help: remove this semicolon to return this value
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.สังเกตการวินิจฉัยของคอมไพเลอร์เอง: เมื่อมี semicolon อยู่ function body ไม่มี tail expression เลย จึง return () โดยอัตโนมัติ — ทางแก้คือลบ semicolon ทิ้ง ซึ่งบรรทัด help: ชี้ตรงไปที่จุดนั้นเลย นี่คือ compiler error แรกๆ ที่ผู้เริ่มต้นเจอบ่อยที่สุด และมันเป็นการแก้แบบ one-character fix เดียวกันเสมอ
Trailing-Expression Return
หัวข้อที่มีชื่อว่า “Trailing-Expression Return”expression สุดท้ายใน function body ที่เขียน โดยไม่มี semicolon คือ return value คุณยังสามารถใช้ keyword return สำหรับ early return ได้ แต่ Rust ที่เป็น idiomatic นิยม trailing expression สำหรับ return path หลัก
fn square(n: i32) -> i32 { n * n // returned because it has no semicolon}
fn max_of_two(a: i32, b: i32) -> i32 { if a > b { a } else { b } // if is an expression}Unit Type
หัวข้อที่มีชื่อว่า “Unit Type”ฟังก์ชันที่ไม่ return ค่าที่มีความหมาย จะ return () (unit type) โดยอัตโนมัติ signature fn foo() เป็น shorthand ของ fn foo() -> () unit type คล้าย void ในภาษาอื่น แต่เป็น type จริงๆ ที่มีค่าเดียว: ()
fn greet(name: &str) { // implicitly returns () println!("Hello, {}!", name);}ฟังก์ชัน describe ใน playground ด้านล่าง return &'static str: reference ไปยัง string literal ที่ถูกฝังไว้ใน binary และมีอายุตลอดทั้งโปรแกรม 'static หมายถึงสิ่งนี้ตรงๆ — “valid ตลอดอายุโปรแกรม” — และ string literal จะเข้าเงื่อนไขนี้เสมอ คุณจะได้เจอ 'static และ lifetime อื่นๆ อย่างละเอียดในโมดูล Traits & Generics
todo! และ unimplemented!
หัวข้อที่มีชื่อว่า “todo! และ unimplemented!”ระหว่างร่างโครงฟังก์ชันก่อนใส่ body จริง todo!() และ unimplemented!() ให้โค้ด compile ผ่านได้ (type-check เป็น type อะไรก็ได้ รวมถึง -> ! ที่จะพูดถึงต่อไป) พร้อม panic เสียงดังถ้า path นั้นถูกเรียกจริงตอน runtime:
// @expect-panicfn compute_total(items: &[i32]) -> i32 { if items.is_empty() { return 0; } todo!("implement the real calculation")}
fn main() { println!("{}", compute_total(&[1, 2, 3]));}thread 'main' (<id>) panicked at src/main.rs:6:5:not yet implemented: implement the real calculationnote: run with `RUST_BACKTRACE=1` environment variable to display a backtraceนี่ดีกว่า panic!("todo") เปล่าๆ มาก: ทั้ง todo! และ unimplemented! สื่อ เจตนา ทั้งในข้อความและในสายตาของ type checker (ผู้อ่าน — หรือ grep) จะบอกได้ทันทีว่า “path นี้ยังไม่เสร็จ” ต่างจาก “path นี้คือ runtime error จริงๆ”
Never Type !
หัวข้อที่มีชื่อว่า “Never Type !”ฟังก์ชันที่ไม่เคย return เลย — เพราะมัน panic เสมอ, loop ไม่รู้จบ หรือเรียก std::process::exit — annotate ได้ด้วย -> ! ซึ่งคือ never type ! พิเศษตรงที่มัน coerce ไปเป็น type อื่นใดก็ได้ ซึ่งเป็นสิ่งที่ทำให้ branch fail(...) แทนที่ branch ที่ return String ภายใน if/else หรือ match ที่ arm อื่น return ค่าจริงได้:
fn fail(msg: &str) -> ! { panic!("{}", msg);}
fn describe_or_fail(n: i32) -> &'static str { if n >= 0 { "non-negative" } else { fail("negative numbers are not supported") }}Function Pointers
หัวข้อที่มีชื่อว่า “Function Pointers”ชื่อฟังก์ชัน โดยไม่เรียกมัน สามารถส่งต่อเป็นค่าของ type fn(ArgTypes) -> ReturnType ได้เอง — function pointer ธรรมดา ต่างจาก (และง่ายกว่า) closure ที่จะพูดถึงในโมดูล Collections & Iterators:
fn double(n: i32) -> i32 { n * 2 }fn triple(n: i32) -> i32 { n * 3 }
fn apply(f: fn(i32) -> i32, value: i32) -> i32 { f(value)}
fn main() { println!("{}", apply(double, 5)); // 10 println!("{}", apply(triple, 5)); // 15}flowchart TD
B["function body { ... }"] --> T{"last line has a ;?"}
T -->|"no"| E["tail expression
-> its value is returned"]
T -->|"yes"| U["last line is a statement
-> body returns ()"]
E --> M{"does the fn signature
say -> SomeType?"}
U --> M
M -->|"types match"| OK["compiles"]
M -->|"types don't match"| ERR["E0308: mismatched types"] fn add(a: i32, b: i32) -> i32 { a + b // trailing expression -- no semicolon -- is the return value}
fn describe(n: i32) -> &'static str { if n > 0 { "positive" } else if n < 0 { "negative" } else { "zero" }}
fn unit_example() { // Functions without -> return the unit type () println!("unit example");}
fn main() { let sum = add(3, 4); println!("add(3, 4) = {}", sum);
println!("describe(10) = {}", describe(10)); println!("describe(-3) = {}", describe(-3)); println!("describe(0) = {}", describe(0));
unit_example();}Compiling…
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”ทุก block ใน Rust — function body, if branch, loop, block เปล่า { } — เป็นไปตามกฎเดียวกันเป๊ะ: ค่าของมันคือ expression สุดท้าย เว้นแต่ expression นั้นลงท้ายด้วย semicolon ซึ่งในกรณีนั้นค่าของ block คือ () -> T ใน function signature ก็แค่การตรวจสอบคำสัญญาของคอมไพเลอร์ที่ใช้กับ block นอกสุด เมื่อคุณมองฟังก์ชันแบบนี้ if-เป็น-expression, block-scoped shadowing และกฎ semicolon จะยุบรวมเป็นความคิดเดียวแทนที่จะเป็นสามเรื่องแยกที่ต้องจำ
Trade-offs
หัวข้อที่มีชื่อว่า “Trade-offs”การใช้ semicolon เป็นตัวอักษรตัวเดียวที่ตัดสิน “มีค่า” กับ “ไม่มีค่า” นั้นกระชับมาก — คุณไม่ต้องเขียน return สำหรับกรณีทั่วไป — แต่หมายความว่า semicolon ที่หลงหรือขาดหายไปตัวเดียวเปลี่ยนความหมายของฟังก์ชัน ไม่ใช่แค่ formatting ต่างจากภาษาที่ whitespace หรือ semicolon เป็นแค่ความสวยงาม ผลตอบแทนคือ expression ประกอบกันได้อย่างสม่ำเสมอ: if, match, loop และ block ธรรมดาใช้แทนกันได้ทุกที่ที่ต้องการค่า โดยไม่ต้อง special-case อันไหนเลย
Common Misconceptions
หัวข้อที่มีชื่อว่า “Common Misconceptions”- “Semicolon ใน Rust เป็นแค่ตัวจบ statement เหมือน C หรือ JavaScript” ส่วนใหญ่ก็ใช่ — แต่ที่บรรทัดสุดท้ายของ block semicolon ยังเป็นสวิตช์ที่ทิ้งค่าของบรรทัดนั้นและทำให้ block return
()แทน - “
todo!()เหมือนกับปล่อย comment // TODO ไว้” Comment ไม่หยุดการทำงาน;todo!()(และunimplemented!()) type-check เป็น type อะไรก็ได้ ทำให้โค้ดรอบข้าง compile ผ่านวันนี้ แต่ panic ทันทีที่บรรทัดนั้นถูกรันจริง — นั่นคือประเด็น: ship โค้ดที่มีtodo!()ที่ไม่ถูกเรียกถึงได้อย่างปลอดภัย แต่ ship โค้ดที่ทำสิ่งผิดแบบเงียบๆ ไม่ปลอดภัย - “function pointer (
fn(i32) -> i32) กับ closure คือสิ่งเดียวกัน” closure capture ตัวแปรจาก environment ได้;fnpointer ธรรมดา capture อะไรไม่ได้เลย — มันคือแค่ address ของฟังก์ชันเดี่ยวๆ closure ที่ไม่ capture อะไรเลยทุกตัว coerce เป็น function pointer ได้ แต่กลับกันไม่ได้
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”AI บางครั้งทิ้งค่าค้างไว้ท้ายฟังก์ชันที่ถูก refactor จาก “return บางอย่าง” เป็น “แค่ print มัน” เพราะบรรทัดท้ายไม่ถูกแตะระหว่างแก้ไข — semicolon ที่ควรถูกเพิ่มเพื่อให้กลับเป็น statement ที่ return () ถูกลืมไป
🐛 หา bug ในโค้ดนี้
fn log_status(code: i32) { println!("status: {}", code); code}เฉลย
log_status ไม่มี -> i32 ใน signature จึงคาดหวังให้ return () — แต่ code ที่ท้ายสุด (ไม่มี semicolon) ทำให้มันเป็น tail expression และ code เป็น i32 โค้ดนี้ compile ไม่ผ่านด้วย error[E0308]: mismatched types, expected (), found i32 และ help: ของคอมไพเลอร์เองยังเสนอ “try adding a return type: -> i32” เป็นทางแก้หนึ่ง ทางแก้อีกทาง — ที่ตรงกับเจตนาจริงของฟังก์ชันนี้ — คือแค่เพิ่ม semicolon หลัง code เพื่อทิ้งค่านั้นไป