[Rust Book] 3.5. Luồng điểu khiển

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:

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:

    let number = 7;

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:

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:

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 ifelse trong một else if expression. Ví dụ:

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ư sau.

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 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 ví dụ trên, kết của của cả ifelse đề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:

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 ifelse 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, whilefor. 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:

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, elsebreak; 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. Ví dụ dưới 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.

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{number}!");

        number -= 1;
    }

    println!("LIFTOFF!!!");
}

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, elsebreak, 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 ví dụ sau.

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, 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 dưới đây.

fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a {
        println!("the value is: {element}");
    }
}

Khi chúng ta chạy đoạn code này, chúng ta sẽ thấy cùng một output. 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ụ, 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, 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:

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).

Source

https://doc.rust-lang.org/stable/book/ch03-05-control-flow.html

Bài liên quan

comments powered by Disqus