Fungsi adalah tempat pertemuan antar bagian program, sekaligus tempat move paling sering terjadi di kode nyata. Setiap kali kita mengirim nilai ke dalam fungsi atau menerima nilai keluar dari fungsi, kepemilikan ikut berpindah — dan pemanggilan fungsi yang tampak biasa di bahasa lain bisa menghasilkan error kompilasi di Rust. Pada Chapter ini kita akan menelusuri dua arah perpindahan tersebut: masuk lewat parameter dan keluar lewat nilai kembalian, lengkap dengan pesan error aslinya. Pemahaman dua arah ini juga akan menyiapkan kita mengenal borrowing sebagai jalan tengah yang jauh lebih nyaman.
Memindahkan Kepemilikan ke Dalam Fungsi #
Aturannya sederhana: mengirim nilai ke fungsi sama seperti melakukan assignment ke parameter. Untuk tipe Copy, nilai tersalin dan variabel pemanggil tetap sah. Untuk tipe non-Copy seperti String, kepemilikan berpindah ke parameter di dalam fungsi.
Mari kita mulai dengan contoh yang sehat — sebuah fungsi pembuat pesan sapaan:
fn build_greeting(name: String) -> String {
format!("Halo, {}! Selamat belajar Rust.", name)
}
fn main() {
let username = String::from("Budi");
let greeting = build_greeting(username);
println!("{}", greeting);
}Output yang Dihasilkan:
Halo, Budi! Selamat belajar Rust.Mari kita telusuri perjalanan kepemilikannya baris demi baris:
- Baris 6 (
let username = ...):usernamedi dalammainmenjadi pemilik teks"Budi"di heap. - Baris 7 (
let greeting = build_greeting(username);): pemanggilan ini terdiri dari dua kejadian. Pertama, kepemilikan teks"Budi"berpindah dariusernameke parameternamemilikbuild_greeting— sejak baris iniusernametidak valid lagi dimain. Kedua,format!membangunStringbaru, danStringbaru tersebut di-return sehingga kepemilikannya berpindah kegreeting. - Baris 1 (
fn build_greeting(name: String) -> String): parameternamemengambil alih kepemilikan teks yang dikirim pemanggil. Nilai kembalianStringakan berpindah lagi ke pemanggil. - Baris 2 (
format!(...)):format!menghasilkanStringbaru di heap. Setelah fungsi selesai, parameternamekeluar scope dan di-drop — teks"Budi"asli pun dipulangkan ke allocator di titik ini, bukan saatmainberakhir. - Baris 9 (
println!("{}", greeting);):greetingsah dipakai karena kepemilikannya diterima lewat nilai kembalian. Sebaliknya,usernamesudah tidak bisa disentuh lagi.
Perhatikan bahwa kehidupan teks "Budi" dan teks sapaan berjalan terpisah: teks pertama lahir di main, pindah ke name, lalu mati bersama akhir build_greeting; teks kedua lahir di dalam fungsi, pindah ke greeting, dan mati di akhir main.
Kode yang Gagal: Variabel Setelah Dipindahkan ke Fungsi #
Sekarang kita lihat bentuk pelanggaran yang paling sering dialami pemula. Kode berikut mencoba memakai note setelah nilainya dikirim ke print_message — kode ini tidak dapat dikompilasi:
fn print_message(message: String) {
println!("Pesan: {}", message);
}
fn main() {
let note = String::from("Belajar ownership hari ini");
print_message(note);
println!("Catatan: {}", note);
}Kompilasi dengan rustc --edition 2021 main.rs akan gagal sebagai berikut.
Pesan error kompilator:
error[E0382]: borrow of moved value: `note`
--> main.rs:9:29
|
6 | let note = String::from("Belajar ownership hari ini");
| ---- move occurs because `note` has type `String`, which does not implement the `Copy` trait
7 | print_message(note);
| ---- value moved here
8 |
9 | println!("Catatan: {}", note);
| ^^^^ value borrowed here after move
|
note: consider changing this parameter type in function `print_message` to borrow instead if owning the value isn't necessary
--> main.rs:1:27
|
1 | fn print_message(message: String) {
| ------------- ^^^^^^ this parameter takes ownership of the value
| |
| in this function
help: consider cloning the value if the performance cost is acceptable
|
7 | print_message(note.clone());
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.Pesan E0382 di atas memuat anotasi yang sangat membantu. Bagian value moved here pada baris 7 menunjuk pemanggilan print_message(note) sebagai titik perpindahan, dan bagian value borrowed here after move pada baris 9 menunjuk println! sebagai titik pelanggaran. Lebih menarik lagi, kompilator menyertakan note khusus: parameter message sebaiknya diubah menjadi bentuk pinjaman (borrow) karena fungsi ini sebenarnya hanya membaca nilai, bukan menggenggamnya. Saran tersebut adalah petunjuk menuju konsep referensi dan borrowing yang akan kita bahas tuntas pada Chapter 6.
Mengembalikan Kepemilikan dari Fungsi #
Arah kedua perpindahan adalah keluar dari fungsi. Setiap return — termasuk nilai di baris terakhir tanpa kata kunci return — memindahkan kepemilikan nilai ke pemanggil. Pola ini berguna ketika fungsi perlu mengubah data heap dan menyerahkan hasil akhirnya kembali:
fn add_degree(name: String) -> String {
let mut full_name = name;
full_name.push_str(", S.Kom");
full_name
}
fn main() {
let lecturer_name = String::from("Siti Rahma");
let degree_name = add_degree(lecturer_name);
println!("Nama dengan gelar: {}", degree_name);
}Output yang Dihasilkan:
Nama dengan gelar: Siti Rahma, S.KomMari kita bedah alur kepemilikannya:
- Baris 8 (
let lecturer_name = ...):lecturer_namedimainmemiliki teks"Siti Rahma". - Baris 9 (
let degree_name = add_degree(lecturer_name);): kepemilikan teks berpindah ke parametername;lecturer_nameberhenti valid. - Baris 2 (
let mut full_name = name;): di dalam fungsi, kepemilikan pindah lagi darinamekefull_name. Perpindahan internal seperti ini sah karenanametidak pernah dipakai lagi setelah baris tersebut. - Baris 3 (
full_name.push_str(", S.Kom");): fungsi mengubah isi teks di heap menjadi"Siti Rahma, S.Kom". Kata kuncimutpadafull_namediperlukan agar isinya boleh diubah. - Baris 4 (
full_name): baris terakhir tanpa titik koma adalah nilai kembalian. Kepemilikan berpindah keluar dari fungsi menuju variabeldegree_namedimain, dan fungsi tidak menjatuhkanfull_namekarena nilai tersebut sudah bukan miliknya.
Fungsi dengan tanda tangan String -> String seperti ini bekerja dengan sempurna — data masuk, diubah, lalu keluar kembali. Pertanyaannya: nyamankah pola ini untuk semua kebutuhan?
Pola Ambil dan Kembalikan, serta Harganya #
Bayangkan sebuah fungsi yang hanya perlu membaca panjang sebuah teks. Mengikuti pola sejauh ini, fungsinya harus menerima String dan mengembalikan String kembali agar pemanggil tetap bisa memakai teksnya — sebuah ritual bolak-balik hanya untuk membaca satu angka. Setiap fungsi pembaca akan menuntut ritual serupa, dan kode kita cepat terbebani variabel penampung serta nilai kembalian yang hilir mudik tanpa makna.
Pola ambil-dan-kembalikan memang sah dan kadang memang tepat — misalnya pada add_degree yang benar-benar mengubah data. Namun untuk operasi baca-tulis ringan, Rust menyediakan mekanisme yang jauh lebih ringkas: meminjam nilai lewat reference tanpa memindahkan kepemilikan sama sekali.
Ringkasan #
Parameter fungsi menerima kepemilikan lewat move (untuk tipe non-Copy) atau salinan murah (untuk tipe Copy), sementara nilai kembalian mengalihkan kepemilikan ke pemanggil. Kita telah membaca pesan E0382 versi fungsi, termasuk saran kompilator untuk mengubah parameter menjadi pinjaman. Dua saran itu — clone dan borrow — adalah dua jalan keluar resmi dari kebuntuan ownership, dan jalan yang kedua adalah bintang utama section ini: referensi dan borrowing.