Tuple trong Rust
Tuple gom nhiều giá trị, có thể thuộc các kiểu khác nhau, lại thành một. Đây là cách nhanh để gói những thứ liên quan với nhau mà không cần định nghĩa hẳn một kiểu mới.
Tạo một tuple
Phần tiêu đề “Tạo một tuple”fn main() { let person: (&str, i32, bool) = ("Alice", 30, true); println!("{:?}", person);}Kết quả:
("Alice", 30, true)Để ý rằng tuple này trộn một &str, một i32 và một bool trong cùng một giá trị — điều mà mảng không làm được, vì mọi phần tử trong mảng phải cùng kiểu.
Để in cả một tuple, hãy dùng {:?} (định dạng debug) thay vì {}. {} thông thường không dùng được cho tuple.
Truy cập phần tử theo vị trí
Phần tiêu đề “Truy cập phần tử theo vị trí”Dùng .0, .1, .2, và cứ thế — vị trí trong tuple được đánh số từ 0:
fn main() { let person = ("Alice", 30, true);
println!("Tên: {}", person.0); println!("Tuổi: {}", person.1); println!("Đang hoạt động: {}", person.2);}Kết quả:
Tên: AliceTuổi: 30Đang hoạt động: trueVị trí phải là một con số viết thẳng trong code. Bạn không thể dùng biến, như person.i, vì mỗi vị trí có thể chứa một kiểu khác nhau, và Rust phải biết kiểu ngay lúc biên dịch.
Destructuring một tuple
Phần tiêu đề “Destructuring một tuple”Bạn có thể tách mọi giá trị ra thành từng biến riêng chỉ trong một dòng (gọi là destructuring, “phân rã”):
fn main() { let person = ("Alice", 30, true); let (name, age, is_active) = person;
println!("{name} {age} tuổi, đang hoạt động: {is_active}");}Kết quả:
Alice 30 tuổi, đang hoạt động: trueCách này thường dễ đọc hơn việc lặp đi lặp lại .0, .1, .2, nhất là khi tuple có hơn hai ba phần tử.
Trả về nhiều giá trị từ một hàm
Phần tiêu đề “Trả về nhiều giá trị từ một hàm”Tuple là cách tự nhiên để trả về nhiều hơn một giá trị, vì một hàm chỉ khai báo được một kiểu trả về:
fn min_max(numbers: &[i32]) -> (i32, i32) { let min = *numbers.iter().min().unwrap(); let max = *numbers.iter().max().unwrap(); (min, max)}
fn main() { let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]); println!("nhỏ nhất: {smallest}, lớn nhất: {largest}");}Kết quả:
nhỏ nhất: 1, lớn nhất: 9Đừng bận tâm về &[i32], .iter() hay .unwrap() lúc này. Chúng được giải thích trong các bài Slice, Iterator và Option. Điều cần để ý ở đây là kiểu trả về (i32, i32) và cách main tách nó ngay thành hai biến.
Thay đổi tuple và hoán đổi giá trị
Phần tiêu đề “Thay đổi tuple và hoán đổi giá trị”Nếu tuple được khai báo với mut, bạn có thể thay đổi từng phần tử của nó, dùng cùng các vị trí .0, .1. Giá trị thì thay đổi được, nhưng hình dạng thì không: một tuple luôn giữ nguyên số lượng và kiểu của các phần tử như lúc ban đầu.
Destructuring cũng hoạt động theo chiều ngược lại: bạn có thể gán vào các biến đã có. Nhờ đó bạn có cách hoán đổi gọn trong một dòng, không cần biến tạm. Nếu chỉ muốn lấy vài phần tử, hãy viết _ cho những phần tử bạn không cần:
fn main() { let mut point = (3, 4); point.0 += 10; // thay đổi một phần tử tại chỗ println!("point là {:?}", point);
let mut a = 1; let mut b = 2; (a, b) = (b, a); // hoán đổi hai giá trị println!("a = {a}, b = {b}");
let (x, _) = point; // giữ x, bỏ qua phần tử thứ hai println!("x = {x}");}Kết quả:
point là (13, 4)a = 2, b = 1x = 13Phép hoán đổi chạy được vì Rust dựng vế phải, (b, a), trước. Chỉ sau đó nó mới tách ra vào a và b. Bạn còn làm được nhiều điều hơn nữa với _ và mẫu trong bài So khớp mẫu.
Tuple rỗng: ()
Phần tiêu đề “Tuple rỗng: ()”Thật ra bạn đã gặp một tuple mà không nhận ra. () được gọi là kiểu unit, và đó là thứ một hàm trả về khi nó không trả về gì có ý nghĩa — nó là kiểu trả về ngầm định của fn main().
Khi nào dùng tuple, khi nào dùng struct
Phần tiêu đề “Khi nào dùng tuple, khi nào dùng struct”Tuple rất hợp cho những nhóm nhanh, không cần tên — đặc biệt là trả về hai ba giá trị từ một hàm. Khi một nhóm giá trị biểu diễn một khái niệm thật sự với các field có tên (như một Person có tên và tuổi mà bạn sẽ gọi bằng tên khắp nơi trong code), một struct thường là lựa chọn tốt hơn và dễ đọc hơn.
Luyện tập
Phần tiêu đề “Luyện tập”1. Tổng và tích
Phần tiêu đề “1. Tổng và tích”Viết hàm sum_and_product(a: i32, b: i32) -> (i32, i32) trả về cả tổng lẫn tích của hai số. Trong main, gọi nó với 5 và 6 rồi destructuring kết quả. Gợi ý: dòng cuối của hàm chính là tuple, (a + b, a * b), không có dấu chấm phẩy.
Xem lời giải
fn sum_and_product(a: i32, b: i32) -> (i32, i32) { (a + b, a * b)}
fn main() { let (sum, product) = sum_and_product(5, 6); println!("tổng = {sum}, tích = {product}");}Kết quả:
tổng = 11, tích = 302. Hình chữ nhật từ hai góc
Phần tiêu đề “2. Hình chữ nhật từ hai góc”Một hình chữ nhật có thể lưu dưới dạng tuple gồm hai điểm, trong đó mỗi điểm lại là một tuple: let rect = ((1, 2), (5, 5));. Hãy tính chiều rộng, chiều cao và diện tích của nó. Gợi ý: tuple có thể lồng nhau, và rect.1.0 nghĩa là “phần tử đầu tiên của điểm thứ hai”.
Xem lời giải
fn main() { let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0; let height = rect.1.1 - rect.0.1;
println!("rộng {width}, cao {height}, diện tích {}", width * height);}Kết quả:
rộng 4, cao 3, diện tích 12Bạn cũng có thể destructuring cả hai điểm cùng lúc: let ((x1, y1), (x2, y2)) = rect;. Gõ dài hơn, nhưng x2 - x1 dễ đọc hơn rect.1.0 - rect.0.0. Khi các dấu chấm bắt đầu chồng chất như thế này, đó là dấu hiệu một struct có field đặt tên sẽ rõ ràng hơn.
Tóm tắt
Phần tiêu đề “Tóm tắt”- Tuple gom các giá trị có thể khác kiểu:
(name, age, is_active). - Truy cập phần tử bằng
.0,.1,.2, hoặc tách tất cả ra cùng lúc bằng destructuring. - Tuple là cách tự nhiên để trả về nhiều giá trị từ một hàm.
- Phần tử của tuple
mutthay đổi được, nhưng kích thước và kiểu thì không.(a, b) = (b, a);hoán đổi hai giá trị. - Với những nhóm có tên và dùng lại nhiều lần, hãy dùng struct.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Tuple chứa được gì mà mảng không chứa được?
2. Làm sao truy cập phần tử thứ hai của tuple tên person?
3. Sau let mut a = 1; let mut b = 2; (a, b) = (b, a);, a và b là bao nhiêu?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- The Tuple Type — The Rust Book (tiếng Anh): phần giới thiệu chính thức về cách tạo, destructuring và truy cập tuple theo vị trí.
- Tuples — Rust by Example (tiếng Anh): các ví dụ chạy được với tuple lồng nhau và tuple làm đối số của hàm.
- Kiểu
tuple— tài liệu thư viện chuẩn (tiếng Anh): những trait mà tuple hỗ trợ, như so sánh, sao chép và in bằng{:?}. - Kiểu
unit— tài liệu thư viện chuẩn (tiếng Anh):()là gì và xuất hiện ở đâu. - Destructuring assignments — The Rust Reference (tiếng Anh): các quy tắc chính xác đằng sau
(a, b) = (b, a);.