[Rust Serial] Bài 2: Error Handling trong Rust
Trong bài học này chúng ta sẽ tìm về kỹ thuật xử lý lỗi trong Rust
Outline
- Chương 0: Kiến thức về xử lý lỗi trong Rust?
- Chương 1: Một số thư viện chuyên về xử lý lỗi
- Chương 2: Best practices về xử lý lỗi
- Chương 3: Thực hành
- Chương 4: Bài tập về nhà
References:
- https://www.rustvn.com/vi-VN/rust-book-vn/ch09-00-error-handling.html
Chương 0: Kiến thức về xử lý lỗi trong Rust?
Các khía cạnh để xử lý lỗi mà chúng ta cần quan tâm:
- Lỗi ấy có thể recoverable được hay cần dừng lại ngay?
- Lan truyền lỗi
- Xử lý lỗi trong các lớp biên như mã lỗi HTTP khi trả về client
- Backtrace và tracing các lỗi
- Hiển thị lỗi: Thông tin, số dòng, file,thời gian, định dạng
- Xử lý lỗi qua nhiều server
Phân loại lỗi
Chương này sẽ học về lý thuyết về xử lý lỗi. Như các bạn đã biết thì, trong bất kỳ chương trình nào của bất kỳ ngôn ngữ nào, ngoài những case thành công, thì các trường hợp lỗi sẽ xuất hiện. Ví dụ như:
- Lỗi giao tiếp với các chương trình như
sql server
,redis server
- Lỗi kết dữ liệu truyền vào không hợp lệ: sô thì thành chữ, chữ thành số, uuid không đúng format...
- Lỗi logic khi tính toán
- Lỗi không có quyền truy cập tài nguyên
- Lỗi không thể đọc cấu hình: Đường dẫn sai, files quá lớn....
- Lỗi truy xuất vào con trỏ null, vượt quá phần tử mảng ...
Chúng ta có thể phân lỗi thành một số loại như sau, xét về mặt tính chất phục hồi:
- Lỗi có thể phục hồi được (recoverable): Như đọc file không tìm thấy. Chúng ta sẽ báo cho người dùng để thử lại
- Lỗi không thể phục hồi (unrecoverable): Ví dụ như các lỗi truy xuất con trỏ null. Lỗi này dường như là dấu hiệu của bugs và cần phải được sửa bởi lập trình viên ngay lập tức
Phân loại vào kiểu nào thì cũng chỉ có tính chất tương đối, bởi vì bạn có thể quyết định phụ thuộc loại chương trình bạn đang viết. Ví dụ lỗi không thể tìm file cấu hình hệ thống có thể coi là có thể phục hồi nhưng trong một số trường hợp chúng ta sẽ phân nó thành không phục hồi luôn bởi chương trình sẽ hoàn toàn không thể chạy được nếu không có thông tin này.
Trong Rust không có exception, để xử lý 2 loại lỗi trên nó đưa ra 2 giải pháp:
- Lỗi recoverable: Sử dụng
Result<T,E>
- Lỗi unrecoverable: Sử dụng
panic!
fn main() {
let v = vec![1, 2, 3];
v[99];
}
Các kỹ thuật xử lý lỗi trong Rust
Tóm lại có một số kỹ thuật sau:
-
Sử dụng
Result<T,E>
và sử dụngmatch
để xử lý Chúng ta sẽ trả vềResult<T,E>
enum Result<T, E> { Ok(T), Err(E), } use std::fs::File;
Eg 1:
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
Eg2:
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
- Sử dụng
Result<T,E>
và sử dụngunwrap
vàexpect
- Đặc tính của hàm
unwrap()
là chương trình sẽ dừng lại và gọipanic!
cho chúng ta nếu chương trình trả về Err - Đặc tính của
expect
thì tương tự chỉ là cho phép mình lựa chọn thông điệp để hiển thị khi lỗi.
- Đặc tính của hàm
use std::fs::File;
fn main() {
let f1 = File::open("hello.txt").unwrap();
let f2 = File::open("hello.txt").expect("Failed to open hello.txt");
}
- Sử dụng toán tử
?
(question mark) để giúp lan truyền lỗi. Nó sẽ trả về một lỗi sớm nếu như gặp lỗi
use std::fs::File;
use std::io;
use std::io::Read;
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
Thực chất ?
toán tử này biến đổi kiểu từ kiểu Error của File::open
tới io::Error
và
f.read_to_string()
tới io::Error
. Chuyện gì xảy ra nếu 2 kiểu này khác nhau.
Thật chất sẽ tìm kiếm xem kiểu E1
có thực hiện một trait Into<E2>
không. Nếu có nó gọi hàm thực hiện trait này để biến đổi kiểu dữ liệu sang.
- Sử dụng
thiserror
để có thể
Chương 1: Một số thư viện chuyên về xử lý lỗi
- thiserror
- anyhow
- snafu
- miette ...
thiserror
Là thư viện giúp cung cấp derive macro
để thự hiện std::error:Error
một cách tự động
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
- Chi tiết
#[error("{var}")] ⟶ write!("{}", self.var)
#[error("{0}")] ⟶ write!("{}", self.0)
#[error("{var:?}")] ⟶ write!("{:?}", self.var)
#[error("{0:?}")] ⟶ write!("{:?}", self.0)
Một số keyword: #[source] #[backtrace], #[error(transparent)], #[from]
Tham khảo link