Về enums - một cấu trúc dữ liệu liệt kê mà có thể ai cũng biết. Enums cho phép xác định một loại bằng cách liệt kê các biến thể có thể có của nó. Trong mỗi ngôn ngữ lập trình, enums có cách khai báo và sử dụng khác nhau. Đối với Rust, enums được sử dụng khá thường xuyên vì nhiều lợi ích mà nó mang lại.
Bài viết ngày hôm nay chúng ta sẽ cùng nhau tìm hiểu về enums, xem nó có gì hay ho hơn so với các ngôn ngữ khác không nhé!
Một enums được khai báo như sau:
enum IpAddrKind {
V4,
V6,
}
Sau đó có thể sử dụng:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
Enums trong Rust cũng lưu trữ được dữ liệu.
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
Thậm chí, chúng ta còn có thể khai báo thêm phương thức cho nó.
enum IpAddr {
V4(String),
V6(String),
}
impl IpAddr {
fn call(&self) {
println!("Hello from IpAddr!");
}
}
let home = IpAddr::V4(String::from("127.0.0.1"));
home.call();
Option
là một enums được định nghĩa sẵn ở trong Rust, nó có một sứ mệnh quan trọng trong việc xử lý dữ liệu null
.
Một Option
có dạng Generic như sau.
enum Option<T> {
None,
Some(T),
}
Nhiều ngôn ngữ lập trình khác, chúng ta quá quen thuộc với kiểu dữ liệu null
đại diện cho việc không có giá trị nào. Rust không có kiểu dữ liệu null
, hầu hết trường hợp cần sử dụng null
thì sẽ chuyển qua sử dụng enums Option
.
None
đại diện cho “không có gì” còn Some
đại diện cho sự hiện hữu của một dạng dữ liệu nào đó. Lấy ví dụ.
let none: Option<i8> = None;
let five: Option<i8> = Some(5);
let six: Option<i8> = Sone(6);
Và dĩ nhiên chúng ta không thể thực hiện các phép tính trực tiếp trên kiểu dữ liệu Option này một cách bình thường.
// error
let sum = none + five;
// error
let sum = five + six;
Vậy thì làm cách nào để sử dụng được enums nói chung cũng như Option
nói riêng?
Hầu hết việc xác định enums nhằm mục đích phân loại và xử lý dữ liệu theo các dạng được định nghĩa từ trước, chính vì thế Rust có cấu trúc xử lý loại rất mạnh mẽ là match
hay còn gọi là “khớp mẫu”.
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
Hàm value_in_cents
nhận vào một enums có kiểu Coin
từ đó trả về giá trị tương ứng với tên của các đồng xu bằng cách sử dụng cú pháp match
.
Đối với Option
, chúng ta cũng sử dụng match
để xử lý các trường hợp None
và Some
, cũng như thực hiện các phép tính cơ bản mà Some
nắm giữ như ví dụ ở đầu bài viết.
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
Hàm plus_one
nhận vào một Option<i32>
và cộng thêm 1
vào giá trị của nó. Bằng cách khớp mẫu, hàm trả về None
nếu như x
là None, ngược lại nếu x
là loại Some
, tức là nó có giá trị thì sẽ trả về giá trị Some
mới bằng cách cộng thêm 1
.
Cho dễ hình dung, match
gần giống với lệnh swich…case
trong JavaScript. Trong khi switch
đưa ra một giá trị khi nào khớp với giá trị trong case
thì hàm xử lý đó ngay lập tức được thực thi. Nếu như switch…case
có trường hợp default
, nghĩa là khi các giá trị trong tất cả case
không khớp thì cuối cùng hàm xử lý default
được thực hiện. Trong Rust cũng có, người ta gọi là “Placeholder” hay “trình giữ chỗ”.
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
Quay trở lại với trường hợp sử dụng Option
, đôi khi chúng ta không quan tâm đến None
lắm mà chỉ đơn giản là muốn thao tác với dữ liệu nếu có của Some
. Ví dụ dưới đây luôn phải khai báo thêm một trình giữ chỗ để “không phải làm gì”.
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
Điều này lặp lại một cách nhàm chán và thừa thãi, thay vào đó, Rust gợi ý chúng ta sử dụng kết hợp if
và let
để chỉ xử lý riêng cho một trường hợp cụ thể của một enums.
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
Cuối cùng, vẫn có một lưu ý quan trọng về quyền sở hữu khi khớp mẫu khi Some
giữ các giá trị tham chiếu. Bạn đọc có thể tham khảo thêm tại The Rust Programming Language - How Matches Interact with Ownership.
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!
Đăng ký nhận thông báo bài viết mới
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ình luận (1)