1 tháng học Rust - Xử lý lỗi

1 tháng học Rust - Xử lý lỗi

Những mẩu tin ngắn hàng ngày dành cho bạn
  • countless.dev là một trang web khá thú vị khi mà nó so sánh giá tiền sử dụng các mô hình LLMs của các nhà cung cấp khác nhau.

    Tại đây bạn có thể nhìn thấy tất cả các mô hình ngôn ngữ lớn phổ biến bởi các nhà cung cấp như OpenAI, Azure, Mistral... Bảng giá cho mỗi 1M tokens đầu vào/ra. Hoặc thậm chí có thể so sánh chúng với nhau để tìm ra nhà cung cấp hoặc mô hình rẻ nhất tuỳ theo mục đích sử dụng.

    » Xem thêm
  • 1,2 năm trước, Kubernetes (k8s) tự nhiên được nhắc đến như một hiện tượng, chắc vì nó bá quá nên ai cũng muốn học và sử dụng. Nó là một công cụ "Automating deployment, scaling, and management of containerized applications" - Vâng! nghe hay ho đấy chứ 🤤.

    Hồi đó thì mình đang đam mê với Docker, đặc biệt là Docker Swarm, cũng tương tự như k8s ý nhưng ở quy mô nhỏ hơn. Docker Swarm thì có vẻ ít phức tạp hơn nhiều so với k8s. Mà như thế cũng tốt vì nó đã và đang đáp ứng rất tốt nhu cầu sử dụng của mình lúc đó, lại còn bớt đi phần phức tạp, lằng nhằng.

    Ấy thế mà 1-2 tháng trở lại đây, các bài viết có tiêu đề "bạn có thực sự cần đến Kubernetes" lại đang nổi lên với tần suất dày hơn. Quả thật k8s rất mạnh nhưng cũng quá phức tạp. Tại sao phải cố dùng dao "mổ trâu để giết gà" cơ chứ? Trừ khi bạn lường trước được độ phức tạp khi muốn áp dụng một công nghệ. Một cái nữa k8s tiêu tốn tài nguyên và nguồn lực ghê ghớm, để vận hành được nó không đơn giản là dựng lên được là xong mà còn phải có rất nhiều nhiều kiến thức nữa 😨.

    À, chắc cũng một phần nữa là do các "ông lớn" đang tập trung đẩy mạnh vào Serverless, giảm bớt sự phức tạp trong khâu vận hành đi, thay vào đó là nên tập trung vào phát triển ứng dụng.

    Bên cạnh đó, thì cái tên WASM cũng đang được nhắc đến rất là nhiều 🤔

    Do you really need Kubernetes in your company/startup? | dev.to

    Do You Really Need Kubernetes?

    » Xem thêm
  • Trước mình cứ khen lấy khen để Serverless, rằng tối ưu chi phí xuống 0đ để duy trì blog các thứ. Đúng là như vậy thật! Nhưng bên cạnh đó serverless cũng có các mặt tối đáng để lưu tâm đấy!

    Hôm kia mình phải mất ngày trời để truy tìm và khắc phục sự cố chỉ vì gọi hàm build-in của Cloudflare KV. Cụ thể là hàm list với limit 1000 - tức là một lần gọi nó trả về 1000 keys của KV. Cơ mà đời không như là mơ. Con số 1000 chỉ là trên lý thuyết. Lúc thì trả về vài trăm, lúc thì vài chục, thậm chí lúc thì lẹt đẹt có vài cái. Thế là làm tắc nghẽn cả hệ thống. À mà cũng không phải là nghẽn mà là hệ thống "nhàn rỗi" quá không có việc gì để làm, trong khi thực tế đáng ra nó phải xử lý cả trăm ngàn cái keys cơ 🥲

    » Xem thêm

Vấn đề

Xử lý lỗi là một công đoạn quan trọng mà không thể thiếu trong lập trình. Một chương trình có tốt hay không phụ thuộc rất nhiều vào việc này. Khi một tính năng được xử lý lỗi đúng cách nó có thể giúp cho lập trình viên giảm tải được nhiều rủi ro sau này.

Một lỗi xảy ra thường do nhiều nguyên nhân, cả khách quan lẫn chủ quan. Lỗi xảy ra do người lập trình vô ý bỏ sót trường hợp, lỗi chính tả, lỗi do không lường trước được sự chồng chéo logic... cho đến những lỗi từ phần cứng, phần mềm, kết nối mạng... Nói như thế để cho bạn đọc hình dung lỗi luôn chờ trực cơ hội để được bung ra ngay lập tức.

Trong Rust, chúng ta có 2 dạng lỗi là: Lỗi không thể phục hồi và Lỗi có thể phục hồi.

Lỗi không thể phục hồi

Lỗi không thể phục hồi để ám chỉ khi gặp lỗi này, Rust sẽ ngay lập tức dừng chương trình, dọn dẹp sạch sẽ bộ nhớ và in ra thông báo lỗi kèm vị trí chính xác nơi xảy ra lỗi.

panic! là một macro tạo ra lỗi không thể phục hồi. Khi gọi panic!, tức là chúng ta muốn dừng chương trình ngay lập tức.

Thông thường các lỗi panic nên được đẩy ra khi có hành vi nguy hiểm đến chương trình. Ví dụ như truy cập vào một thuộc tính không có sẵn:

fn main() {
    let v = vec![1, 2, 3];

    v[99];
}

Rõ ràng vị trí thứ 99 không tồn tại trong v, nên điều hợp lý nhất ở đây là Rust sẽ tự đẩy ra một lỗi panic cho hành vi này.

Thực tế, chúng ta vẫn có thể chủ động tạo ra lỗi panic đơn giản bằng cách gọi panic!.

fn main() {
    panic!("crash and burn");
}

Ngay lập tức chương trình sẽ bị dừng lại và chi tiết về lỗi cũng như vị trí gây ra lỗi được trả về trong console.

   Compiling panic v0.1.0 (file:///projects/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Lỗi có thể phục hồi

Ngược lại với lỗi không thể phục hồi do đó là hành vi nguy hiểm, thì lỗi có thể phục hồi ám chỉ các lỗi có hướng để giải quyết, không nhất thiết phải dừng lại chương trình. Ví dụ như người dùng nhập vào một đoạn ký tự không hợp lệ, thay vì thoát thì có thể xuất hiện thông báo để yêu cầu họ nhập lại.

Result là một đối tượng tạo ra lỗi có thể phục hồi. Result là một enum gồm có Ok hoặc Err đại diện cho giá trị thành công và lỗi.

enum Result<T, E> {
    Ok(T),  
    Err(E),  
}

Lấy một ví dụ về hành vi mở một tệp tin trong Rust.

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,  
        Err(error) => panic!("Problem opening the file: {:?}", error),  
    };
}

File::open trả về một Result, nếu mở tệp thành công, Ok có giá trị, ngược lại thì Err chứa thông tin lỗi được trả về. Để biết được greeting_file_result nhận giá trị Ok hay Err, chúng ta cần dùng match để kiểm tra và xử lý.

Thông thường, có nhiều lý do để tạo ra lỗi như tệp không tồn tại, không có quyền đọc hoặc lỗi hệ hống... Nắm bắt được điều này, Rust cho phép tạo ra thêm các dạng của lỗi, được dùng để phân loại lỗi.

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,  
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,  
                Err(e) => panic!("Problem creating the file: {:?}", e),  
            },  
            other_error => {
                panic!("Problem opening the file: {:?}", other_error);
            }
        },  
    };
}

Cách trên xử lý lỗi hơi dài dòng, Rust cung cấp cơ chế rút gọn mã hơn đó là sử dụng unwrapexpect.

  • unwrap trả về panic! nếu lỗi.
  • expect tương tự như unwrap, nhưng trả về được thêm thông báo lỗi do người dùng chỉ định.
use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt").unwrap();
    # or
    let greeting_file = File::open("hello.txt")
        .expect("hello.txt should be included in this project");
}

Xử lý lỗi lan truyền

Khi tạo ra một hàm, thay vì xử lý lỗi ngay trong hàm thì Rust có một cách phổ biến hơn là trả về một đối tượng Result. Khi đó lỗi được chuyển sang xử lý ở nơi gọi hàm, đây cũng là một cách giúp mã dễ bảo trì hơn vì tránh được các cuộc gọi panic! ở khắp nơi trong chương trình.

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,  
        Err(e) => return Err(e),  
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),  
        Err(e) => Err(e),  
    }
}

Như trong ví dụ trên, read_username_from_file trả về một Result.

Nhưng vẫn có quá nhiều mã được viết ra trong ví dụ trên, có thể thấy chúng ta đang phải kiểm tra giá trị OkErr liên tục bằng cách dùng match. Hơi rắc rối đúng không? Hãy làm nó ngắn lại bằng cách sử dụng toán tử ?.

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username)
}

Ở đây việc đặt dấu ? cam kết cho giá trị Ok, nếu bất cứ chỗ nào đẩy ra panic, chương trình sẽ dừng lại với một thông báo lỗi panic.

Cao cấp
Hello

5 bài học sâu sắc

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Mỗi sản phẩm đi kèm với những câu chuyện. Thành công của người khác là nguồn cảm hứng cho nhiều người theo sau. 5 bài học rút ra được đã thay đổi con người tôi mãi mãi. Còn bạn? Hãy bấm vào ngay!

Xem tất cả

Đăng ký nhận thông báo bài viết mới

hoặc
* Bản tin tổng hợp được gửi mỗi 1-2 tuần, huỷ bất cứ lúc nào.
Author

Xin chào, tôi tên là Hoài - một anh Dev kể chuyện bằng cách viết ✍️ và làm sản phẩm 🚀. Với nhiều năm kinh nghiệm lập trình, tôi đã đóng góp một phần công sức cho nhiều sản phẩm mang lại giá trị cho người dùng tại nơi đang làm việc, cũng như cho chính bản thân. Sở thích của tôi là đọc, viết, nghiên cứu... Tôi tạo ra trang Blog này với sứ mệnh mang đến những bài viết chất lượng cho độc giả của 2coffee.dev.Hãy theo dõi tôi qua các kênh LinkedIn, Facebook, Instagram, Telegram.

Bạn thấy bài viết này có ích?
Không

Bình luận (0)

Nội dung bình luận...