Struct trong Rust
Struct gom các giá trị liên quan thành một kiểu có tên. Nếu tuple là một gói nhanh, không tên, thì struct đặt tên cho từng field — khiến code dùng nó dễ đọc hơn rất nhiều.
Định nghĩa và tạo struct
Phần tiêu đề “Định nghĩa và tạo struct”struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{} {} tuổi", alice.name, alice.age);}Kết quả:
Alice 30 tuổiSo với tuple ("Alice", 30) — với struct, alice.name và alice.age tự giải thích ý nghĩa. Bạn không phải nhớ rằng phần tử 0 là tên và phần tử 1 là tuổi.
Làm cho struct khả biến
Phần tiêu đề “Làm cho struct khả biến”Giống như biến, các instance (thể hiện) của struct mặc định là bất biến. Thêm mut để thay đổi một field sau khi tạo:
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{} giờ đã {} tuổi", alice.name, alice.age);}Thêm hành vi bằng phương thức
Phần tiêu đề “Thêm hành vi bằng phương thức”Bạn có thể gắn hàm vào struct bằng một khối impl. Bên trong, &self chỉ chính instance mà phương thức được gọi trên đó:
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("Chào, mình là {} và mình {} tuổi", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}Kết quả:
Chào, mình là Alice và mình 30 tuổiHàm kiểu constructor
Phần tiêu đề “Hàm kiểu constructor”Một mẫu phổ biến là hàm new bên trong impl để tạo instance giúp bạn, nhờ đó nơi gọi không phải tự viết ra từng field:
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{} {} tuổi", bob.name, bob.age);}Để ý age đứng một mình bên trong Person { name: name.to_string(), age } — khi một biến có tên trùng khớp với tên field, bạn có thể bỏ qua việc viết age: age mà chỉ cần viết age. Rust gọi đây là field init shorthand (cách viết tắt khi khởi tạo field).
In struct với derive
Phần tiêu đề “In struct với derive”Thử println!("{:?}", alice) trên struct như đã viết ở trên, và nó sẽ không biên dịch — Rust không biết cách in debug một kiểu tự định nghĩa trừ khi bạn bảo nó. Thêm #[derive(Debug)] phía trên struct để có tính năng đó miễn phí:
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}Kết quả:
Person { name: "Alice", age: 30 }#[derive(...)] là cách Rust tự động sinh ra các chức năng thông dụng cho một kiểu — bạn sẽ thấy Debug, và sau này là Clone cùng nhiều trait khác, được thêm theo cách này liên tục.
Luyện tập
Phần tiêu đề “Luyện tập”1. Phương thức cho hình chữ nhật
Phần tiêu đề “1. Phương thức cho hình chữ nhật”Tạo struct Rectangle với các field width và height (u32). Cho nó hai phương thức: area, trả về diện tích, và is_square, trả về true khi hai cạnh bằng nhau. Gợi ý: cả hai phương thức chỉ đọc các field, nên chúng nhận &self.
Xem lời giải
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("hình dài: diện tích {}, vuông? {}", wide.area(), wide.is_square()); println!("hình vuông: diện tích {}, vuông? {}", square.area(), square.is_square());}Kết quả:
hình dài: diện tích 24, vuông? falsehình vuông: diện tích 25, vuông? true2. Tài khoản ngân hàng
Phần tiêu đề “2. Tài khoản ngân hàng”Tạo struct Account với owner (String) và balance tính bằng xu (u32). Viết Account::new("Ana") bắt đầu số dư ở 0. Sau đó nạp 50 và 25 xu bằng cách thay đổi field, rồi in tài khoản theo hai cách: bằng một câu bình thường và bằng {:?}. Gợi ý: biến phải là let mut thì bạn mới thay đổi được account.balance, và {:?} cần #[derive(Debug)].
Xem lời giải
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{} có {} xu", account.owner, account.balance); println!("{:?}", account);}Kết quả:
Ana có 75 xuAccount { owner: "Ana", balance: 75 }new tự đặt balance: 0, nên nơi gọi không thể quên. Đó là một phần lớn lý do các hàm constructor phổ biến đến vậy.
3. Danh sách người
Phần tiêu đề “3. Danh sách người”Đặt ba giá trị Person vào một vector: Ana (34), Ben (16) và Chen (52). In tên của mọi người từ 18 tuổi trở lên, rồi in ra ai lớn tuổi nhất. Gợi ý: duyệt bằng for person in &people, và theo dõi người lớn tuổi nhất cho đến lúc đó bằng let mut oldest = &people[0];.
Xem lời giải
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} là người trưởng thành", person.name); } if person.age > oldest.age { oldest = person; } }
println!("Lớn tuổi nhất: {} ({})", oldest.name, oldest.age);}Kết quả:
Ana là người trưởng thànhChen là người trưởng thànhLớn tuổi nhất: Chen (52)oldest là một tham chiếu (&Person) trỏ vào vector, không phải bản sao, nên không có gì bị clone. Mỗi khi tìm thấy người lớn tuổi hơn, ta chỉ việc cho oldest trỏ sang người đó.
Tóm tắt
Phần tiêu đề “Tóm tắt”- Struct gom các field có tên thành một kiểu:
struct Person { name: String, age: u32 }. - Tạo instance bằng
Person { name: ..., age: ... }; đánh dấumutđể thay đổi field về sau. - Thêm hành vi bằng khối
impl;&selfchỉ instance mà phương thức được gọi trên đó. - Thêm
#[derive(Debug)]để in được struct bằng{:?}.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Ưu điểm chính của struct so với tuple là gì?
2. Thêm #[derive(Debug)] vào struct cho phép bạn làm gì?
3. Trong Person { name: name.to_string(), age }, chữ age đứng một mình có tác dụng gì?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- Defining and Instantiating Structs — The Rust Book (tiếng Anh): còn nói về cú pháp cập nhật struct (
..other), tuple struct và unit-like struct. - An Example Program Using Structs — The Rust Book (tiếng Anh): viết lại một chương trình hình chữ nhật từ các biến rời rạc sang tuple rồi sang struct, để bạn thấy vì sao struct hữu ích.
- Method Syntax — The Rust Book (tiếng Anh):
&selfso với&mut self, phương thức có thêm tham số, và hàm liên kết (associated function) nhưnew. - Structures — Rust by Example (tiếng Anh): ví dụ chạy được về mọi loại struct, với một bài tập nhỏ ở cuối.
- Derive — Rust by Example (tiếng Anh): những gì
#[derive(...)]có thể sinh ra cho bạn, nhưDebug,ClonevàPartialEq.