Bỏ qua để đến nội dung

Option trong Rust

Rust không có null. Nếu một giá trị có thể không có mặt, điều đó được thể hiện thẳng trong kiểu bằng Option. Lúc đầu có vẻ hơi rườm rà, nhưng nó loại bỏ cả một nhóm lỗi: “tham chiếu null”, thủ phạm của vô số lần sập chương trình ở các ngôn ngữ khác.

Option là một enum có hai biến thể (variant):

enum Option<T> {
Some(T), // có một giá trị
None, // không có giá trị
}

Thật ra bạn đã gặp nó rồi: .get() trên vector và .get() trên HashMap đều trả về một Option.

fn main() {
let has_value: Option<i32> = Some(5);
let no_value: Option<i32> = None;
println!("{:?}", has_value);
println!("{:?}", no_value);
}

Kết quả:

Some(5)
None
▶ Tự chạy thử

Cách an toàn để lấy giá trị ra là match. Rust buộc bạn xử lý cả hai trường hợp, nên bạn không thể vô tình quên trường hợp “không có”:

fn main() {
let age: Option<i32> = Some(30);
match age {
Some(value) => println!("Tuổi là {value}"),
None => println!("Không có thông tin tuổi"),
}
}
▶ Tự chạy thử
fn find_user(id: u32) -> Option<&'static str> {
if id == 1 {
Some("Alice")
} else {
None
}
}
fn main() {
match find_user(1) {
Some(name) => println!("Tìm thấy: {name}"),
None => println!("Không tìm thấy người dùng"),
}
match find_user(99) {
Some(name) => println!("Tìm thấy: {name}"),
None => println!("Không tìm thấy người dùng"),
}
}

Kết quả:

Tìm thấy: Alice
Không tìm thấy người dùng
▶ Tự chạy thử

Thay vì trả về null hoặc ném ngoại lệ khi không tìm thấy người dùng, chính kiểu trả về của hàm đã nói với mọi nơi gọi nó rằng “có thể không tìm thấy gì”, và trình biên dịch không cho họ bỏ qua khả năng đó.

Với trường hợp đơn giản, viết cả một match có thể thấy hơi thừa. Có vài lối tắt:

fn main() {
let age: Option<i32> = None;
let value = age.unwrap_or(0); // dùng 0 nếu không có giá trị
println!("{value}");
}
▶ Tự chạy thử

Còn có .unwrap(), trả thẳng giá trị cho bạn, nhưng sẽ panic (sập chương trình) nếu đó là None:

let age: Option<i32> = Some(30);
let value = age.unwrap(); // ổn ở đây, vì age là Some

Hãy viết fn first_even(numbers: &[i32]) -> Option<i32> trả về số chẵn đầu tiên trong slice, hoặc None nếu không có. In kết quả cho [3, 7, 8, 10] bằng match, và cho [1, 3, 5] bằng .unwrap_or(0). Gợi ý: duyệt slice và return Some(n) ngay khi n % 2 == 0; sau vòng lặp thì trả về None.

Xem lời giải
fn first_even(numbers: &[i32]) -> Option<i32> {
for &n in numbers {
if n % 2 == 0 {
return Some(n);
}
}
None
}
fn main() {
match first_even(&[3, 7, 8, 10]) {
Some(n) => println!("Số chẵn đầu tiên: {n}"),
None => println!("Không có số chẵn"),
}
let fallback = first_even(&[1, 3, 5]).unwrap_or(0);
println!("Có giá trị dự phòng: {fallback}");
}

Kết quả:

Số chẵn đầu tiên: 8
Có giá trị dự phòng: 0
▶ Tự chạy thử

Hãy tạo một HashMap chứa các thiết lập, trong đó chỉ có "volume" được đặt là 80. Rồi duyệt qua các khoá "volume" và "brightness", in giá trị nếu có, hoặc in thông báo “chưa được đặt” nếu không có. Gợi ý: .get(key) trên HashMap trả về một Option, nên bạn có thể match trực tiếp trên nó.

Xem lời giải
use std::collections::HashMap;
fn main() {
let mut settings = HashMap::new();
settings.insert("volume", 80);
for key in ["volume", "brightness"] {
match settings.get(key) {
Some(value) => println!("{key} = {value}"),
None => println!("{key} chưa được đặt"),
}
}
}

Kết quả:

volume = 80
brightness chưa được đặt
▶ Tự chạy thử
  • Option<T> biểu diễn một giá trị có thể có hoặc không: Rust không có null.
  • Some(value) chứa một thứ gì đó; None nghĩa là không có gì.
  • match là cách an toàn để xử lý cả hai trường hợp; trình biên dịch không cho bạn quên trường hợp nào.
  • .unwrap_or(default) cho bạn một giá trị dự phòng; .unwrap() panic nếu giá trị là None, nên hãy dùng thật dè dặt.

Kiểm tra nhanh

1. Rust dùng gì thay cho null với một giá trị có thể bị thiếu?

2. Chuyện gì xảy ra khi bạn gọi .unwrap() trên một giá trị None?

3. age.unwrap_or(0) làm gì khi age là None?

4. Vì sao match được coi là cách an toàn để lấy giá trị ra khỏi Option?

5. Những phương thức nào được nhắc đến trên trang này vốn đã trả về Option?

Điểm: 0 / 5