Slice trong Rust
Slice cho phép bạn mượn một đoạn của mảng hoặc vector — một “khung nhìn” vào một phần của nó — mà không sao chép dữ liệu hay lấy quyền sở hữu. Nếu bạn đã dùng mượn (borrowing) với một giá trị đơn lẻ, thì slice là cùng ý tưởng đó áp dụng cho một dãy phần tử.
Cắt slice từ một mảng
Phần tiêu đề “Cắt slice từ một mảng”fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // các phần tử ở chỉ số 1, 2, 3 println!("{:?}", middle);}Kết quả:
[20, 30, 40]1..4 là cùng loại range bạn đã thấy trong vòng lặp for — nó bắt đầu ở chỉ số 1 và dừng trước chỉ số 4, nên bạn nhận được các chỉ số 1, 2 và 3.
Cách viết tắt range cho slice
Phần tiêu đề “Cách viết tắt range cho slice”Vài cách viết tắt hay gặp:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // từ đầu đến trước chỉ số 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // từ chỉ số 2 đến hết -> [30, 40, 50] println!("{:?}", &numbers[..]); // toàn bộ -> [10, 20, 30, 40, 50]}String slice
Phần tiêu đề “String slice”Thật ra bạn đã dùng slice từ trước — &str là một slice của String (hoặc của một chuỗi khác)! Đó là lý do hai kiểu này phối hợp với nhau ăn ý đến vậy:
fn main() { let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3]; println!("{first_word}"); // "The"}Câu ví dụ được giữ nguyên tiếng Anh vì [0..3] cắt theo byte, không theo chữ cái: mỗi chữ tiếng Việt có dấu chiếm 2–3 byte, nên cắt giữa chừng một chữ như vậy sẽ gây panic.
Vì sao dùng slice thay vì sao chép?
Phần tiêu đề “Vì sao dùng slice thay vì sao chép?”Bạn có thể nghĩ đến việc giải quyết chuyện này bằng cách sao chép ra một Vec hay String mới cho đoạn mình cần. Slice tránh hoàn toàn việc đó — nó chỉ trỏ vào một phần của dữ liệu sẵn có, giống như một tham chiếu (&) trỏ vào một giá trị đơn lẻ. Nghĩa là không cấp phát thêm, không sao chép, và các quy tắc mượn mà bạn đã biết từ bài Mượn vẫn áp dụng.
Hàm nhận một slice
Phần tiêu đề “Hàm nhận một slice”Slice đặc biệt hữu ích khi làm tham số của hàm, vì nó chấp nhận cả mảng lẫn vector:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] chấp nhận slice từ mảng hoặc từ Vec — một hàm duy nhất, cả hai chỗ gọi đều chạy được.
Thay đổi dữ liệu qua slice khả biến
Phần tiêu đề “Thay đổi dữ liệu qua slice khả biến”Đến giờ, slice của chúng ta chỉ đọc dữ liệu. Một slice khả biến (mutable slice), &mut [T], cho phép một hàm thay đổi những phần tử nó được giao, nhưng chỉ những phần tử đó:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // chỉ chỉ số 1 và 2 println!("{:?}", scores);
double_all(&mut scores); // cả vector println!("{:?}", scores);}Kết quả:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]Lần gọi đầu truyền &mut scores[1..3], nên double_all chỉ thấy chỉ số 1 và 2. Mọi thứ bên ngoài đoạn đó đều an toàn trước nó. Bên trong hàm, slice lại bắt đầu từ chỉ số 0: với double_all, nó chỉ là một danh sách gồm hai số.
Điều này an toàn nhờ một quy tắc mà bạn sẽ học kỹ trong bài Mượn: khi hàm đang giữ một slice &mut, không thứ gì khác được dùng các phần tử đó, nên hai phần code của bạn không bao giờ thay đổi chúng cùng lúc. Vector cũng phải được khai báo mut, nếu không Rust sẽ không cho bạn mượn nó theo kiểu khả biến.
Luyện tập
Phần tiêu đề “Luyện tập”1. Chia đôi
Phần tiêu đề “1. Chia đôi”Tạo vector vec![3, 6, 9, 12, 15, 18]. In nửa đầu và nửa sau của nó thành hai slice riêng. Gợi ý: tính let mid = numbers.len() / 2; và dùng ..mid cùng mid...
Xem lời giải
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("Nửa đầu: {:?}", first_half); println!("Nửa sau: {:?}", second_half);}Kết quả:
Nửa đầu: [3, 6, 9]Nửa sau: [12, 15, 18]Vì mid được tính từ len(), code vẫn chạy đúng nếu bạn thêm hay bớt số. Với độ dài lẻ, phần tử dư sẽ nằm ở nửa sau.
2. Số lớn nhất trong mọi danh sách
Phần tiêu đề “2. Số lớn nhất trong mọi danh sách”Viết hàm largest(numbers: &[i32]) -> i32 trả về số lớn nhất. Gọi nó với một mảng, một vector, và một slice chỉ gồm phần tử đầu tiên của vector. Gợi ý: bắt đầu với numbers[0] rồi duyệt qua phần còn lại, như bạn làm với mảng.
Xem lời giải
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Kết quả:
93510Duyệt qua một slice cho bạn tham chiếu đến từng phần tử (&i32), chứ không phải bản thân con số. *n nghĩa là “giá trị mà n trỏ tới”, nên *n > biggest so sánh hai con số bình thường. Phiên bản này panic với slice rỗng, vì numbers[0] không tồn tại. Sau này bạn sẽ học cách trả về một Option cho trường hợp đó.
Tóm tắt
Phần tiêu đề “Tóm tắt”- Slice (
&[T]) mượn một đoạn của mảng hoặc vector mà không sao chép. 1..4chọn các chỉ số từ 1 đến 3; bỏ trống một bên của..để đi từ đầu hoặc đến hết.- Bản thân
&strlà một slice văn bản — vì thế chuỗi và slice có rất nhiều hành vi chung. - Ưu tiên
&[T]làm kiểu tham số của hàm khi bạn muốn nhận cả mảng lẫn vector. - Slice khả biến (
&mut [T]) cho phép hàm chỉ thay đổi những phần tử nó được giao.
Kiểm tra nhanh
Phần tiêu đề “Kiểm tra nhanh”Kiểm tra nhanh
1. Slice cho phép bạn làm gì với một phần của mảng hoặc vector?
2. &numbers[1..4] gồm những chỉ số nào?
3. Vì sao &[i32] là lựa chọn tốt cho kiểu tham số của hàm?
Điểm: 0 / 3
Đọc thêm
Phần tiêu đề “Đọc thêm”- The Slice Type — The Rust Book (tiếng Anh): dẫn dắt từng bước đến slice qua ví dụ “từ đầu tiên của một chuỗi”.
- Arrays and Slices — Rust by Example (tiếng Anh): các ví dụ chạy được về mượn mảng dưới dạng slice.
slice— tài liệu thư viện chuẩn (tiếng Anh): rất nhiều phương thức của slice, nhưfirst,split_at,containsvàsort.- Slice types — The Rust Reference (tiếng Anh): định nghĩa chính xác, nếu bạn muốn biết chính xác
[T]và&[T]nghĩa là gì.