Luồng điểu khiển
Việc quyết định chạy code hay không hay chạy code lặp đi lặp lại dựa vào điều
kiện là những block code cơ bản trong hầu hết các ngôn ngữ lập trình. Phổ biến
nhất là câu điều kiện if
và vòng lặp.
if
Expressions
Câu điều kiện if
cho phép bạn rẽ nhánh code tùy thuộc theo điều kiện. Bạn đưa
ra một điều kiện và sau đó chỉ ra, “Nếu điều kiện này thỏa mãn, chạy block code
này. Nếu điện kiện không thỏa mãn, đừng chạy block code này.”
Tạo một project mới tên là branches trong thư mục projects của bạn để tìm hiểu về câu điều kiện. Trong file src/main.rs, nhập như sau:
Filename: src/main.rs
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
Một câu điều kiện bắt đầu với 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 nếu biến number
có giá trị nhỏ hơn 5 hay
không. Block code mà chúng ta muốn thực thi, nếu điều kiện thỏa mãn, được đặt ngay
sau điều kiện bên trong cặp dấu ngoặc nhọn. Block code liên kết với điều kiện này
đôi khi được gọi là arm, giống như arm ở match
expression mà chúng ta đã nói
ở phần “So sánh số đoán với số bí mật”
của Chương 2.
Ngoài ra, chúng ta cũng có thể thêm else
expression để cho chương trình thực
thi một block code thay thế khi điều kiện trả về là sai. Nếu bạn không cung cấp
else
và điều kiện không được thỏa mãn, chương trình sẽ chỉ bỏ qua block if
và
chạy đoạn code tiếp theo.
Thử chạy đoạn code này, bạn sẽ thấy output như sau:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was true
Hãy thử đổi giá trị của number
sang một giá trị mà làm cho điều kiện false
để xem điều gì sẽ xảy ra:
fn main() {
let number = 7;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
Chạy lại chương trình và xem lại output:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was false
Các bạn hãy nhớ rằng điều kiện trong đoạn code này phải là một giá trị bool
.
Nếu điều kiện không phải là bool
, chúng ta sẽ gặp lỗi. Ví dụ, thử chạy đoạn code
sau:
Filename: src/main.rs
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
Lần này, điều kiện if
đưa ra một giá trị là 3
, và Rust bắn ra 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` due to previous error
Lỗi chỉ ra rằng Rust đợi nhận một giá trị bool
nhưng lại nhận được một giá trị
integer. Không như những ngôn ngữ khác, ví dụ như Ruby và JavaScript, Rust không
tự động ép kiểu không phải Boolean sang kiểu Boolean. Bạn phải hiểu rõ và luôn
dùng if
với một giá trị Boolean làm điều kiện của nó. Nếu bạn muốn block code
chỉ chạy khi một số không phải là 0
, bạn có thể thay đổi if
như sau:
Filename: src/main.rs
fn main() { let number = 3; if number != 0 { println!("number was something other than zero"); } }
Chạy đoạn code 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ể có nhiều điều kiện bằng cách kết hợp if
và else
trong một
else if
expression. Ví dụ:
Filename: 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 có thể đi. Sau khi chạy nó, bạn sẽ thấy output sau:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
number is divisible by 3
Khi chương trình này chạy, nó kiểm tra mỗi điều kiện if
theo thứ tự và thực thi
phần nào mà điều kiện của nó thỏa mãn trước. Lưu ý rằng thậm chí 6 chia hết cho 2,
chúng ta không thấy output number is divisible by 2
, hay number is not divisible by 4, 3, or 2
từ else
block. Đó là bởi vì Rust chỉ thực thi block thỏa mãn điều
kiện trước, và một khi nó tìm thấy block đó, nó không check những block còn lại nữa.
Sử dụng quá nhiều else if
có thể làm code của bạn trở lên lộn xộn, vậy nên nếu
bạn có nhiều hơn một cặp else if
, bạn có lẽ cần refactor code của mình. Chương
6 mô tả một cách xây dựng rẽ nhánh mạnh mẽ của Rust gọi là match
cho những trường
hợp như vậy.
Sử dụng if
trong statement let
Bởi vì if
là một expression, chúng ta có thể dùng nó ở phía bên phải của một
statement let
, như trong Listing 3-2.
Filename: src/main.rs
fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {}", number); }
Listing 3-2: Gán kết quả của expression if
cho một biến
Biến number
sẽ được gán bằng giá trị trả về của expression if
. Giờ chạy thử
đoạn code trên để xem điều gì sẽ xảy ra:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/branches`
The value of number is: 5
Các bạn cần nhớ rằng những block code trả ra expression cuối cùng của chúng, và
con số cũng là những expression. Trong trường hợp này, giá trị của cả expression
if
phụ thuộc vào việc đoạn code nào được thực thi. Điều này nghĩa là giá trị
tiềm năng, có thể là kết quả từ mỗi arm của if
, phải cùng kiểu; trong Listing
3-2, kết của của cả if
và else
đều là số nguyên i32
. Nếu kiểu không khớp,
như ví dụ sau đây, thì chúng ta sẽ gặp lỗi:
Filename: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {}", number);
}
Khi chúng ta cố biên dịch đoạn code này, nó sẽ bị lỗi. Arm của if
và else
có kiểu giá trị không tương thích, và Rust chỉ ra chính xác vị trí của 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` due to previous error
Expression trong block if
trả ra một số nguyên integer, và expression trong
block else
trả ra một string. Thứ này sẽ không thể hoạt động bởi vì chỉ có thể
có một kiểu dữ liệu. Rust cần biết tại thời điểm biên dịch, kiểu của biến number
là gì. Rust không thể biết điều đó nếu kiểu của number
chỉ được xác định tại
thời điểm runtime; trình biên dịch sẽ phức tạp hơn và khó đảm bảo về code nếu nó
phải theo dõi nhiều giả định về kiểu của bất kì biến nào.
Chạy lặp lại với các vòng lặp
Việc thực thi một đoạn code nhiều hơn một lần nhiều khi rất hữu dụng. Để làm việc này, Rust cung cấp cho chúng ta một vài kiểu vòng lặp. Một vòng lặp chạy đoạn code bên trong thân vòng lặp từ đầu đến cuối và lặp lại từ đầu. Để trải nghiệm vòng lặp, hãy cùng tạo một project mới tên là loops.
Rust có ba kiểu lặp: loop
, while
và for
. Hãy thử từng cái trong số chúng.
Lặp code với loop
Từ khóa loop
nói với Rust thực thi một đoạn code lặp đi lặp lại mãi mãi cho tới
khi bạn bảo nó dừng lại.
Ví dụ, sửa file src/main.rs trong thư mục loops thành như sau:
Filename: 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 đi in lại
liên tục cho tới 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. Thử chạy nó nào:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!
Ký tự ^C
hiển thị nơi bạn nhấn ctrl-c.
Bạn có thể nhìn thấy từ again!
hoặc không, in sau ^C
, tùy thuộc vào vị trí
của code trong vòng lặp khi nó nhận được tín hiệu ngắt.
May mắn thay, Rust cung cấp một cách khác, tin cậy hơn để thoát một vòng lặp.
Bạn có thể đặt từ khóa break
trong vòng lặp để nói với chương trình khi nào
dừng thực thi vòng lặp. Nhớ lại rằng chúng ta đã làm điều này ở game đoán số
phần “Thoát sau khi đoán chính xác” ở Chương 2 để thoát chương trình khi người chơi thắng trò chơi do đoán đúng
số cần đoán.
Chúng ta cũng đã sử dụng continue
trong trò chơi đoán số, nói với vòng lặp rằng
bỏ qua phần code còn lại trong lần lặp này và đi tới lần lặp tiếp theo.
Trả về giá trị từ vòng lặp
Một trong những cách dùng loop
là thử lại một operation mà bạn biết nó có thể không thành công,
ví dụ kiểm tra liệu một thread đã hoàn thành công việc của nó hay chưa. Tuy nhiên,
bạn có thể cần truyền kết quả của hành động đó cho phần còn lại của code. Để làm
điều này, bạn có thể thêm giá trị bạn muốn trả về sau lệnh break
mà bạn sử dụng
để dừng vòng lặp; giá trị đó sẽ được trả ra ngoài vòng lặp nên bạn có thể dùng nó,
như được thể hiện dưới đâ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 tên là counter
và khởi tạo nó bằng
0
. Sau đó chúng ta khai báo một biến tên là result
để giữ giá trị trả về từ
vòng lặp. Ở mỗi lần lặp, chúng ta thêm 1
vào biến counter
, sau đó kiểm trả
nếu counter bằng 10
hay không. Khi nó thỏa mãn, 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 một dấu chấm phẩy để
kết thúc statement gán giá trị cho result
. Cuối cùng, chúng ta in ra giá trị
của result
, trong trường hợp này là 20.
Lặp có điều kiện với while
Việc chương trình kiểm tra điều kiện trong vòng lặp thường khá hữu ích. Khi điều
kiện là đúng, chạy vòng lặp. Khi điều kiện không còn đúng, chương trình gọi
break
, dừng vòng lặp. Kiểu vòng lặp này có thể được tạo bằng cách kết hợp loop
,
if
, else
và break
; bạn có thể thử nó ngay bây giờ trong một chương trình
nếu bạn muốn.
Tuy nhiên, phương thức này khá phổ biến nên Rust có sẵn một cấu trúc cho nó, gọi
là vòng lặp while
. Listing 3-3 sử dụng while
: chương trình lặp ba lần, đếm
ngược mỗi lần, sau đó, sau vòng lặp, nó in một tin nhắn khác và thoát.
Filename: src/main.rs
fn main() { let mut number = 3; while number != 0 { println!("{}!", number); number -= 1; } println!("LIFTOFF!!!"); }
Listing 3-3: Sử dụng vòng lặp while
để chạy code trong
khi điều kiện còn đúng
Cách này loại bỏ rất nhiều phần không cần thiết so với sử dụng loop
, if
,
else
và break
, và nó cũng rõ ràng hơn. Khi điều kiện còn đúng, code chạy;
ngược lại, nó thoát vòng lặp.
Lặp qua một tập hợp với for
Bạn có thể sử dụng while
để lặp qua các phần tử của một tập hợp, như mảng chẳng
hạn. Ví dụ, hãy cùng nhìn vào Listing 3-4.
Filename: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; let mut index = 0; while index < 5 { println!("the value is: {}", a[index]); index += 1; } }
Listing 3-4: Lặp qua từng phần tử của một tập hợp
sử dụng vòng lặp while
Ở đây, code đếm qua các phần tử của mảng. Nó bắt đầu tại chỉ số 0
, sau đó lặp
cho đến chỉ số cuối của mảng (đó là khi index < 5
không còn đúng nữa). Chạy đoạn
code này sẽ in ra mọi phần tử của mảng:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [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ả 5 giá trị của mảng hiển thị trong terminal như mong đợi. Thậm chí mặc dù
index
đạt giá trị 5
, vòng lặp dừng thực thi trước khi cố lấy giá trị thứ sáu
từ mảng.
Nhưng cách tiếp cận này dễ có lỗi; chúng ta có thể khiến chương trình panic nếu độ dài chỉ số không chính xác. Thêm vào đó, nó cũng chậm vì trình biên dịch thêm runtime code để thực hiện việc kiểm tra điều kiện trên mọi phần tử ở mọi lần lặp.
Một cách khác ngắn gọn hơn, bạn có thể dùng vòng lặp for
và thực thi code cho
mỗi phần tử của tập hợp. Một vòng lặp for
sẽ giống như code trong Listing 3-5.
Filename: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {}", element); } }
Listing 3-5: Lặp qua mỗi phần tử của một tập hợp
sử dụng vòng lặp for
Khi chúng ta chạy đoạn code này, chúng ta sẽ thấy cùng một output như Listing 3-4. Quan trọng hơn, bây giờ chúng ta đã tăng độ an toàn của code lên và loại bỏ nguy cơ bug xảy ra do đi quá kết thúc của mảng hay không đi đủ xa và bỏ xót vài phần tử.
Ví dụ, ở Listing 3-4, nếu bạn thay đổi định nghĩa của mảng a
thành có bốn phần
tử nhưng quên cập nhật điều kiện while index < 4
, code sẽ panic. Sử dụng vòng
lặp for
, bạn sẽ không cần nhớ phải thay đổi bất kì phần code nào khác nếu bạn
thay đổi số giá trị trong mảng.
Tính an toàn và ngắn gọn của vòng lặp for
khiến nó là vòng lặp được dùng nhiều
nhất trong Rust. Thậm chí trong tình huống mà bạn muốn chạy một đoạn code với số
lần nhất định, như trong ví dụ đếm ngược sử dụng vòng lặp while
ở Listing 3-3,
hầu hết các Rustacean sẽ dùng vòng lặp for
. Cách để làm việc đó là dùng Range
,
một kiểu được cung cấp bởi thư viện chuẩn, nó sẽ sinh 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ố nào đó.
Ví dụ đếm ngược sẽ trông như thế này nếu dùng vòng lặp for
và một phương thức
khác mà chúng ta chưa nói tới, rev
, để đảo ngược cả đoạn:
Filename: src/main.rs
fn main() { for number in (1..4).rev() { println!("{}!", number); } println!("LIFTOFF!!!"); }
Đoạn code nhìn đẹp hơn rất nhiều.
Tổng kết
Thế là xong! Đây là một chương khá dài: bạn đã học về biến, kiểu dữ liệu vô hướng
(scalar) và phức hợp (compound), hàm, comment, câu điều kiện if
và vòng lặp!
Nếu bạn muốn thực hành với những khái niệm đã được thảo luận trong chương này,
hãy thử xây dựng chương trình làm những điều sau:
- Chuyển đổi nhiệt độ giữa Fahrenheit và Celsius.
- Sinh số Fibonacci thứ n.
- In ra lời bài hát mừng 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à hầu như không tồn tại trong những ngôn ngữ lập trình khác: ownership (quyền sở hữu).