Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fungsi

Fungsi itu ada di mana-mana di kode Rust. Kita udah liat salah satu fungsi paling penting di bahasanya: fungsi main, yang jadi entry point buat banyak program. Kita juga udah liat keyword fn, yang ngebolehin kita mendeklarasikan fungsi baru.

Kode Rust pake snake case sebagai gaya konvensional buat nama fungsi sama variabel, di mana semua hurufnya kecil (lowercase) dan pake garis bawah (underscore) buat misahin kata. Ini program yang isinya contoh definisi fungsi:

Nama file: src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Kita mendefinisikan fungsi di Rust dengan nulis fn diikuti sama nama fungsi dan tanda kurung. Kurung kurawal ngasih tau compiler di mana body fungsinya mulai sama selesai.

Kita bisa manggil fungsi apa pun yang udah kita definisikan dengan nulis namanya diikuti tanda kurung. Karena another_function didefinisikan di programnya, dia bisa dipanggil dari dalem fungsi main. Inget ya kalau kita mendefinisikan another_function setelah fungsi main di source code-nya; kita bisa aja mendefinisikannya sebelum main juga kok. Rust nggak peduli di mana kita mendefinisikan fungsi kita, yang penting mereka didefinisikan di suatu tempat di scope yang bisa diliat sama pemanggilnya.

Yuk kita bikin project biner baru namanya functions buat eksplor fungsi lebih lanjut. Taruh contoh another_function tadi di src/main.rs terus jalanin. Kita bakal liat output kayak gini:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

Baris-baris kodenya jalan sesuai urutan kemunculannya di fungsi main. Pertama pesan “Hello, world!” dicetak, terus another_function dipanggil dan pesannya dicetak.

Parameter

Kita bisa mendefinisikan fungsi biar punya parameter, yaitu variabel khusus yang jadi bagian dari signature sebuah fungsi. Pas sebuah fungsi punya parameter, kita bisa ngasih nilai konkret buat parameter itu. Secara teknis, nilai konkret itu namanya argument, tapi pas lagi ngobrol santai, orang-orang cenderung pake kata parameter sama argument secara bergantian buat nyebut variabel di definisi fungsi maupun nilai konkret yang dimasukin pas manggil fungsinya.

Di versi another_function ini kita nambahin satu parameter:

Nama file: src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

Coba jalanin program ini; kita bakal dapet output kayak gini:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

Deklarasi another_function punya satu parameter namanya x. Tipe dari x ditentuin sebagai i32. Pas kita masukin 5 ke another_function, macro println! naruh 5 di tempat pasangan kurung kurawal yang isinya x di format string-nya.

Di signature fungsi, kita harus mendeklarasikan tipe dari tiap parameter. Ini keputusan yang disengaja di desainnya Rust: nuntut annotasi tipe di definisi fungsi artinya compiler hampir nggak pernah butuh kita buat nulis tipenya di tempat lain di kode buat cari tau tipe mana yang kita maksud. Compiler juga bisa ngasih pesan error yang lebih ngebantu kalau dia tau tipe apa yang diharapin sama fungsinya.

Pas mendefinisikan banyak parameter, pisahin deklarasi parameternya pake koma, kayak gini:

Nama file: src/main.rs

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Contoh ini bikin fungsi namanya print_labeled_measurement dengan dua parameter. Parameter pertama namanya value dan tipenya i32. Yang kedua namanya unit_label dan tipenya char. Fungsinya terus nyetak teks yang isinya baik value maupun unit_label.

Yuk coba jalanin kode ini. Ganti program yang ada di project functions kita di file src/main.rs sama contoh di atas terus jalanin pake cargo run:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

Karena kita manggil fungsinya dengan 5 sebagai nilai buat value dan 'h' sebagai nilai buat unit_label, output programnya isinya nilai-nilai itu.

Statement dan Ekspresi (Statements and Expressions)

Body fungsi itu disusun dari serangkaian statement yang opsional bisa diakhiri sama sebuah ekspresi. Sejauh ini, fungsi-fungsi yang kita bahas belum ada ekspresi akhirnya, tapi kita udah liat ekspresi sebagai bagian dari sebuah statement. Karena Rust itu bahasa yang berbasis ekspresi (expression-based language), ini perbedaan penting yang harus dipahamin. Bahasa lain nggak punya perbedaan yang sama, jadi yuk kita liat apa itu statement sama ekspresi dan gimana perbedaannya ngaruh ke body fungsi.

  • Statement adalah instruksi yang ngelakuin suatu aksi dan nggak balikin nilai.
  • Ekspresi dievaluasi jadi sebuah nilai hasil.

Yuk kita liat beberapa contoh.

Sebenernya kita udah pake statement sama ekspresi. Bikin variabel terus ngasih nilai ke variabel itu pake keyword let itu adalah sebuah statement. Di Listing 3-1, let y = 6; adalah sebuah statement.

Filename: src/main.rs
fn main() {
    let y = 6;
}
Listing 3-1: Deklarasi fungsi main yang isinya satu statement

Definisi fungsi juga termasuk statement; seluruh contoh di atas itu sebenernya sebuah statement. (Tapi kayak yang bakal kita liat di bawah, manggil fungsi itu bukan statement.)

Statement nggak balikin nilai. Makanya, kita nggak bisa nge-assign sebuah statement let ke variabel lain, kayak yang dicoba sama kode berikut; kita bakal dapet error:

Nama file: src/main.rs

fn main() {
    let x = (let y = 6);
}

Pas kita jalanin program ini, error yang kita dapet bakal keliatan kayak gini:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^
  |
  = note: only supported directly in conditions of `if` and `while` expressions

warning: unnecessary parentheses around assigned value
 --> src/main.rs:2:13
  |
2 |     let x = (let y = 6);
  |             ^         ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
2 -     let x = (let y = 6);
2 +     let x = let y = 6;
  |

warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` (bin "functions") due to 1 previous error; 1 warning emitted

Statement let y = 6 nggak balikin nilai, jadi nggak ada apa-apa buat di-bind ke x. Ini beda sama apa yang terjadi di bahasa lain, kayak C sama Ruby, di mana assignment balikin nilai dari assignment-nya. Di bahasa-bahasa itu, kita bisa nulis x = y = 6 terus bikin baik x maupun y punya nilai 6; hal itu nggak berlaku di Rust.

Ekspresi dievaluasi jadi sebuah nilai dan nyusun sebagian besar sisa kode yang bakal kita tulis di Rust. Coba pikirin operasi matematika, kayak 5 + 6, yang merupakan ekspresi yang dievaluasi jadi nilai 11. Ekspresi bisa jadi bagian dari statement: di Listing 3-1, angka 6 di statement let y = 6; adalah sebuah ekspresi yang dievaluasi jadi nilai 6. Manggil fungsi itu adalah sebuah ekspresi. Manggil macro itu adalah sebuah ekspresi. Sebuah blok scope baru yang dibuat pake kurung kurawal juga ekspresi, contohnya:

Nama file: src/main.rs

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

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

Ekspresi ini:

{
    let x = 3;
    x + 1
}

adalah sebuah blok yang, dalam kasus ini, dievaluasi jadi 4. Nilai itu terus di-bind ke y sebagai bagian dari statement let. Inget ya kalau baris x + 1 nggak punya titik koma di akhirnya, beda sama kebanyakan baris yang udah kita liat sejauh ini. Ekspresi nggak pake titik koma di akhir. Kalau kita nambahin titik koma di akhir ekspresi, kita ngerubahnya jadi statement, dan dia nggak bakal balikin nilai. Terus inget ini pas kita eksplor nilai return fungsi sama ekspresi selanjutnya.

Fungsi dengan Nilai Return

Fungsi bisa balikin nilai ke kode yang manggil mereka. Kita nggak ngasih nama buat nilai return, tapi kita harus mendeklarasikan tipenya setelah tanda panah (->). Di Rust, nilai return dari sebuah fungsi itu sinonim sama nilai dari ekspresi terakhir di blok body fungsinya. Kita bisa return lebih awal dari sebuah fungsi pake keyword return terus nentuin nilainya, tapi kebanyakan fungsi balikin ekspresi terakhir secara implisit. Ini contoh fungsi yang balikin nilai:

Nama file: src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

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

Nggak ada pemanggilan fungsi, macro, atau bahkan statement let di fungsi five—cuma ada angka 5 sendirian. Itu fungsi yang sangat valid di Rust. Inget ya kalau tipe return fungsinya ditentuin juga, yaitu -> i32. Coba jalanin kode ini; output-nya bakal kayak gini:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

Angka 5 di five adalah nilai return fungsinya, makanya tipe return-nya i32. Yuk kita pelajari ini lebih detail. Ada dua bagian penting: pertama, baris let x = five(); nunjukin kalau kita pake nilai return fungsi buat menginisialisasi variabel. Karena fungsi five balikin 5, baris itu sama aja kayak gini:

#![allow(unused)]
fn main() {
let x = 5;
}

Kedua, fungsi five nggak punya parameter dan mendefinisikan tipe nilai return-nya, tapi body fungsinya cuma angka 5 kesepian tanpa titik koma karena itu adalah ekspresi yang nilainya mau kita balikin.

Yuk liat contoh lainnya:

Nama file: src/main.rs

fn main() {
    let x = plus_one(5);

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

fn plus_one(x: i32) -> i32 {
    x + 1
}

Jalanin kode ini bakal nyetak The value of x is: 6. Tapi kalau kita naruh titik koma di akhir baris yang isinya x + 1, ngerubahnya dari ekspresi jadi statement, kita bakal dapet error:

Nama file: src/main.rs

fn main() {
    let x = plus_one(5);

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

fn plus_one(x: i32) -> i32 {
    x + 1;
}

Compile kode ini ngasilin error kayak gini:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: remove this semicolon to return this value

For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` (bin "functions") due to 1 previous error

Pesan error utamanya, mismatched types, ngungkapin inti masalah kodenya. Definisi fungsi plus_one bilang kalau dia bakal balikin i32, tapi statement nggak dievaluasi jadi sebuah nilai, yang direpresentasikan sama (), yaitu tipe unit. Makanya, nggak ada apa pun yang dibalikin, yang bertentangan sama definisi fungsi dan ngasilin error. Di output ini, Rust ngasih pesan yang mungkin bisa ngebantu benerin masalah ini: dia nyaranin buat ngapus titik komanya, yang bakal benerin error-nya.