การจัดการ Error ใน Rust 2026: Result, Option, thiserror และ anyhow
คู่มือการจัดการ error ใน Rust ด้วย Result, Option, ตัวดำเนินการ ?, thiserror และ anyhow สำหรับนักพัฒนาไทย พร้อม best practices ครบถ้วน

การจัดการ error ใน Rust ยึดหลักสอง enum—Result<T, E> และ Option<T>—ที่บังคับให้จัดการความสำเร็จ ความล้มเหลว และการไม่มีค่าอย่างชัดเจนในขณะคอมไพล์ ต่างจาก exception ที่แพร่กระจายโดยไม่เห็น แนวทางของ Rust ทำให้เส้นทาง error ปรากฏใน signature ของฟังก์ชัน กำจัดประเภทของข้อผิดพลาด runtime ที่ไม่คาดคิดทั้งหมด ตัวดำเนินการ ? ร่วมกับ crate เช่น thiserror และ anyhow ทำให้โมเดลที่ชัดเจนนี้เรียบง่ายขึ้นโดยไม่สูญเสียความชัดเจน
ใช้ Option<T> สำหรับค่าที่อาจไม่มีอย่างถูกต้อง (ฟิลด์การตั้งค่า ผลการค้นหา) ใช้ Result<T, E> เมื่อการดำเนินการอาจล้มเหลวพร้อมข้อมูล error ที่มีความหมาย (I/O ไฟล์ request เครือข่าย การ parsing)
ทำความเข้าใจพื้นฐานของ Result และ Option
Result<T, E> แสดงถึงความสำเร็จ (Ok(T)) หรือความล้มเหลว (Err(E)) Option<T> แสดงถึงการมีค่า (Some(T)) หรือไม่มี (None) ทั้งสองเป็น sum types—compiler รับรองว่าทุก variant ได้รับการจัดการ
// Demonstrates Result and Option basic patterns
fn find_user(id: u64) -> Option<String> {
// Returns None if user doesn't exist
if id == 0 {
None
} else {
Some(format!("User-{}", id))
}
}
fn parse_port(s: &str) -> Result<u16, std::num::ParseIntError> {
// Returns Err if parsing fails
s.parse::<u16>()
}
fn main() {
// Option handling - must address None case
match find_user(42) {
Some(name) => println!("Found: {}", name),
None => println!("User not found"),
}
// Result handling - must address Err case
match parse_port("8080") {
Ok(port) => println!("Port: {}", port),
Err(e) => println!("Invalid port: {}", e),
}
}Compiler ปฏิเสธโค้ดที่ละเว้นค่า return เหล่านี้โดยไม่มีการจัดการอย่างชัดเจน การออกแบบนี้จับ bug ในขณะคอมไพล์ที่จะปรากฏเป็น null pointer exception หรือ uncaught error ในภาษาอื่น
ตัวดำเนินการเครื่องหมายคำถามสำหรับการแพร่กระจายที่กระชับ
ตัวดำเนินการ ? แปลงสาย match ที่ยาวเยิ่นยานเป็นโค้ดเชิงเส้นที่อ่านง่าย เมื่อใช้กับ Result มันจะ return ก่อนกำหนดด้วย error ถ้ามี หรือ unwrap ค่าที่สำเร็จ เช่นเดียวกันกับ Option
// Using ? for clean error propagation
use std::fs::File;
use std::io::{self, BufRead, BufReader};
fn read_first_line(path: &str) -> Result<String, io::Error> {
let file = File::open(path)?; // Returns early if open fails
let mut reader = BufReader::new(file);
let mut line = String::new();
reader.read_line(&mut line)?; // Returns early if read fails
Ok(line.trim().to_string())
}
fn get_port_from_config(path: &str) -> Result<u16, Box<dyn std::error::Error>> {
let content = read_first_line(path)?;
let port = content.parse::<u16>()?; // ParseIntError converts via From
Ok(port)
}ตัวดำเนินการ ? ต้องการประเภท error ที่สามารถแปลงเป็นประเภท error return ของฟังก์ชันผ่าน trait From การใช้ Box<dyn std::error::Error> ตามที่แสดงด้านบนยอมรับประเภท error ใดๆ ที่ implement trait Error มาตรฐาน
สร้าง Custom Error ด้วย thiserror
Crate thiserror กำจัด boilerplate สำหรับประเภท error แบบกำหนดเอง มัน derive การ implement Error, Display, และ From ผ่าน procedural macro
// Custom error types using thiserror 2.0
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("configuration file not found at {path}")]
NotFound { path: String },
#[error("invalid port number: {0}")]
InvalidPort(#[from] std::num::ParseIntError),
#[error("IO error reading config")]
IoError(#[from] std::io::Error),
#[error("missing required field: {0}")]
MissingField(String),
}
fn load_config(path: &str) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)?; // IoError auto-converts
let port: u16 = content
.lines()
.find(|l| l.starts_with("port="))
.ok_or(ConfigError::MissingField("port".into()))?
.strip_prefix("port=")
.unwrap()
.parse()?; // ParseIntError auto-converts to InvalidPort
Ok(Config { port })
}
struct Config {
port: u16,
}Attribute #[from] สร้างการแปลงอัตโนมัติ ช่วยให้ใช้ ? ได้อย่างราบรื่นกับประเภท error พื้นฐานต่างๆ ข้อความ error กลายเป็นเอกสารในตัวผ่าน format string #[error(...)]
Error ระดับแอปพลิเคชันด้วย anyhow
ในขณะที่ thiserror เหมาะกับโค้ด library ที่มีประเภท error เฉพาะ anyhow มุ่งเป้าไปที่แอปพลิเคชันที่บริบทของ error สำคัญกว่าความละเอียดของประเภท Trait Context ของมันเพิ่มข้อความอธิบายให้กับ error ใดๆ
// Application error handling with anyhow 1.0
use anyhow::{Context, Result, bail, ensure};
fn load_database_url() -> Result<String> {
std::env::var("DATABASE_URL")
.context("DATABASE_URL environment variable not set")
}
fn connect_to_database(url: &str) -> Result<DatabaseConnection> {
ensure!(!url.is_empty(), "database URL cannot be empty");
let conn = DatabaseConnection::new(url)
.context("failed to establish database connection")?;
if !conn.is_healthy() {
bail!("database connection unhealthy after establishment");
}
Ok(conn)
}
fn main() -> Result<()> {
let url = load_database_url()?;
let conn = connect_to_database(&url)
.context("application startup failed")?;
// Context chains create readable error traces:
// Error: application startup failed
// Caused by:
// 0: failed to establish database connection
// 1: connection refused
Ok(())
}
struct DatabaseConnection;
impl DatabaseConnection {
fn new(_url: &str) -> Result<Self> { Ok(Self) }
fn is_healthy(&self) -> bool { true }
}Method context() ห่อ error ด้วยข้อมูลเพิ่มเติม สร้างสายที่ช่วยในการ debug Macro bail! ให้ early exit พร้อมข้อความ error ที่ format แล้ว ขณะที่ ensure! ทำหน้าที่เหมือน assertion ที่ return error แทน panic
พร้อมที่จะพิชิตการสัมภาษณ์ Rust แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
รวม thiserror และ anyhow ในโปรเจกต์จริง
Library เปิดเผย error ที่มีโครงสร้างผ่าน thiserror เพื่อการจัดการแบบ programmatic โดยผู้ใช้ แอปพลิเคชันห่อ error เหล่านั้นด้วย anyhow เพื่อ output ที่มนุษย์อ่านได้ การแยกนี้รักษา API ให้สะอาดในขณะที่คงความสามารถในการ debug
// Exposes typed errors for programmatic handling
use thiserror::Error;
#[derive(Error, Debug)]
pub enum PaymentError {
#[error("insufficient funds: required {required}, available {available}")]
InsufficientFunds { required: u64, available: u64 },
#[error("card declined: {reason}")]
CardDeclined { reason: String },
#[error("payment provider unavailable")]
ProviderUnavailable(#[source] reqwest::Error),
}
pub fn process_payment(amount: u64) -> Result<Receipt, PaymentError> {
// Library returns specific, matchable error types
Err(PaymentError::InsufficientFunds {
required: amount,
available: 50,
})
}
pub struct Receipt;// Wraps library errors with context
use anyhow::{Context, Result};
use my_payment_lib::{process_payment, PaymentError};
fn checkout(cart_total: u64) -> Result<()> {
match process_payment(cart_total) {
Ok(_receipt) => Ok(()),
Err(PaymentError::InsufficientFunds { required, available }) => {
// Handle specific case differently
println!("Add {} to your balance", required - available);
Ok(())
}
Err(e) => Err(e).context("checkout payment processing failed"),
}
}Pattern นี้ช่วยให้ caller match กับ variant เฉพาะเมื่อการกู้คืนเป็นไปได้ ในขณะยังคงได้รับประโยชน์จากบริบท error ที่มากเมื่อแพร่กระจายความล้มเหลวขึ้นไปด้านบน ชุมชน Rust ส่วนใหญ่ได้มาตรฐานแนวทางนี้ ตามที่กล่าวถึงใน แนวทาง API Rust
Pattern การจัดการ Error สำหรับโค้ด Async
ฟังก์ชัน async return Result เหมือนกับ synchronous ตัวดำเนินการ ? ทำงานเหมือนกันใน async block และทั้ง thiserror และ anyhow รวมได้โดยไม่ต้องแก้ไข
// Error handling in async Rust with Tokio
use anyhow::{Context, Result};
use std::time::Duration;
async fn fetch_user_data(user_id: u64) -> Result<UserData> {
let response = reqwest::get(format!("https://api.example.com/users/{}", user_id))
.await
.context("HTTP request to user API failed")?;
let status = response.status();
if !status.is_success() {
anyhow::bail!("user API returned status {}", status);
}
let data: UserData = response
.json()
.await
.context("failed to parse user data JSON")?;
Ok(data)
}
async fn fetch_with_retry(user_id: u64, attempts: u32) -> Result<UserData> {
let mut last_error = None;
for attempt in 1..=attempts {
match fetch_user_data(user_id).await {
Ok(data) => return Ok(data),
Err(e) => {
last_error = Some(e);
if attempt < attempts {
tokio::time::sleep(Duration::from_millis(100 * attempt as u64)).await;
}
}
}
}
Err(last_error.unwrap()).context(format!("failed after {} attempts", attempts))
}
#[derive(serde::Deserialize)]
struct UserData {
name: String,
}เมื่อรวมการดำเนินการ async หลายรายการ ใช้ try_join! จาก tokio หรือ futures เพื่อรันพร้อมกันในขณะแพร่กระจาย error แรก สำหรับแนวคิดที่เกี่ยวข้อง สำรวจโมดูล คำถามสัมภาษณ์ async/await
Downcasting และการตรวจสอบ Error
ทั้ง anyhow::Error และ Box<dyn Error> รองรับ downcasting เพื่อกู้คืนประเภท error ดั้งเดิม สิ่งนี้ช่วยให้ logging รายละเอียดเฉพาะในขณะยังคงแพร่กระจาย error ทั่วไป
// Inspecting wrapped error types
use anyhow::{Context, Result};
use std::io;
fn log_and_propagate(result: Result<()>) -> Result<()> {
if let Err(ref e) = result {
// Check if the root cause is a specific type
if let Some(io_err) = e.downcast_ref::<io::Error>() {
match io_err.kind() {
io::ErrorKind::NotFound => {
tracing::warn!("file not found, using defaults");
}
io::ErrorKind::PermissionDenied => {
tracing::error!("permission denied - check file ownership");
}
_ => {
tracing::error!("IO error: {:?}", io_err);
}
}
}
}
result
}Downcasting เชื่อมช่องว่างระหว่างการจัดการ error ทั่วไปและ logic การกู้คืนเฉพาะ ใช้อย่างประหยัด—ถ้า downcasting เกิดขึ้นบ่อย พิจารณาว่า enum error ที่มีประเภทจะดีกว่า
แปลงระหว่าง Option และ Result
Standard library ให้ method สำหรับแปลงระหว่าง Option และ Result ช่วยให้ composition ราบรื่นเมื่อ API ต่างๆ ใช้ pattern ต่างกัน
// Option and Result interoperability
fn get_env_port() -> Option<u16> {
std::env::var("PORT")
.ok() // Result -> Option (discards error)
.and_then(|s| s.parse().ok())
}
fn get_env_port_with_error() -> Result<u16, String> {
std::env::var("PORT")
.map_err(|_| "PORT not set".to_string())?
.parse()
.map_err(|_| "PORT is not a valid number".to_string())
}
fn lookup_and_parse(map: &std::collections::HashMap<String, String>, key: &str) -> Result<u16, String> {
map.get(key)
.ok_or_else(|| format!("key '{}' not found", key))? // Option -> Result
.parse()
.map_err(|e| format!("parse error for '{}': {}", key, e))
}Method ok() ทิ้งรายละเอียด error เมื่อเฉพาะการมีอยู่เท่านั้นที่สำคัญ Method ok_or() และ ok_or_else() แปลง None เป็น custom error ช่วยให้ propagation ? จากค่า Option Pattern เหล่านี้ปรากฏบ่อยใน คำถามสัมภาษณ์ pattern matching
ข้อพิจารณาด้านประสิทธิภาพ
การจัดการ error ใน Rust ไม่มีค่าใช้จ่าย runtime บนเส้นทางความสำเร็จ Result และ Option เป็น enum ที่จัดสรรบน stack ที่มีขนาดแน่นอน Compiler optimize การตรวจสอบเมื่อสามารถพิสูจน์ได้ว่า branch ไม่สามารถเข้าถึงได้
| แนวทาง | ค่าใช้จ่ายเส้นทางสำเร็จ | ค่าใช้จ่ายเส้นทางล้มเหลว | |--------|------------------------|------------------------| | Result/Option | ศูนย์ | Stack unwinding (ถูก) | | panic! | ศูนย์ | Full stack unwinding + cleanup | | Exception C++ | ศูนย์ (ปกติ) | การจัดสรร heap แพง + RTTI |
หลีกเลี่ยง unwrap() และ expect() ในโค้ด library—เก็บไว้สำหรับกรณีที่ความล้มเหลวบ่งบอก bug จริงๆ สำหรับเส้นทางที่สำคัญต่อประสิทธิภาพที่ error เกิดขึ้นบ่อย พิจารณาใช้ enum ที่มี data inline แทนประเภท error ที่จัดสรรบน heap
สรุป
Result<T, E>จัดการความล้มเหลวที่กู้คืนได้;Option<T>จัดการการไม่มีค่า—ทั้งสองบังคับการจัดการตอนคอมไพล์- ตัวดำเนินการ
?แพร่กระจาย error อย่างกระชับ ต้องการ implementation traitFromสำหรับการแปลงประเภท thiserrorสร้างประเภท error ที่มีโครงสร้างสำหรับ library โดยไม่มี boilerplateanyhowให้สาย error ตามบริบทสำหรับแอปพลิเคชัน รองรับcontext(),bail!, และensure!- รวมทั้งสอง: library เปิดเผย error ที่มีประเภทผ่าน
thiserror, แอปพลิเคชันห่อด้วยanyhow - โค้ด async ใช้ pattern เดียวกัน—
?ทำงานใน async block โดยไม่ต้องแก้ไข - ใช้
ok_or()เพื่อแปลงOptionเป็นResult; ใช้ok()สำหรับกลับกัน - Downcast wrapped error เฉพาะเมื่อ logic การกู้คืนเฉพาะต้องการประเภทดั้งเดิม
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
แท็ก
แชร์
บทความที่เกี่ยวข้อง

Rust Traits และ Generics ฉบับสมบูรณ์ 2026: Trait Upcasting, AsyncFn และรูปแบบขั้นสูงสำหรับสัมภาษณ์งาน
คู่มือเชิงลึกเกี่ยวกับ Rust traits และ generics พร้อมฟีเจอร์ใหม่จาก Rust 2024 Edition: trait upcasting, AsyncFn closures, RPITIT และรูปแบบขั้นสูงที่ใช้ในการสัมภาษณ์จริง

สมาร์ตพอยน์เตอร์ใน Rust อธิบายครบ: Box, Rc, Arc และ RefCell ปี 2026
อธิบายสมาร์ตพอยน์เตอร์ Box, Rc, Arc และ RefCell ใน Rust พร้อมตัวอย่างที่คอมไพล์ได้ปี 2026 ตารางตัดสินใจ และคำถามสัมภาษณ์ที่พบบ่อย

Async/Await ใน Rust: อธิบาย Tokio, Futures และ Asynchronous Concurrency อย่างครบถ้วน
บทความอธิบายการเขียนโปรแกรมแบบ Asynchronous ใน Rust ด้วย async/await, Tokio runtime และ Futures ครอบคลุมตั้งแต่พื้นฐานจนถึงแนวทางปฏิบัติขั้นสูงสำหรับระบบที่ต้องการประสิทธิภาพสูง