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ụng match để 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ụng unwrapexpect
    • Đặc tính của hàmunwrap()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.
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::Errorf.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

anyhow

snafu

miette

Chương 2: Kiến thức về xử lý lỗi trong Rust?

Chương 3: Thực hành

Chương 3: Bài tập về nhà