Error Handling (เชิงลึก)
TypeScript try/catch vs Rust Result<T, E>
หัวข้อที่มีชื่อว่า “TypeScript try/catch vs Rust Result<T, E>”TypeScript errors ไม่ปรากฏที่ type level — ฟังก์ชันใดก็ได้สามารถ throw อะไรก็ได้ทุกเมื่อ และ compiler ไม่บังคับให้คุณจัดการ Rust เข้ารหัสความล้มเหลวโดยตรงใน return type: ฟังก์ชันที่อาจล้มเหลว return Result<T, E> โดยที่ T คือค่าสำเร็จและ E คือ error ถ้าคุณ ignore Result compiler จะเตือน
// TypeScript — errors are invisible in the signaturefunction parseId(s: string): number { const n = parseInt(s, 10); if (isNaN(n)) throw new Error(`Invalid id: ${s}`); if (n === 0) throw new Error("id zero not allowed"); return n;}
try { console.log(parseId("42")); // 42 console.log(parseId("abc")); // throws} catch (err) { console.error(err instanceof Error ? err.message : err);}use std::num::ParseIntError;
#[derive(Debug)]enum AppError { Parse(ParseIntError), NotFound(String),}
impl std::fmt::Display for AppError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { AppError::Parse(e) => write!(f, "parse error: {e}"), AppError::NotFound(s) => write!(f, "not found: {s}"), } }}
impl From<ParseIntError> for AppError { fn from(e: ParseIntError) -> Self { AppError::Parse(e) }}
fn parse_id(s: &str) -> Result<u32, AppError> { let n: u32 = s.trim().parse()?; // ? converts ParseIntError via From if n == 0 { return Err(AppError::NotFound(String::from("id zero not allowed"))); } Ok(n)}
fn main() { println!("{:?}", parse_id("42")); println!("{:?}", parse_id("abc")); println!("{:?}", parse_id("0"));}? operator
หัวข้อที่มีชื่อว่า “? operator”? ที่ท้าย expression ที่ return Result คือ syntactic sugar ของ “ถ้าเป็น Err ให้แปลง error แล้ว return ออกไปเลย ไม่เช่นนั้นก็ unwrap ค่า Ok” เขียนตัวเดียวแทน match แบบเต็ม ๆ ได้หลายบล็อก
// ไม่มี ?let n = match s.trim().parse::<u32>() { Ok(val) => val, Err(e) => return Err(AppError::from(e)),};
// มี ? — behaviour เหมือนกันทุกประการ ตัวอักษรเดียวlet n: u32 = s.trim().parse()?;สำหรับ ? ทำงานข้าม error types ต่างกัน target error type ต้อง implement From<SourceError> standard library และ thiserror จัดการนี้อัตโนมัติ
thiserror — custom errors ที่ ergonomic
หัวข้อที่มีชื่อว่า “thiserror — custom errors ที่ ergonomic”crate thiserror กำจัด boilerplate ของการเขียน Display และ From ด้วยมือ annotation #[derive(Error)] และ attribute #[error("...")] บนแต่ละ variant ก็เพียงพอ
// TypeScript — custom error class hierarchyclass AppError extends Error { constructor(message: string) { super(message); this.name = "AppError"; }}
class ParseError extends AppError { constructor(public input: string) { super(`Invalid input: ${input}`); }}
class NotFoundError extends AppError { constructor(public id: string) { super(`Not found: ${id}`); }}use thiserror::Error;
#[derive(Error, Debug)]enum AppError { #[error("not found: {0}")] NotFound(String),
#[error("parse error: {0}")] Parse(#[from] std::num::ParseIntError),}
fn parse_id(s: &str) -> Result<u32, AppError> { let n: u32 = s.trim().parse()?; // ParseIntError -> AppError::Parse via #[from] if n == 0 { return Err(AppError::NotFound(String::from("id zero not allowed"))); } Ok(n)}
fn main() { match parse_id("42") { Ok(id) => println!("Parsed: {id}"), Err(e) => println!("Error: {e}"), } match parse_id("abc") { Ok(id) => println!("Parsed: {id}"), Err(e) => println!("Error: {e}"), } match parse_id("0") { Ok(id) => println!("Parsed: {id}"), Err(e) => println!("Error: {e}"), }}anyhow — error handling ระดับ application
หัวข้อที่มีชื่อว่า “anyhow — error handling ระดับ application”thiserror ใช้สำหรับ library code ที่ผู้เรียกต้องตรวจสอบ error type anyhow ใช้สำหรับ application/binary code ที่คุณแค่ต้องการให้ errors propagate อย่างชัดเจนพร้อม context messages ดีๆ โดยไม่ต้องการ match บน specific variants
use anyhow::{Context, Result};
fn read_config(path: &str) -> Result<String> { std::fs::read_to_string(path) .with_context(|| format!("failed to read config at {path}"))}anyhow::Result<T> เป็น alias ของ Result<T, anyhow::Error> error ใดก็ตามที่ implement std::error::Error สามารถแปลงเป็น anyhow::Error ได้ด้วย ?
ลองเล่นดู
หัวข้อที่มีชื่อว่า “ลองเล่นดู”use thiserror::Error;
#[derive(Error, Debug)]enum AppError { #[error("not found: {0}")] NotFound(String), #[error("parse error: {0}")] Parse(#[from] std::num::ParseIntError),}
fn parse_id(s: &str) -> Result<u32, AppError> { let n: u32 = s.trim().parse()?; if n == 0 { return Err(AppError::NotFound(String::from("id zero not allowed"))); } Ok(n)}
fn main() { match parse_id("42") { Ok(id) => println!("Parsed: {id}"), Err(e) => println!("Error: {e}"), } match parse_id("abc") { Ok(id) => println!("Parsed: {id}"), Err(e) => println!("Error: {e}"), } match parse_id("0") { Ok(id) => println!("Parsed: {id}"), Err(e) => println!("Error: {e}"), }}Compiling…