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

Vòng lặp while trong Rust

Vòng lặp while cứ chạy mãi chừng nào điều kiện vẫn còn true. Đây là công cụ phù hợp khi bạn biết mình đang chờ điều gì, dù không biết chính xác sẽ phải lặp bao nhiêu lần.

fn main() {
let mut count = 1;
while count <= 5 {
println!("{count}");
count += 1;
}
}

Kết quả:

1
2
3
4
5
▶ Tự chạy thử

Điều kiện (count <= 5) được kiểm tra trước mỗi lần chạy thân vòng lặp. Ngay khi nó thành false, vòng lặp kết thúc — ở đây là sau khi count thành 6.

Bạn có thể viết cùng một việc bằng loop kèm if/break tự viết tay, nhưng while nói thẳng ý bạn hơn: “cứ tiếp tục chừng nào điều này còn đúng.” Hãy để dành loop cho những trường hợp mà điều kiện dừng không nằm tự nhiên ở đầu vòng lặp.

// dùng while — rõ ràng, đi thẳng vào vấn đề
let mut n = 0;
while n < 3 {
println!("{n}");
n += 1;
}
// cùng việc đó bằng loop — vẫn chạy, nhưng phải đọc nhiều hơn
let mut n = 0;
loop {
if n >= 3 {
break;
}
println!("{n}");
n += 1;
}

while hay được dùng cho những việc như chờ một giá trị đạt tới mục tiêu:

fn main() {
let mut balance = 100;
while balance < 200 {
balance += 25;
println!("Số dư hiện tại là {balance}");
}
println!("Đã đạt mục tiêu!");
}

Kết quả:

Số dư hiện tại là 125
Số dư hiện tại là 150
Số dư hiện tại là 175
Số dư hiện tại là 200
Đã đạt mục tiêu!
▶ Tự chạy thử

Điều kiện có thể là bất kỳ biểu thức nào cho ra một bool, nên bạn có thể kết hợp nhiều phép kiểm tra bằng && và || trong bài Toán tử, chẳng hạn while balance < 200 && months < 12.

Một số ngôn ngữ có vòng lặp do { ... } while (condition). Nó chạy thân vòng lặp trước rồi mới kiểm tra điều kiện, nên thân vòng lặp luôn chạy ít nhất một lần. Rust không có loại này. Thay vào đó, bạn dùng loop và đặt phép kiểm tra ở cuối.

Đây là một trường hợp mà điều đó quan trọng. Để đếm số chữ số của một số, bạn có thể chia liên tục cho 10 đến khi không còn gì. Nhưng số 0 vẫn có một chữ số, và một vòng lặp while thông thường không hề chạy với nó:

fn main() {
let number = 0;
// Vòng lặp while kiểm tra trước, nên nó không bao giờ chạy với 0.
let mut n = number;
let mut digits = 0;
while n > 0 {
digits += 1;
n /= 10;
}
println!("while bảo {number} có {digits} chữ số");
// loop + kiểm tra ở cuối thì thân vòng lặp chạy ít nhất một lần.
let mut n = number;
let mut digits = 0;
loop {
digits += 1;
n /= 10;
if n == 0 {
break;
}
}
println!("loop bảo {number} có {digits} chữ số");
}

Kết quả:

while bảo 0 có 0 chữ số
loop bảo 0 có 1 chữ số
▶ Tự chạy thử

Đổi number thành 4096 thì cả hai phiên bản đều đồng ý: 4 chữ số. Sự khác biệt chỉ lộ ra khi điều kiện sai ngay từ đầu. Vì vậy, quy tắc dễ nhớ là: nếu thân vòng lặp phải chạy ít nhất một lần, hãy viết loop với if ... { break; } ở cuối.

Còn một kiểu vòng lặp while nữa là while let, chạy chừng nào một giá trị còn khớp với một mẫu (pattern). Nó được giới thiệu trong bài If Let và While Let.

Cộng dồn 1 + 2 + 3 + … và dừng ngay khi tổng lớn hơn 100. In ra bạn đã cộng bao nhiêu số và tổng là bao nhiêu. Gợi ý: bạn cần hai biến mut, một cho số tiếp theo và một cho tổng.

Xem lời giải
fn main() {
let mut next = 1;
let mut total = 0;
while total <= 100 {
total += next;
next += 1;
}
println!("Đã cộng {} số, tổng là {total}", next - 1);
}

Kết quả:

Đã cộng 14 số, tổng là 105
▶ Tự chạy thử

Sau lượt cuối, next đã tiến thêm một bước, vì thế ta in next - 1. Những chi tiết lệch một đơn vị (off-by-one) như thế này rất đáng để bạn thử nhẩm lại với một ví dụ nhỏ.

Bắt đầu với 1000. Chừng nào số đó còn lớn hơn 1, hãy chia nó cho 2 (chia nguyên) và in ra. Cuối cùng, in ra đã cần bao nhiêu lần chia đôi. Gợi ý: n /= 2 chia n cho 2 và bỏ phần dư.

Xem lời giải
fn main() {
let mut n = 1000;
let mut steps = 0;
while n > 1 {
n /= 2;
steps += 1;
println!("{n}");
}
println!("Cần {steps} lần chia đôi để về 1");
}

Kết quả:

500
250
125
62
31
15
7
3
1
Cần 9 lần chia đôi để về 1
▶ Tự chạy thử

125 / 2 là 62 chứ không phải 62.5, vì cả hai số đều là số nguyên. Nhờ vậy vòng lặp dừng đúng ở 1.

  • while condition { ... } lặp lại chừng nào điều kiện còn true, và điều kiện được kiểm tra trước mỗi lần chạy.
  • Hãy chắc rằng có thứ gì đó bên trong vòng lặp cuối cùng sẽ làm điều kiện thành false, nếu không nó sẽ chạy mãi mãi.
  • Ưu tiên while thay cho loop khi điều kiện dừng có thể diễn đạt tự nhiên là “chừng nào X còn đúng”.
  • Rust không có do-while. Khi thân vòng lặp phải chạy ít nhất một lần, hãy dùng loop với phép kiểm tra break ở cuối.

Kiểm tra nhanh

1. Lỗi kinh điển nào gây ra một vòng lặp while vô hạn?

2. Theo bài này, khi nào nên ưu tiên while thay cho loop?

3. Làm sao để viết một vòng lặp mà thân của nó luôn chạy ít nhất một lần?

Điểm: 0 / 3