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.
fn main() {
let y = 6;
}
main yang isinya satu statementDefinisi 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.