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

Option, Result และ ไม่มี Null

TypeScript (และ JavaScript) มีสามวิธีในการแสดง “บางอย่างผิดพลาด”:

  1. null — ความไม่มีของ value โดยตั้งใจ
  2. undefined — value ที่ยังไม่ได้ initialize หรือหายไป
  3. Exception ที่ throw — error ที่ escape call stack โดยไม่มองเห็น

ทั้งสามมองไม่เห็นใน function signature function ที่ return string อาจ return null อย่างเงียบๆ throw error หรือทำงานปกติ — คุณบอกไม่ได้จาก type

Rust ไม่มี null, ไม่มี undefined, และไม่มี exception Value ที่ไม่มีคือ Option<T> operation ที่ล้มเหลวได้ return Result<T, E> ทั้งคู่เป็นแค่ enum variant มองเห็นได้ใน function signature ทุกตัว

Option<T> เป็น enum ที่มีสอง variant: Some(T) (มี value) และ None (ไม่มี value) Compiler บังคับให้คุณ handle ทั้งคู่

TypeScript
// TypeScript — null อาจปรากฏอย่างเงียบๆ
function findUser(id: number): string | null {
if (id === 1) return "Alice";
return null; // ลืมตรวจสอบได้ง่าย
}
const user = findUser(2);
console.log(user?.toUpperCase() ?? "not found");
// ถ้าไม่มี optional chaining: user.toUpperCase() จะ throw TypeError
Rust
fn find_user(id: u32) -> Option<String> {
if id == 1 { Some("Alice".to_string()) } else { None }
}
fn main() {
// Pattern match — ต้อง handle ทั้งสอง case
match find_user(2) {
Some(name) => println!("found: {}", name),
None => println!("not found"),
}
// Helper ที่สะดวก
let upper = find_user(1)
.map(|s| s.to_uppercase())
.unwrap_or_else(|| "NOT FOUND".to_string());
println!("{}", upper);
}

Result<T, E> เป็น enum ที่มีสอง variant: Ok(T) (สำเร็จ) และ Err(E) (ล้มเหลว) Function ที่ล้มเหลวได้ return Result ทำให้ error มองเห็นใน type signature

TypeScript
// TypeScript — exception มองไม่เห็นใน signature
function parseNumber(s: string): number {
const n = parseInt(s);
if (isNaN(n)) throw new Error(`not a number: ${s}`);
return n;
}
try {
const n = parseNumber("abc");
console.log(n * 2);
} catch (e) {
// ลืม try/catch ได้ง่าย — และ signature ไม่ได้บอกใบ้เลย
console.error(e);
}
Rust
fn parse_and_double(s: &str) -> Result<i32, String> {
let n: i32 = s.parse().map_err(|_| format!("not a number: {}", s))?;
Ok(n * 2)
}
fn main() {
match parse_and_double("21") {
Ok(v) => println!("doubled: {}", v),
Err(e) => println!("error: {}", e),
}
match parse_and_double("abc") {
Ok(v) => println!("doubled: {}", v),
Err(e) => println!("error: {}", e),
}
}
fn parse_and_double(s: &str) -> Result<i32, String> {
let n: i32 = s.parse().map_err(|_| format!("not a number: {}", s))?;
Ok(n * 2)
}
fn main() {
match parse_and_double("21") {
Ok(v) => println!("doubled: {}", v),
Err(e) => println!("error: {}", e),
}
let maybe: Option<i32> = Some(42);
if let Some(v) = maybe {
println!("got: {}", v);
}
let none: Option<i32> = None;
println!("unwrap_or: {}", none.unwrap_or(0));
}
`Option<T>` ใน Rust แทนอะไร?
Operator `?` ทำอะไรเมื่อใช้กับ `Result<T, E>`?
Error model ของ Rust แตกต่างจาก `throw` ของ TypeScript อย่างไร?