When Rust encounters a situation it cannot recover from — such as an out-of-bounds array access — it panics. You can also trigger a panic manually with the panic! macro. When a program panics it prints an error message, unwinds the stack, and exits.
Khi Rust gặp tình huống mà nó không thể tiếp tục — chẳng hạn như truy cập ngoài giới hạn mảng — nó sẽ panic. Bạn cũng có thể kích hoạt panic theo cách thủ công bằng macro panic!. Khi chương trình panic, nó in ra thông báo lỗi, dọn dẹp stack, và thoát.
1fn main() {2 // Explicit panic with a message3 // panic!("Something went terribly wrong");45 // Index out of bounds causes a panic automatically6 let v = vec![1, 2, 3];7 // v[99]; // thread 'main' panicked at 'index out of bounds'89 // RUST_BACKTRACE=1 cargo run → shows full stack trace10}1112fn only_positive(n: i32) -> i32 {13 if n <= 0 {14 panic!("Expected a positive number, got {n}");15 }16 n17}- In examples and prototypes where error handling would obscure the main point
Trong các ví dụ và nguyên mẫu khi xử lý lỗi sẽ làm mờ ý chính
- In tests — panicking is how tests fail
Trong các test — panic là cách để test thất bại
- When state is genuinely unrecoverable and continuing would cause worse corruption
Khi trạng thái thực sự không thể khôi phục và tiếp tục sẽ gây ra hỏng hóc nghiêm trọng hơn
- When a contract violation occurs (precondition not met by the caller)
Khi vi phạm hợp đồng API (điều kiện tiên quyết không được đáp ứng bởi người gọi)
Đừng dùng panic để kiểm soát luồng bình thường. Mọi lỗi có thể khôi phục đều nên dùng Result<T, E>.
Result is the standard return type for operations that can fail in Rust. It is an enum with two variants: Ok(T) for success and Err(E) for failure. The compiler forces you to handle both cases.
Result là kiểu trả về chuẩn cho các hoạt động có thể thất bại trong Rust. Nó là một enum với hai biến thể: Ok(T) cho thành công và Err(E) cho thất bại. Trình biên dịch buộc bạn phải xử lý cả hai trường hợp.
1use std::fs;2use std::io;3use std::num::ParseIntError;45// Result<T, E> is defined as:6// enum Result<T, E> {7// Ok(T),8// Err(E),9// }1011fn read_username(path: &str) -> Result<String, io::Error> {12 fs::read_to_string(path)T is the type of the success value, E is the type of the error. Both are generic parameters, so Result can represent any success/failure pair.
T là kiểu của giá trị thành công, E là kiểu của lỗi. Cả hai đều là tham số generic, vì vậy Result có thể biểu diễn bất kỳ cặp thành công/thất bại nào.
These two convenience methods extract the value inside Ok, or panic if the result is Err. They are useful in prototypes and quick scripts but should be avoided in production code.
Hai phương thức tiện lợi này trích xuất giá trị bên trong Ok, hoặc panic nếu kết quả là Err. Chúng hữu ích trong các nguyên mẫu và script nhanh, nhưng nên tránh trong code sản xuất.
1use std::fs;23fn main() {4 // unwrap() panics with a generic message on Err5 let content = fs::read_to_string("data.txt").unwrap();67 // expect() lets you provide a custom panic message — prefer this over unwrap8 let content = fs::read_to_string("data.txt")9 .expect("data.txt should exist and be readable");1011 // On Option types too12 let v = vec![1, 2, 3];13 let first = v.first().unwrap(); // panics if empty14 let first = v.first().expect("vec was empty"); // clearer panic15 println!("{first}");16}Dùng expect thay vì unwrap — thông báo lỗi của bạn sẽ giải thích tại sao lỗi không nên xảy ra, giúp debug dễ hơn khi nó thực sự xảy ra.
The ? operator is syntactic sugar for propagating errors up the call chain. If the value is Ok(x), it unwraps to x. If it is Err(e), it converts e to the enclosing function's error type and returns early.
Toán tử ? là cú pháp tiện lợi để truyền lỗi lên trên trong chuỗi cuộc gọi. Nếu giá trị là Ok(x), nó giải nén thành x. Nếu là Err(e), nó chuyển đổi e sang kiểu lỗi của hàm bao và return sớm.
1use std::fs;2use std::io;3use std::io::Read;45// Without ? — verbose6fn read_name_verbose(path: &str) -> Result<String, io::Error> {7 let mut file = match fs::File::open(path) {8 Ok(f) => f,9 Err(e) => return Err(e),10 };11 let mut s = String::new();12 match file.read_to_string(&mut s) {The ? operator calls From::from on the error to convert the error type if needed. This allows your function to return a single error type even when inner operations produce different error types.
Toán tử ? gọi From::from trên lỗi để chuyển đổi kiểu lỗi nếu cần. Điều này cho phép hàm của bạn trả về một kiểu lỗi duy nhất ngay cả khi các hoạt động bên trong tạo ra nhiều loại lỗi khác nhau.
For library code you should define your own error type. This lets users of your library pattern-match on specific error kinds and handle them appropriately.
Đối với code thư viện, bạn nên định nghĩa kiểu lỗi riêng của mình. Điều này cho phép người dùng của thư viện của bạn khớp mẫu trên các loại lỗi cụ thể và xử lý chúng phù hợp.
1use std::fmt;2use std::num::ParseIntError;34#[derive(Debug)]5enum AppError {6 Parse(ParseIntError),7 OutOfRange(i32),8 Io(std::io::Error),9}1011// Display is needed for human-readable error messages12impl fmt::Display for AppError {When you implement From<OtherError> for your error type, the ? operator automatically converts OtherError into yours. This creates a unified error type for an entire function.
Khi bạn triển khai From<OtherError> cho kiểu lỗi của mình, toán tử ? sẽ tự động chuyển đổi OtherError thành kiểu của bạn. Điều này tạo ra kiểu lỗi thống nhất cho toàn bộ hàm.
1use std::num::ParseIntError;2use std::io;34#[derive(Debug)]5enum AppError {6 Parse(ParseIntError),7 Io(io::Error),8}910// Implement From so ? can auto-convert11impl From<ParseIntError> for AppError {12 fn from(e: ParseIntError) -> Self {These two crates complement each other and cover most error-handling needs in real application and library code.
Hai crate này bổ sung cho nhau và giải quyết hầu hết các nhu cầu xử lý lỗi trong code ứng dụng và thư viện thực tế.
1// Cargo.toml: thiserror = "1"2use thiserror::Error;34#[derive(Error, Debug)]5enum DataError {6 #[error("failed to read file: {0}")]7 Io(#[from] std::io::Error),89 #[error("invalid format: expected integer, got '{input}'")]10 Parse {11 input: String,12 #[source]13 source: std::num::ParseIntError,14 },1516 #[error("value {0} is out of range 0-100")]17 OutOfRange(i32),18}1920// thiserror generates Display and Error impls automatically1// Cargo.toml: anyhow = "1"2use anyhow::{Context, Result, bail, ensure};34fn load_config(path: &str) -> Result<String> {5 // anyhow::Result<T> = Result<T, anyhow::Error>6 let content = std::fs::read_to_string(path)7 .with_context(|| format!("failed to open config at '{path}'"))?;89 ensure!(!content.is_empty(), "config file is empty");1011 if content.contains("INVALID") {12 bail!("config contains invalid keyword");- Use thiserror to define explicit error types in libraries (callers can pattern-match)
Dùng thiserror để định nghĩa các kiểu lỗi rõ ràng trong thư viện (người dùng có thể khớp mẫu)
- Use anyhow for flexible error handling in application code (faster development)
Dùng anyhow để xử lý lỗi linh hoạt trong code ứng dụng (tốc độ phát triển nhanh hơn)
- Return Result instead of panicking for any recoverable error
Trả về Result thay vì panic cho mọi lỗi có thể khôi phục
- Use the ? operator to keep code clean and readable
Dùng toán tử ? để giữ code gọn gàng và đọc được
- Implement Display and std::error::Error for custom error types
Triển khai Display và std::error::Error cho kiểu lỗi tùy chỉnh
- Provide context when propagating errors (e.g., anyhow's with_context)
Cung cấp ngữ cảnh khi truyền lỗi (ví dụ: with_context của anyhow)
- Pattern-match on specific error variants rather than catching everything at once
Khớp mẫu trên các biến thể lỗi cụ thể thay vì bắt mọi thứ một lần
- Document the errors your public functions return in their doc comments
Ghi lại các lỗi trả về của hàm public trong tài liệu
Triết lý của Rust: làm cho các lỗi trở nên rõ ràng trong các chữ ký kiểu. Nếu một hàm có thể thất bại, kiểu trả về của nó phải nói lên điều đó.
Key Takeaways
Điểm Chính
- Result<T, E> represents success (Ok) or failure (Err)Result<T, E> biểu diễn thành công (Ok) hoặc thất bại (Err)
- The ? operator propagates errors concisely up the call stackToán tử ? truyền lỗi ngắn gọn lên ngăn xếp gọi
- Use expect() for unrecoverable errors with a descriptive messageDùng expect() cho lỗi không thể phục hồi với thông điệp mô tả
- Custom error types implement the std::error::Error traitKiểu lỗi tùy chỉnh triển khai trait std::error::Error
Practice
Test your understanding of this chapter
Which variant of Result<T, E> indicates a successful operation?
Biến thể nào của Result<T, E> cho biết thao tác thành công?
What does the ? operator do when it encounters an Err(e) value?
Toán tử ? làm gì khi gặp giá trị Err(e)?
The expect() method is preferable to unwrap() because it lets you provide a descriptive panic message.
Phương thức expect() được ưu tiên hơn unwrap() vì nó cho phép bạn cung cấp thông báo panic mô tả rõ ràng hơn.
The ? operator can be used inside functions that return the unit type () with no Result or Option in their signature.
Toán tử ? có thể được dùng bên trong các hàm trả về kiểu đơn vị () mà không có Result hay Option trong chữ ký của chúng.
Propagate an error with the ? operator
Truyền lỗi bằng toán tử ?
fn read_name(path: &str) -> Result<String, io::Error> { let mut file = fs::File::open(path); let mut s = String::new(); file.read_to_string(&mut s); Ok(s) }