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

Vector trong Rust

Vector (Vec) giống như một mảng, nhưng có thể dài ra và ngắn lại lúc chạy. Trong code Rust hằng ngày, khi cần một danh sách, bạn sẽ dùng Vec thường xuyên hơn nhiều so với mảng có kích thước cố định.

fn main() {
let numbers: Vec<i32> = vec![1, 2, 3];
println!("{:?}", numbers);
}

vec![] là một macro (để ý dấu !) tạo ra một Vec từ danh sách giá trị, tương tự như cách bạn viết một mảng.

▶ Tự chạy thử

Bạn cũng có thể bắt đầu với một vector rỗng rồi thêm dần vào:

let mut numbers: Vec<i32> = Vec::new();

Dùng .push() để thêm vào cuối. Vector cần là mut vì bạn đang thay đổi nó:

fn main() {
let mut numbers = Vec::new();
numbers.push(1);
numbers.push(2);
numbers.push(3);
println!("{:?}", numbers);
}

Kết quả:

[1, 2, 3]
▶ Tự chạy thử

Cùng cú pháp ngoặc vuông như mảng:

fn main() {
let fruits = vec!["táo", "chuối", "anh đào"];
println!("{}", fruits[0]);
println!("Độ dài: {}", fruits.len());
}
▶ Tự chạy thử

Để tra cứu an toàn hơn mà không panic khi chỉ số sai, hãy dùng .get(), nó trả về một Option (xem thêm trong bài Option):

fn main() {
let fruits = vec!["táo", "chuối", "anh đào"];
match fruits.get(10) {
Some(fruit) => println!("Tìm thấy: {fruit}"),
None => println!("Không có loại quả nào ở chỉ số đó"),
}
}
▶ Tự chạy thử
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5];
numbers.pop(); // xóa phần tử cuối -> [1, 2, 3, 4]
numbers.remove(0); // xóa theo chỉ số -> [2, 3, 4]
println!("{:?}", numbers);
}
▶ Tự chạy thử
fn main() {
let numbers = vec![10, 20, 30];
for n in &numbers {
println!("{n}");
}
}
▶ Tự chạy thử

Để ý dấu & trước numbers — duyệt bằng for n in &numbers sẽ mượn (borrow) từng phần tử thay vì lấy quyền sở hữu cả vector. Bỏ dấu & đi thì vector bị move vào vòng lặp, nghĩa là bạn không thể dùng numbers sau đó nữa. Hầu như lúc nào bạn cũng muốn mượn.

Bạn sẽ để ý kiểu được viết là Vec<i32> — phần <i32> nói rằng “một vector chứa các giá trị i32.” Cách viết với dấu ngoặc nhọn này gọi là generic, và bạn sẽ gặp lại nó đầy đủ trong bài Generic. Bây giờ, chỉ cần đọc Vec<T> là “một vector chứa các giá trị kiểu T.”

Bắt đầu với một vector rỗng. Duyệt qua các số từ 1 đến 10 và chỉ push các số chẵn. Sau đó in vector và số phần tử của nó. Gợi ý: số n là số chẵn khi n % 2 == 0.

Xem lời giải
fn main() {
let mut evens = Vec::new();
for n in 1..=10 {
if n % 2 == 0 {
evens.push(n);
}
}
println!("{:?}", evens);
println!("Tìm thấy {} số chẵn", evens.len());
}

Kết quả:

[2, 4, 6, 8, 10]
Tìm thấy 5 số chẵn
▶ Tự chạy thử

Để ý rằng ta không hề ghi kiểu. Rust nhìn vào các lệnh gọi push(n) và tự suy ra evens là một Vec<i32>.

Push ba cái đĩa vào một vector: "xanh dương", "xanh lá", rồi "đỏ". Lấy hai cái đĩa trên cùng ra bằng .pop() và in từng cái. Cuối cùng, in ra những gì còn lại. Gợi ý: .pop() trả về một Option, nên hãy in nó bằng {:?}.

Xem lời giải
fn main() {
let mut plates = Vec::new();
plates.push("xanh dương");
plates.push("xanh lá");
plates.push("đỏ");
println!("Lấy ra {:?}", plates.pop());
println!("Lấy ra {:?}", plates.pop());
println!("Còn lại: {:?}", plates);
}

Kết quả:

Lấy ra Some("đỏ")
Lấy ra Some("xanh lá")
Còn lại: ["xanh dương"]
▶ Tự chạy thử

pop() luôn lấy phần tử cuối cùng mà bạn đã push, giống như nhấc cái đĩa trên cùng khỏi chồng đĩa. Nó trả về Some(...), chứ không phải giá trị trần, vì vector có thể đang rỗng. Khi đó bạn sẽ nhận None thay vì chương trình bị crash.

  • Vec<T> là danh sách có thể co giãn; tạo bằng vec![...] hoặc Vec::new().
  • .push() thêm vào cuối; .pop() xóa phần tử cuối; .remove(i) xóa theo chỉ số.
  • vec[i] panic khi chỉ số sai; .get(i) thì trả về một Option, không panic.
  • Duyệt bằng for item in &vec để mượn từng phần tử thay vì tiêu thụ cả vector.

Kiểm tra nhanh

1. Điều gì xảy ra khi bạn truy cập một Vec bằng chỉ số sai, như numbers[10]?

2. Phương thức nào cho phép tra cứu theo chỉ số mà không có nguy cơ panic?

3. Dấu & trong for n in &numbers làm gì?

Điểm: 0 / 3