Luồng Điều Khiển
Khả năng chạy một số mã tùy thuộc vào việc một điều kiện là true
và chạy một
số mã lặp đi lặp lại trong khi một điều kiện là true
là những khối xây dựng cơ
bản trong hầu hết các ngôn ngữ lập trình. Các cấu trúc phổ biến nhất cho phép
bạn kiểm soát luồng thực thi của mã Rust là biểu thức if
và vòng lặp.
Biểu Thức if
Biểu thức if
cho phép bạn phân nhánh mã của mình dựa trên các điều kiện. Bạn
cung cấp một điều kiện và sau đó nói, "Nếu điều kiện này được đáp ứng, hãy chạy
khối mã này. Nếu điều kiện không được đáp ứng, đừng chạy khối mã này."
Tạo một dự án mới có tên branches trong thư mục projects của bạn để khám phá
biểu thức if
. Trong tệp src/main.rs, nhập nội dung sau:
Tên tệp: src/main.rs
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
Tất cả các biểu thức if
bắt đầu bằng từ khóa if
, theo sau là một điều kiện.
Trong trường hợp này, điều kiện kiểm tra xem biến number
có giá trị nhỏ hơn 5
hay không. Chúng ta đặt khối mã để thực thi nếu điều kiện là true
ngay sau
điều kiện bên trong dấu ngoặc nhọn. Các khối mã liên kết với các điều kiện trong
biểu thức if
đôi khi được gọi là nhánh, giống như các nhánh trong biểu thức
match
mà chúng ta đã thảo luận trong phần "So Sánh Dự Đoán với Số Bí
Mật" của Chương 2.
Tùy chọn, chúng ta cũng có thể bao gồm một biểu thức else
, mà chúng ta đã chọn
ở đây, để cung cấp cho chương trình một khối mã thay thế để thực thi nếu điều
kiện đánh giá thành false
. Nếu bạn không cung cấp một biểu thức else
và điều
kiện là false
, chương trình sẽ bỏ qua khối if
và chuyển sang đoạn mã tiếp
theo.
Hãy thử chạy mã này; bạn sẽ thấy đầu ra sau:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was true
Hãy thử thay đổi giá trị của number
thành một giá trị khiến điều kiện thành
false
để xem điều gì xảy ra:
fn main() {
let number = 7;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
Chạy chương trình lần nữa và xem đầu ra:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was false
Cũng cần lưu ý rằng điều kiện trong mã này phải là một bool
. Nếu điều kiện
không phải là bool
, chúng ta sẽ gặp lỗi. Ví dụ, hãy thử chạy mã sau:
Tên tệp: src/main.rs
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
Điều kiện if
đánh giá thành một giá trị 3
lần này, và Rust báo lỗi:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` (bin "branches") due to 1 previous error
Lỗi chỉ ra rằng Rust mong đợi một bool
nhưng nhận được một số nguyên. Không
giống như các ngôn ngữ như Ruby và JavaScript, Rust sẽ không tự động chuyển đổi
các kiểu không phải Boolean thành Boolean. Bạn phải rõ ràng và luôn cung cấp cho
if
một Boolean làm điều kiện của nó. Nếu chúng ta muốn khối mã if
chạy chỉ
khi một số không bằng 0
, ví dụ, chúng ta có thể thay đổi biểu thức if
thành
như sau:
Tên tệp: src/main.rs
fn main() { let number = 3; if number != 0 { println!("number was something other than zero"); } }
Chạy mã này sẽ in ra number was something other than zero
.
Xử Lý Nhiều Điều Kiện với else if
Bạn có thể sử dụng nhiều điều kiện bằng cách kết hợp if
và else
trong một
biểu thức else if
. Ví dụ:
Tên tệp: src/main.rs
fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } }
Chương trình này có bốn đường dẫn có thể thực hiện. Sau khi chạy nó, bạn sẽ thấy đầu ra sau:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
number is divisible by 3
Khi chương trình này thực thi, nó kiểm tra từng biểu thức if
lần lượt và thực
thi thân đầu tiên mà điều kiện đánh giá thành true
. Lưu ý rằng mặc dù 6 chia
hết cho 2, chúng ta không thấy đầu ra number is divisible by 2
, cũng không
thấy văn bản number is not divisible by 4, 3, or 2
từ khối else
. Đó là vì
Rust chỉ thực thi khối cho điều kiện true
đầu tiên, và khi tìm thấy một điều
kiện, nó thậm chí không kiểm tra phần còn lại.
Sử dụng quá nhiều biểu thức else if
có thể làm rối mã của bạn, vì vậy nếu bạn
có nhiều hơn một, bạn có thể muốn cấu trúc lại mã của mình. Chương 6 mô tả một
cấu trúc phân nhánh mạnh mẽ của Rust gọi là match
cho những trường hợp này.
Sử dụng if
trong một câu lệnh let
Vì if
là một biểu thức, chúng ta có thể sử dụng nó ở phía bên phải của câu
lệnh let
để gán kết quả cho một biến, như trong Listing 3-2.
fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); }
Biến number
sẽ được gắn với một giá trị dựa trên kết quả của biểu thức if
.
Chạy mã này để xem điều gì xảy ra:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/branches`
The value of number is: 5
Hãy nhớ rằng các khối mã đánh giá thành biểu thức cuối cùng trong chúng, và các
số tự chúng cũng là biểu thức. Trong trường hợp này, giá trị của toàn bộ biểu
thức if
phụ thuộc vào khối mã nào được thực thi. Điều này có nghĩa là các giá
trị có khả năng là kết quả từ mỗi nhánh của if
phải cùng một loại; trong
Listing 3-2, kết quả của cả nhánh if
và nhánh else
đều là số nguyên i32
.
Nếu các loại không khớp, như trong ví dụ sau, chúng ta sẽ gặp lỗi:
Tên tệp: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}
Khi cố gắng biên dịch mã này, chúng ta sẽ gặp lỗi. Các nhánh if
và else
có
kiểu giá trị không tương thích, và Rust chỉ ra chính xác nơi tìm thấy vấn đề
trong chương trình:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:4:44
|
4 | let number = if condition { 5 } else { "six" };
| - ^^^^^ expected integer, found `&str`
| |
| expected because of this
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` (bin "branches") due to 1 previous error
Biểu thức trong khối if
đánh giá thành một số nguyên, và biểu thức trong khối
else
đánh giá thành một chuỗi. Điều này sẽ không hoạt động vì các biến phải có
một kiểu duy nhất, và Rust cần biết tại thời điểm biên dịch kiểu biến number
là gì, một cách chắc chắn. Biết kiểu của number
cho phép trình biên dịch xác
minh kiểu là hợp lệ ở mọi nơi chúng ta sử dụng number
. Rust sẽ không thể làm
điều đó nếu kiểu của number
chỉ được xác định trong thời gian chạy; trình biên
dịch sẽ phức tạp hơn và sẽ cung cấp ít đảm bảo hơn về mã nếu phải theo dõi nhiều
kiểu giả định cho bất kỳ biến nào.
Lặp Lại với Vòng Lặp
Thường rất hữu ích để thực thi một khối mã nhiều lần. Cho nhiệm vụ này, Rust cung cấp một số vòng lặp, sẽ chạy qua mã bên trong thân vòng lặp đến cuối và sau đó bắt đầu lại từ đầu ngay lập tức. Để thử nghiệm với vòng lặp, hãy tạo một dự án mới có tên loops.
Rust có ba loại vòng lặp: loop
, while
, và for
. Hãy thử từng loại.
Lặp Lại Mã với loop
Từ khóa loop
yêu cầu Rust thực thi một khối mã lặp đi lặp lại mãi mãi hoặc cho
đến khi bạn yêu cầu nó dừng lại.
Ví dụ, hãy thay đổi tệp src/main.rs trong thư mục loops của bạn để trông như thế này:
Tên tệp: src/main.rs
fn main() {
loop {
println!("again!");
}
}
Khi chúng ta chạy chương trình này, chúng ta sẽ thấy again!
được in liên tục
cho đến khi chúng ta dừng chương trình bằng tay. Hầu hết các terminal hỗ trợ
phím tắt ctrl-c để ngắt một chương trình bị kẹt trong một
vòng lặp liên tục. Hãy thử:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!
Ký hiệu ^C
biểu thị nơi bạn đã nhấn ctrl-c.
Bạn có thể sẽ thấy hoặc không thấy từ again!
được in sau ^C
, tùy thuộc vào
vị trí mã trong vòng lặp khi nó nhận được tín hiệu ngắt.
May mắn thay, Rust cũng cung cấp một cách để thoát khỏi vòng lặp bằng mã. Bạn có
thể đặt từ khóa break
trong vòng lặp để yêu cầu chương trình dừng thực thi
vòng lặp. Hãy nhớ rằng chúng ta đã làm điều này trong trò chơi đoán số ở phần
"Thoát Sau Khi Đoán Đúng" của
Chương 2 để thoát chương trình khi người dùng thắng trò chơi bằng cách đoán đúng
số.
Chúng ta cũng đã sử dụng continue
trong trò chơi đoán số, trong một vòng lặp
nó bảo chương trình bỏ qua bất kỳ mã còn lại nào trong lần lặp này của vòng lặp
và chuyển sang lần lặp tiếp theo.
Trả Về Giá Trị từ Vòng Lặp
Một trong những cách sử dụng của loop
là thử lại một hoạt động mà bạn biết có
thể thất bại, chẳng hạn như kiểm tra xem một luồng đã hoàn thành công việc của
nó hay chưa. Bạn cũng có thể cần truyền kết quả của hoạt động đó ra khỏi vòng
lặp đến phần còn lại của mã của bạn. Để làm điều này, bạn có thể thêm giá trị
bạn muốn trả về sau biểu thức break
bạn sử dụng để dừng vòng lặp; giá trị đó
sẽ được trả về từ vòng lặp để bạn có thể sử dụng nó, như được hiển thị ở đây:
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); }
Trước vòng lặp, chúng ta khai báo một biến có tên counter
và khởi tạo nó thành
0
. Sau đó, chúng ta khai báo một biến có tên result
để giữ giá trị trả về từ
vòng lặp. Trong mỗi lần lặp của vòng lặp, chúng ta thêm 1
vào biến counter
,
và sau đó kiểm tra xem counter
có bằng 10
không. Khi nó bằng 10
, chúng ta
sử dụng từ khóa break
với giá trị counter * 2
. Sau vòng lặp, chúng ta sử
dụng dấu chấm phẩy để kết thúc câu lệnh gán giá trị cho result
. Cuối cùng,
chúng ta in giá trị trong result
, trong trường hợp này là 20
.
Bạn cũng có thể return
từ bên trong một vòng lặp. Trong khi break
chỉ thoát
khỏi vòng lặp hiện tại, return
luôn thoát khỏi hàm hiện tại.
Nhãn Vòng Lặp để Phân Biệt Giữa Nhiều Vòng Lặp
Nếu bạn có vòng lặp trong vòng lặp, break
và continue
áp dụng cho vòng lặp
bên trong nhất tại thời điểm đó. Bạn có thể tùy chọn chỉ định một nhãn vòng
lặp trên một vòng lặp mà bạn có thể sử dụng với break
hoặc continue
để chỉ
định rằng những từ khóa đó áp dụng cho vòng lặp được gắn nhãn thay vì vòng lặp
bên trong nhất. Nhãn vòng lặp phải bắt đầu bằng một dấu nháy đơn. Đây là một ví
dụ với hai vòng lặp lồng nhau:
fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); }
Vòng lặp bên ngoài có nhãn 'counting_up
, và nó sẽ đếm lên từ 0 đến 2. Vòng lặp
bên trong không có nhãn đếm ngược từ 10 đến 9. break
đầu tiên không chỉ định
nhãn sẽ chỉ thoát khỏi vòng lặp bên trong. Câu lệnh break 'counting_up;
sẽ
thoát khỏi vòng lặp bên ngoài. Mã này in:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
Vòng Lặp Có Điều Kiện với while
Một chương trình thường cần đánh giá một điều kiện trong một vòng lặp. Trong khi
điều kiện là true
, vòng lặp chạy. Khi điều kiện không còn là true
, chương
trình gọi break
, dừng vòng lặp. Có thể thực hiện hành vi như thế này bằng cách
kết hợp loop
, if
, else
, và break
; bạn có thể thử điều đó bây giờ trong
một chương trình, nếu bạn muốn. Tuy nhiên, mẫu này rất phổ biến nên Rust có một
cấu trúc ngôn ngữ tích hợp cho nó, gọi là vòng lặp while
. Trong Listing 3-3,
chúng ta sử dụng while
để chạy chương trình ba lần, đếm ngược mỗi lần, và sau
đó, sau vòng lặp, in một thông báo và thoát.
fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }
Cấu trúc này loại bỏ rất nhiều việc lồng nhau sẽ cần thiết nếu bạn sử dụng
loop
, if
, else
, và break
, và nó rõ ràng hơn. Trong khi một điều kiện
đánh giá thành true
, mã chạy; nếu không, nó thoát khỏi vòng lặp.
Lặp Qua một Tập Hợp với for
Bạn có thể chọn sử dụng cấu trúc while
để lặp qua các phần tử của một tập hợp,
chẳng hạn như một mảng. Ví dụ, vòng lặp trong Listing 3-4 in ra từng phần tử
trong mảng a
.
fn main() { let a = [10, 20, 30, 40, 50]; let mut index = 0; while index < 5 { println!("the value is: {}", a[index]); index += 1; } }
Ở đây, mã đếm qua các phần tử trong mảng. Nó bắt đầu từ chỉ mục 0
, và sau đó
lặp cho đến khi đạt đến chỉ mục cuối cùng trong mảng (đó là khi index < 5
không còn true
nữa). Chạy mã này sẽ in mọi phần tử trong mảng:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
Tất cả năm giá trị mảng xuất hiện trong terminal, như mong đợi. Mặc dù index
sẽ đạt đến giá trị 5
tại một thời điểm nào đó, vòng lặp dừng thực thi trước
khi cố gắng lấy giá trị thứ sáu từ mảng.
Tuy nhiên, cách tiếp cận này dễ xảy ra lỗi; chúng ta có thể khiến chương trình
hoảng sợ nếu giá trị chỉ mục hoặc điều kiện kiểm tra không chính xác. Ví dụ, nếu
bạn thay đổi định nghĩa của mảng a
để có bốn phần tử nhưng quên cập nhật điều
kiện thành while index < 4
, mã sẽ hoảng sợ. Nó cũng chậm, vì trình biên dịch
thêm mã thời gian chạy để thực hiện kiểm tra điều kiện xem chỉ mục có nằm trong
giới hạn của mảng trong mỗi lần lặp qua vòng lặp không.
Là một lựa chọn thay thế ngắn gọn hơn, bạn có thể sử dụng vòng lặp for
và thực
thi một số mã cho mỗi phần tử trong một tập hợp. Một vòng lặp for
trông giống
như mã trong Listing 3-5.
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } }
Khi chúng ta chạy mã này, chúng ta sẽ thấy đầu ra giống như trong Listing 3-4.
Quan trọng hơn, bây giờ chúng ta đã tăng độ an toàn của mã và loại bỏ khả năng
xảy ra lỗi có thể dẫn đến vượt quá cuối mảng hoặc không đi đủ xa và bỏ qua một
số phần tử. Mã máy được tạo từ vòng lặp for
cũng có thể hiệu quả hơn, vì chỉ
mục không cần phải so sánh với độ dài của mảng ở mỗi lần lặp.
Sử dụng vòng lặp for
, bạn sẽ không cần phải nhớ thay đổi bất kỳ mã nào khác
nếu bạn thay đổi số lượng giá trị trong mảng, như bạn sẽ làm với phương pháp
được sử dụng trong Listing 3-4.
Sự an toàn và ngắn gọn của vòng lặp for
làm cho chúng trở thành cấu trúc vòng
lặp được sử dụng phổ biến nhất trong Rust. Ngay cả trong những tình huống mà bạn
muốn chạy một số mã một số lần nhất định, như trong ví dụ đếm ngược sử dụng vòng
lặp while
trong Listing 3-3, hầu hết người dùng Rust sẽ sử dụng vòng lặp
for
. Cách để làm điều đó sẽ là sử dụng một Range
, được cung cấp bởi thư viện
tiêu chuẩn, tạo ra tất cả các số theo thứ tự bắt đầu từ một số và kết thúc trước
một số khác.
Đây là cách đếm ngược sẽ trông như thế nào khi sử dụng vòng lặp for
và một
phương pháp khác mà chúng ta chưa nói đến, rev
, để đảo ngược phạm vi:
Tên tệp: src/main.rs
fn main() { for number in (1..4).rev() { println!("{number}!"); } println!("LIFTOFF!!!"); }
Mã này tốt hơn một chút, phải không?
Tóm Tắt
Bạn đã làm được! Đây là một chương đáng kể: bạn đã học về biến, kiểu dữ liệu vô
hướng và phức hợp, hàm, nhận xét, biểu thức if
, và vòng lặp! Để thực hành với
các khái niệm được thảo luận trong chương này, hãy thử xây dựng các chương trình
để thực hiện các việc sau:
- Chuyển đổi nhiệt độ giữa Fahrenheit và Celsius.
- Tạo số Fibonacci thứ n.
- In lời bài hát Giáng sinh "The Twelve Days of Christmas", tận dụng sự lặp lại trong bài hát.
Khi bạn sẵn sàng tiếp tục, chúng ta sẽ nói về một khái niệm trong Rust mà không thường tồn tại trong các ngôn ngữ lập trình khác: quyền sở hữu.