Cộng đồng lập trình Rust đang tích cực tranh luận về việc có nên từ bỏ tiêu chuẩn hiện tại sử dụng enum lỗi rộng để chuyển sang các phương pháp xử lý lỗi chính xác hơn, đặc thù cho từng hàm. Cuộc thảo luận này đã thu hút sự chú ý khi các nhà phát triển tìm kiếm những cách tốt hơn để thể hiện các yêu cầu xử lý lỗi thông qua hệ thống kiểu dữ liệu của Rust .
Vấn đề hiện tại với Enum lỗi rộng
Hầu hết các dự án Rust ngày nay đều theo mô hình tạo một enum lỗi lớn cho mỗi module hoặc crate để bao phủ tất cả các trường hợp lỗi có thể xảy ra. Mặc dù cách tiếp cận này giảm thiểu code boilerplate, nhưng nó tạo ra một vấn đề đáng kể: các hàm trả về các kiểu lỗi bao gồm cả những biến thể mà chúng không bao giờ thực sự tạo ra. Điều này buộc các nhà phát triển phải tự xác định những biến thể lỗi nào có liên quan cho mỗi lần gọi hàm, thường dựa vào tài liệu có thể không đầy đủ hoặc đã lỗi thời.
Cộng đồng nhận ra đây là một vấn đề cơ bản làm suy yếu một trong những điểm mạnh cốt lõi của Rust - sử dụng hệ thống kiểu dữ liệu để ngăn chặn các lỗi lập trình. Khi các kiểu lỗi quá rộng, trình biên dịch không thể giúp các nhà phát triển hiểu được điều gì thực sự có thể xảy ra sai trong bối cảnh cụ thể của họ.
Kiểu lỗi đặc thù cho từng hàm nhận được sự ủng hộ
Ngày càng nhiều nhà phát triển ủng hộ việc tạo các kiểu lỗi riêng biệt cho từng hàm hoặc hành động. Cách tiếp cận này đảm bảo rằng mỗi hàm chỉ trả về những lỗi mà nó thực sự có thể tạo ra, làm cho code chính xác hơn và tự mô tả. Trình biên dịch sau đó có thể cung cấp hướng dẫn tốt hơn về những lỗi nào cần được xử lý trong từng tình huống.
Tuy nhiên, sự chính xác này đi kèm với một cái giá. Việc thêm các biến thể lỗi mới yêu cầu cập nhật toàn bộ chuỗi gọi hàm, điều này có thể tẻ nhạt trong quá trình phát triển tích cực. Một số nhà phát triển thấy sự đánh đổi này đáng giá vì trình biên dịch hiển thị rõ ràng nơi cần chú ý đến việc xử lý lỗi, ngăn chặn việc quên các trường hợp ngoại lệ.
So sánh các phương pháp xử lý lỗi:
Phương pháp | Ưu điểm | Nhược điểm |
---|---|---|
Enum lỗi toàn module | Ít boilerplate, dễ triển khai | Các hàm trả về các biến thể lỗi không liên quan |
Lỗi đặc thù cho từng hàm | Kiểu lỗi chính xác, tài liệu tốt hơn | Cần cập nhật toàn bộ chuỗi gọi hàm |
Bộ lỗi được tạo bằng macro | Chuyển đổi tự động, kết hợp toán học | Debug phức tạp, tạo code "ma thuật" |
Giải pháp bên thứ ba | Hệ sinh thái phong phú, giải pháp đã được chứng minh | Phụ thuộc thêm, phân mảnh |
Các giải pháp dựa trên Macro gặp phải sự phản kháng
Một số crate như error-set
và terrors
cố gắng giải quyết những vấn đề này bằng cách sử dụng macro để tự động tạo các kiểu lỗi chính xác. Những công cụ này cho phép các nhà phát triển định nghĩa các tập hợp lỗi kết hợp các biến thể cụ thể với các hợp của các tập hợp lỗi khác, tạo ra một cách tiếp cận toán học hơn cho việc kết hợp lỗi.
Mặc dù có khả năng kỹ thuật, những giải pháp dựa trên macro này gặp phải sự phản kháng đáng kể từ cộng đồng. Nhiều nhà phát triển bày tỏ sự mệt mỏi với macro và thích các cách tiếp cận đơn giản không dựa vào magic tạo code. Mối quan tâm là việc sử dụng macro nặng làm cho codebase khó hiểu và debug hơn, tương tự như các vấn đề thấy trong các ngôn ngữ động.
Tôi đang hơi mệt mỏi với macro trong Rust . Theo quan điểm khiêm tốn của tôi, càng ít sử dụng magic trong codebase thì càng tốt.
Các Crate Xử Lý Lỗi Phổ Biến Trong Rust:
anyhow
- Xử lý lỗi đa năng cho các ứng dụngthiserror
- Macro derive cho các kiểu lỗi tùy chỉnherror-set
- Tổ hợp tập hợp lỗi dựa trên macroterrors
- Quản lý tập hợp lỗi ở cấp độ kiểuSmartErr
- Mô hình xử lý lỗi thay thế
Hạn chế của thư viện chuẩn thúc đẩy sự phụ thuộc vào bên thứ ba
Cuộc tranh luận làm nổi bật một vấn đề rộng hơn với cách tiếp cận phát triển thư viện chuẩn của Rust . Không giống như các ngôn ngữ bao gồm hỗ trợ xử lý lỗi toàn diện và async runtime, Rust yêu cầu hầu hết các dự án phải phụ thuộc vào các crate bên thứ ba như anyhow
, thiserror
, và tokio
cho chức năng cơ bản.
Mặc dù cách tiếp cận này cho phép đổi mới hệ sinh thái và thử nghiệm với các giải pháp khác nhau, nhưng nó cũng có nghĩa là mọi dự án Rust phải đưa ra các quyết định kiến trúc cơ bản về xử lý lỗi trước khi viết code đáng kể. Một số nhà phát triển lo lắng về việc phình to dependency, đặc biệt khi các dự án đơn giản cuối cùng phải build hàng chục crate cho chức năng cơ bản.
Nhìn về tương lai
Cuộc thảo luận đang diễn ra phản ánh thách thức rộng hơn của Rust trong việc cân bằng giữa tính an toàn kiểu dữ liệu với khả năng sử dụng thực tế. Mặc dù cộng đồng đồng ý rằng các cách tiếp cận xử lý lỗi hiện tại có những khuyết điểm đáng kể, nhưng không có sự đồng thuận về con đường tốt nhất phía trước. Một số nhà phát triển tiếp tục thúc đẩy các kiểu lỗi chính xác hơn, đặc thù cho từng hàm mặc dù phải làm việc thêm, trong khi những người khác thích các cách tiếp cận đơn giản hơn sử dụng các công cụ hiện có như anyhow
cho code ứng dụng.
Cuộc tranh luận cũng tiết lộ những câu hỏi sâu sắc hơn về việc liệu cách tiếp cận cơ bản của Rust đối với xử lý lỗi thông qua các kiểu Result
có phải là lựa chọn đúng hay không, với một số người gợi ý rằng các hệ thống dựa trên exception với thông tin ngữ cảnh phong phú có thể tốt hơn cho sự phát triển dài hạn của ngôn ngữ.
Tham khảo: On Error Handling in Rust