Aliran Kontrol

Kemampuan untuk menjalankan beberapa kode tergantung pada apakah suatu kondisi true dan untuk menjalankan beberapa kode berulang kali saat kondisi adalah true blok bangunan dasar di sebagian besar bahasa pemrograman. Konstruksi paling umum yang memungkinkan Anda mengontrol aliran eksekusi kode Rust adalah ekspresi if dan loop.

Ekspresi if

Ekspresi if memungkinkan Anda untuk mencabangkan kode Anda tergantung pada kondisi. Anda memberikan kondisi dan kemudian menyatakan, “Jika kondisi ini terpenuhi, jalankan blok kode ini. Jika syaratnya tidak terpenuhi, jangan jalankan blok kode ini.”

Buat proyek baru bernama branches di direktori projects Anda untuk menjelajahi ekspresi if. Di file src/main.rs, masukkan yang berikut ini:

Nama file: src/main.rs

fn main() {
    let number = 3;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

Semua ekspresi if dimulai dengan kata kunci if, diikuti dengan kondisi. Dalam hal ini, kondisi memeriksa apakah variabel number memiliki nilai kurang dari 5 atau tidak. Kami menempatkan blok kode untuk dieksekusi jika kondisi tepat true setelah kondisi di dalam kurung kurawal. Blok kode yang terkait dengan kondisi dalam ekspresi terkadang if disebut lengan, seperti lengan dalam ekspresi match yang telah kita bahas di bagian “Membandingkan Tebakan dengan Angka Rahasia” di Bab 2.

Opsional, kami juga dapat menyertakan ekspresi else, yang kami pilih untuk dilakukan di sini, untuk memberikan program blok kode alternatif untuk dieksekusi jika kondisi dievaluasi menjadi false. Jika Anda tidak memberikan ekspresi else dan kondisinya adalah false, program hanya akan melewatkan blok if dan melanjutkan ke bit kode berikutnya.

$ 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

Mari kita coba mengubah nilai number menjadi nilai yang membuat kondisi false untuk melihat apa yang terjadi:

fn main() {
    let number = 7;

    if number < 5 {
        println!("condition was true");
    } else {
        println!("condition was false");
    }
}

Jalankan program lagi, dan lihat hasilnya:

$ 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

Perlu diperhatikan juga bahwa kondisi dalam kode ini harus berupa bool. Jika kondisinya bukan bool, kita akan mendapatkan error. Misalnya, coba jalankan kode berikut:

Nama file: src/main.rs

fn main() {
    let number = 3;

    if number {
        println!("number was three");
    }
}

Kondisi if dievaluasi ke nilai 3 saat ini, dan Rust melontarkan kesalahan:

$ 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

Kesalahan menunjukkan bahwa Rust mengharapkan bool tetapi mendapat bilangan bulat. Tidak seperti bahasa Ruby dan JavaScript, Rust tidak akan secara otomatis mencoba mengubah tipe non-Boolean menjadi Boolean. Anda harus eksplisit dan selalu menyediakan if dengan Boolean sebagai syaratnya. Jika kita ingin blok kode if berjalan hanya ketika sebuah angka tidak sama dengan 0, misalnya, kita dapat mengubah ekspresi if menjadi berikut:

Nama file: src/main.rs

fn main() {
    let number = 3;

    if number != 0 {
        println!("number was something other than zero");
    }
}

Menjalankan kode ini akan mencetak number was something other than zero.

Menangani Berbagai Kondisi dengan else if

Anda dapat menggunakan beberapa kondisi dengan menggabungkan if dan else dalam sebuah ekspresi else if. Misalnya:

Nama file: 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");
    }
}

Program ini memiliki empat kemungkinan jalur yang dapat diambil. Setelah menjalankannya, Anda akan melihat output berikut:

$ 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

Saat program ini dijalankan, program ini memeriksa setiap ekspresi if secara bergiliran dan mengeksekusi badan pertama yang kondisinya dievaluasi menjadi true. Perhatikan bahwa meskipun 6 habis dibagi 2, kita tidak melihat output number is divisible by 2, juga tidak melihat teks number is not divisible by 4, 3, or 2 dari blok else. Itu karena Rust hanya mengeksekusi blok untuk kondisi true pertama, dan setelah menemukannya, ia bahkan tidak memeriksa sisanya.

Menggunakan terlalu banyak ekspresi else if dapat mengacaukan kode Anda, jadi jika Anda memiliki lebih dari satu, Anda mungkin ingin memperbaiki kode Anda. Bab 6 menjelaskan konstruksi percabangan Rust yang kuat yang disebut match untuk kasus ini.

Menggunakan if dalam Pernyataan let

Karena if adalah ekspresi, kita dapat menggunakannya di sisi kanan pernyataan let untuk menetapkan hasil ke variabel, seperti pada Daftar 3-2.

Nama file: src/main.rs

fn main() {
    let condition = true;
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
}

Daftar 3-2: Menugaskan hasil ekspresi if ke variabel

Variabel number akan terikat pada nilai sesuai hasil ekspresi if. Jalankan kode ini untuk melihat apa yang terjadi:

$ 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

Ingatlah bahwa blok kode mengevaluasi ekspresi terakhir di dalamnya, dan angka itu sendiri juga merupakan ekspresi. Dalam hal ini, nilai seluruh ekspresi if bergantung pada blok kode mana yang dieksekusi. Artinya nilai-nilai yang berpotensi dihasilkan dari masing-masing lengan if harus bertipe sama; pada Daftar 3-2, hasil lengan if dan else adalah i32 bilangan bulat. Jika tipenya tidak cocok, seperti contoh berikut, kita akan mendapatkan kesalahan:

Nama file: src/main.rs

fn main() {
    let condition = true;

    let number = if condition { 5 } else { "six" };

    println!("The value of number is: {number}");
}

Saat kami mencoba mengkompilasi kode ini, kami akan mendapatkan kesalahan. Lengan if and else memiliki tipe nilai yang tidak kompatibel, dan Rust menunjukkan dengan tepat di mana menemukan masalah dalam program:

$ 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

Ekspresi di blok if dievaluasi ke integer, dan ekspresi di blok else dievaluasi ke string. Ini tidak akan berhasil karena variabel harus memiliki satu tipe, dan Rust perlu mengetahui pada waktu kompilasi tipe variabel number tersebut, secara definitif. Mengetahui tipe number memungkinkan kompiler memverifikasi tipe valid di mana pun kami menggunakan number. Rust tidak akan dapat melakukan itu jika tipe dari number hanya ditentukan pada saat runtime; kompiler akan lebih kompleks dan akan membuat lebih sedikit jaminan tentang kode jika harus melacak beberapa tipe hipotetis untuk variabel apa pun.

Pengulangan dengan Loop

Seringkali berguna untuk mengeksekusi blok kode lebih dari sekali. Untuk tugas ini, Rust menyediakan beberapa loop, yang akan dijalankan melalui kode di dalam loop body sampai akhir dan kemudian segera mulai kembali dari awal. Untuk bereksperimen dengan perulangan, mari buat proyek baru bernama loops.

Rust memiliki tiga jenis loop: loop, while, dan for. Mari kita coba masing-masing.

Mengulang Kode dengan loop

Kata kunci loop memberi tahu Rust untuk mengeksekusi blok kode berulang kali selamanya atau sampai Anda secara eksplisit menyuruhnya berhenti.

Sebagai contoh, ubah file src/main.rs di direktori loops Anda menjadi seperti ini:

Nama file: src/main.rs

fn main() {
    loop {
        println!("again!");
    }
}

Saat kita menjalankan program ini, kita akan melihat again! dicetak terus menerus sampai kita menghentikan program secara manual. Sebagian besar terminal mendukung pintasan keyboard ctrl-c untuk menginterupsi program yang macet terus-menerus. Cobalah:

$ 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!

Simbol ^C mewakili tempat Anda menekan ctrl-c. Anda mungkin atau mungkin tidak melihat kata again! yang dicetak setelah ^C, tergantung di mana kode berada di loop saat menerima sinyal interupsi.

Untungnya, Rust juga menyediakan cara untuk keluar dari loop menggunakan kode. Anda dapat menempatkan kata kunci break di dalam loop untuk memberi tahu program kapan harus berhenti menjalankan loop. Ingatlah bahwa kita melakukan ini dalam permainan tebak-tebakan di bagian “Berhenti Setelah Menebak dengan Benar” “Quitting After a Correct Guess” pada Bab 2 untuk keluar dari program saat pengguna memenangkan permainan dengan menebak angka yang benar.

Kami juga menggunakan continue dalam permainan tebak-tebakan, yang dalam satu loop memberi tahu program untuk melewati kode yang tersisa dalam iterasi loop ini dan melanjutkan ke iterasi berikutnya.

Mengembalikan Nilai dari Loop

Salah satu kegunaan loop adalah mencoba kembali operasi yang Anda tahu mungkin gagal, seperti memeriksa apakah thread telah menyelesaikan tugasnya. Anda mungkin juga perlu meneruskan hasil operasi itu keluar dari loop ke seluruh kode Anda. Untuk melakukan ini, Anda bisa menambahkan nilai yang ingin Anda kembalikan setelah ekspresi break yang Anda gunakan untuk menghentikan perulangan; nilai itu akan dikembalikan dari loop sehingga Anda dapat menggunakannya, seperti yang ditunjukkan di sini:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {result}");
}

Sebelum loop, kami mendeklarasikan variabel bernama counter dan menginisialisasi ke 0. Kemudian kami mendeklarasikan variabel bernama result untuk menampung nilai yang dikembalikan dari loop. Pada setiap iterasi loop, kita menambahkan 1 variabel counter, lalu memeriksa apakah counter sama dengan 10. Saat itu, kami menggunakan kata kunci break dengan nilai counter * 2. Setelah perulangan, kita menggunakan titik koma untuk mengakhiri pernyataan yang menetapkan nilai ke result. Terakhir, kami mencetak nilai dalam result, yang dalam hal ini adalah 20.

Label Loop untuk Disambiguasi Antara Beberapa Loop

Jika Anda memiliki loop di dalam loop, break dan continue diterapkan ke loop terdalam pada saat itu. Secara opsional, Anda dapat menentukan label loop pada loop yang kemudian dapat Anda gunakan dengan break atau continue untuk menentukan bahwa kata kunci tersebut berlaku untuk loop berlabel, bukan loop terdalam. Label loop harus dimulai dengan kutip tunggal. Berikut adalah contoh dengan dua loop bersarang:

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}");
}

Loop luar memiliki label 'counting_up, dan akan menghitung dari 0 hingga 2. Loop dalam tanpa label menghitung mundur dari 10 hingga 9. break pertama yang tidak menentukan label hanya akan keluar dari loop di dalam. Pernyataan break 'counting_up; akan keluar dari loop luar. Kode ini mencetak:

$ cargo run
   Compiling loops v0.1.0 (file:///projects/loops)
    Finished dev [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

Loop Bersyarat dengan while

Suatu program seringkali perlu mengevaluasi suatu kondisi dalam satu loop. Saat kondisinya true, loop berjalan. Ketika kondisi berhenti menjadi true, program memanggil break, menghentikan perulangan. Dimungkinkan untuk menerapkan perilaku seperti ini menggunakan kombinasi loop, if, else, dan break; Anda dapat mencobanya sekarang dalam sebuah program, jika Anda mau. Namun, pola ini sangat umum sehingga Rust memiliki konstruksi bahasa bawaan untuknya, yang disebut loop while. Dalam Daftar 3-3, kami menggunakan while untuk mengulang program tiga kali, menghitung mundur setiap iterasi, dan kemudian, setelah pengulangan, cetak pesan dan keluar.

Nama file: src/main.rs

fn main() {
    let mut number = 3;

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

        number -= 1;
    }

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

Daftar 3-3: Menggunakan perulangan while untuk menjalankan kode saat kondisi benar

Konstruk ini menghilangkan banyak sarang yang diperlukan jika Anda menggunakan loop, if, else, dan break, dan lebih jelas. Saat kondisi bernilai true, kode berjalan; jika tidak, itu keluar dari loop.

Pengulangan melalui Koleksi dengan for

Anda dapat memilih untuk menggunakan konstruk while untuk mengulang elemen koleksi, seperti array. Sebagai contoh, loop pada Daftar 3-4 mencetak setiap elemen dalam array a.

Nama file: 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;
    }
}

Daftar 3-4: Mengulang setiap elemen koleksi menggunakan loop while

Di sini, kode dihitung melalui elemen-elemen dalam array. Ini dimulai pada index 0, dan kemudian berulang hingga mencapai indeks terakhir dalam array (yaitu, ketika index < 5 tidak lagi true). Menjalankan kode ini akan mencetak setiap elemen dalam array:

$ 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

Kelima nilai array muncul di terminal, seperti yang diharapkan. Meskipun index akan mencapai nilai 5 di beberapa titik, loop berhenti mengeksekusi sebelum mencoba mengambil nilai keenam dari array.

Namun, pendekatan ini rawan kesalahan; dapat menyebabkan program kita menjadi panik jika nilai indeks atau kondisi pengujian salah. Misalnya, jika Anda mengubah definisi array a menjadi empat elemen tetapi lupa memperbarui kondisinya menjadi while index < 4, kode akan panik. Ini juga lambat, karena kompiler menambahkan kode runtime untuk melakukan pemeriksaan kondisional apakah indeks berada dalam batas array pada setiap iterasi melalui loop.

Sebagai alternatif yang lebih ringkas, Anda bisa menggunakan perulangan for dan mengeksekusi beberapa kode untuk setiap item dalam koleksi. Loop for terlihat seperti kode pada Daftar 3-5.

Nama file: src/main.rs

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

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

Daftar 3-5: Mengulang setiap elemen koleksi menggunakan loop for

Saat kita menjalankan kode ini, kita akan melihat output yang sama seperti pada Daftar 3-4. Lebih penting lagi, kami sekarang telah meningkatkan keamanan kode dan menghilangkan kemungkinan bug yang mungkin dihasilkan dari melampaui akhir array atau tidak cukup jauh dan kehilangan beberapa item.

Dengan menggunakan for, Anda tidak perlu mengingat untuk mengubah kode lain jika Anda mengubah jumlah nilai dalam array, seperti yang Anda lakukan dengan metode yang digunakan pada Daftar 3-4.

Keamanan dan keringkasan for menjadikannya konstruksi loop yang paling umum digunakan di Rust. Bahkan dalam situasi di mana Anda ingin menjalankan beberapa kode beberapa kali, seperti dalam contoh hitung mundur yang menggunakan perulangan while di Daftar 3-3, kebanyakan Rustacean akan menggunakan perulangan for. Cara melakukannya adalah dengan menggunakan Range, yang disediakan oleh pustaka standar, yang menghasilkan semua angka secara berurutan mulai dari satu angka dan diakhiri sebelum angka lainnya.

Berikut tampilan hitungan mundur menggunakan perulangan for dan metode lain yang belum kita bicarakan, rev, untuk membalikkan rentang:

Nama file: src/main.rs

fn main() {
    for number in (1..4).rev() {
        println!("{number}!");
    }
    println!("LIFTOFF!!!");
}

Kode ini sedikit lebih bagus, bukan?

Ringkasan

Anda berhasil! Ini adalah bab yang cukup besar: Anda belajar tentang variabel, tipe data skalar dan compound, fungsi, komentar, ekspresi if, dan loop! Untuk berlatih dengan konsep yang dibahas dalam bab ini, cobalah membuat program untuk melakukan hal berikut:

  • Mengkonversi suhu antara Fahrenheit dan Celsius.
  • Hasilkan angka Fibonacci ke-n .
  • Cetak lirik lagu Natal “The Twelve Days of Christmas”, memanfaatkan pengulangan dalam lagu tersebut.

Saat Anda siap untuk melanjutkan, kita akan berbicara tentang konsep di Rust yang tidak umum ada dalam bahasa pemrograman lain: kepemilikan (ownership).