Borrowing (meminjam) adalah cara memakai sebuah nilai tanpa mengambil pemiliknya, lewat reference: penunjuk ke nilai milik variabel lain, ditulis dengan &. Bayangin nota Sari di Kedai Martabak Bang Rusdi. Di pelajaran ownership, kasir menyerahkan notanya ke dapur, dan kasir gak memegangnya lagi. Padahal dapur sering cuma perlu membaca. Cukup kasir tunjukkan notanya: dapur membaca, nota tetap di tangan kasir. Kasir, dapur, dan Sari boleh membaca nota yang sama bersamaan. Tapi kalau ada yang sedang menulis tambahan di nota, gak boleh ada orang lain yang membaca atau menulis saat itu, karena bisa salah baca tulisan yang belum selesai. Menunjukkan nota itu ¬a, meminjamkan pena itu &mut nota, dan aturan "banyak yang membaca, atau satu yang menulis" itu aturan borrowing.
🧪 Tebakan cepat: kasir mencatat pesanan pertama lewat pinjaman,
let pertama = &pesanan[0];, lalu ada pesanan baru,pesanan.push(45000);, lalu kasir mencetakpertama. Lolos compile? Tebak dulu, jawabannya ada di bagian "Kenapa aturannya seketat itu".
&: meminjam untuk membaca
fn hitung_panjang(s: &String) -> usize {
s.len()
}
fn masak(nota: &String) {
println!("Dimasak: {}", nota);
}
fn main() {
let nota = String::from("2x martabak manis");
let panjang = hitung_panjang(¬a);
masak(¬a);
println!("Kasir: {} ({} huruf)", nota, panjang);
}
Dimasak: 2x martabak manis
Kasir: 2x martabak manis (17 huruf)
¬amembuat reference kenota. Pemiliknya tetapnota; yang dikirim cuma penunjuknya, 8 byte di komputer 64-bit.s: &Stringartinya parameter ini meminjam sebuahString. Bandingkan denganmasak(nota: String)di pelajaran ownership, yang mengambil pemiliknya.- Saat
hitung_panjangselesai,skeluar scope, tapi gak ada yang di-drop: peminjam gak pernah memiliki. Makanyanotamasih bisa dicetak di baris terakhir, tanpa E0382.
&mut: meminjam untuk mengubah
fn tambah_item(nota: &mut String, item: &str) {
nota.push_str(item);
}
fn tambah_es_teh(total: &mut u32) {
*total += 5000;
}
fn main() {
let mut nota = String::from("2x martabak manis");
let mut total: u32 = 102000;
tambah_item(&mut nota, ", 1x es teh");
tambah_es_teh(&mut total);
println!("{}", nota);
println!("Total: {}", total);
}
2x martabak manis, 1x es teh
Total: 107000
- Mengubah lewat pinjaman butuh
mutdi tiga tempat: variabelnya (let mut nota), pinjamannya (&mut nota), dan tipe parameternya (&mut String). *totalartinya "nilai yang ditunjuktotal". Tanda*ini disebut dereference.*total += 5000mengubahtotalmilikmaindari 102000 jadi 107000. Untuk method, Rust menambah*sendiri, jadinota.push_str(item)cukup.- Lupa
&mutdi pemanggilan,tambah_item(¬a, ...), ditolak dengan E0308types differ in mutability. Lupalet mut, ditolak dengan E0596cannot borrow `nota` as mutable, as it is not declared as mutable.
Aturannya: banyak pembaca, atau satu penulis
Pada satu waktu, sebuah nilai boleh punya salah satu dari ini:
- Pinjaman
&sebanyak apa pun, atau - Tepat satu pinjaman
&mut, dan selama ia hidup gak ada pinjaman lain.
Jadi &mut cuma boleh satu pada satu waktu. Dua penulis sekaligus ditolak:
fn main() {
let mut nota = String::from("2x martabak manis");
let kasir = &mut nota;
let dapur = &mut nota;
kasir.push_str(", 1x es teh");
dapur.push_str(", dibungkus");
println!("{}", nota);
}
error[E0499]: cannot borrow `nota` as mutable more than once at a time
--> src/main.rs:4:17
|
3 | let kasir = &mut nota;
| --------- first mutable borrow occurs here
4 | let dapur = &mut nota;
| ^^^^^^^^^ second mutable borrow occurs here
5 | kasir.push_str(", 1x es teh");
| ----- first borrow later used here
Pemiliknya sendiri juga ikut aturan. Selama ada yang meminjam, pemilik gak boleh mengubah nilainya, bahkan angka u32: let kasir = &harga; harga = 35000; lalu memakai kasir ditolak dengan E0506 cannot assign to `harga` because it is borrowed.
Kenapa aturannya seketat itu
Ini kode tebakan tadi:
fn main() {
let mut pesanan = vec![102000, 70000, 38000];
let pertama = &pesanan[0];
pesanan.push(45000);
println!("Pertama: {}", pertama);
}
error[E0502]: cannot borrow `pesanan` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let pertama = &pesanan[0];
| ------- immutable borrow occurs here
4 | pesanan.push(45000);
| ^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("Pertama: {}", pertama);
| ------- immutable borrow later used here
Gak lolos. Itu jawaban tebakannya. push butuh &mut pesanan, padahal pertama masih meminjam untuk membaca. Ini bukan aturan iseng. vec![...] berisi 3 angka punya kapasitas pas 3, jadi push memesan blok heap baru berkapasitas 6, memindahkan isinya ke sana, lalu mengembalikan blok lama. Saat dicoba, alamat isinya berubah dari 0x100dbded0 ke 0x977000980. Kalau compiler membiarkannya, pertama akan menunjuk blok lama yang sudah dikembalikan: dangling reference, bug yang di C++ lolos compile lalu membaca sampah atau crash. Aturan "satu penulis" juga yang membuat program dengan banyak thread aman dari data race: dua thread menulis data yang sama bersamaan.
Pinjaman berakhir di pemakaian terakhir
Pindahkan println! ke atas push, dan programnya lolos:
fn main() {
let mut pesanan = vec![102000, 70000, 38000];
let pertama = &pesanan[0];
println!("Pertama: {}", pertama);
pesanan.push(45000);
println!("{:?}", pesanan);
}
Pertama: 102000
[102000, 70000, 38000, 45000]
Pinjaman pertama gak hidup sampai }, tapi cuma sampai pemakaian terakhirnya, yaitu baris println! pertama. Sesudah itu, push boleh meminjam untuk menulis. Fitur ini namanya non-lexical lifetimes (NLL), ada sejak edition 2018. Jadi kalau ketemu E0502 atau E0499, sering perbaikannya cukup memindahkan pemakaian pinjaman lama ke atas pinjaman baru.
Referensi gak boleh hidup lebih lama dari datanya
fn main() {
let catatan;
{
let nota = String::from("2x martabak manis");
catatan = ¬a;
}
println!("{}", catatan);
}
error[E0597]: `nota` does not live long enough
--> src/main.rs:5:19
|
4 | let nota = String::from("2x martabak manis");
| ---- binding `nota` declared here
5 | catatan = ¬a;
| ^^^^^ borrowed value does not live long enough
6 | }
| - `nota` dropped here while still borrowed
7 | println!("{}", catatan);
| ------- borrow later used here
nota di-drop di } blok dalam, tapi catatan masih dipakai sesudahnya. Rentang hidup sebuah pinjaman disebut lifetime, dan compiler memastikan tiap lifetime selesai sebelum datanya dibuang.
Fungsi yang mengembalikan reference kadang perlu menyebut lifetime-nya sendiri. Fungsi ini memilih teks yang lebih panjang:
fn terpanjang<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", terpanjang("martabak manis", "es teh"));
}
Output-nya martabak manis. Tanpa <'a>, fn terpanjang(a: &str, b: &str) -> &str ditolak dengan E0106 missing lifetime specifier, karena compiler gak tahu hasilnya meminjam dari a atau dari b. 'a cuma nama untuk sebuah lifetime: tulisan di atas berjanji "hasilnya boleh dipakai selama a dan b dua-duanya masih hidup". Lifetime gak memperpanjang umur apa pun, ia cuma menjelaskan. Fungsi yang membuat String sendiri lalu mengembalikan &String juga kena E0106, dan saran compiler-nya tepat: instead, you are more likely to want to return an owned value, yaitu kembalikan String-nya.
Slice: meminjam sebagian
fn total(item: &[u32]) -> u32 {
item.iter().sum()
}
fn main() {
let nota = String::from("2x martabak manis");
let porsi: &str = ¬a[0..2];
let menu: &str = ¬a[3..];
let pesanan = vec![102000, 70000, 38000];
let papan: [u32; 4] = [32000, 38000, 5000, 5000];
println!("{} | {}", porsi, menu);
println!("{:?}", &pesanan[1..]);
println!("{}", total(&pesanan));
println!("{}", total(&pesanan[1..]));
println!("{}", total(&papan));
}
2x | martabak manis
[70000, 38000]
210000
108000
80000
- Slice adalah pinjaman ke sebagian isi yang berurutan.
¬a[0..2]meminjam byte 0 sampai sebelum 2, yaitu2x.¬a[3..]meminjam dari byte 3 sampai habis. &stryang kamu pakai sejak awal itu slice teks.&[u32]itu slice angka, dari Vec atau array. Isinya gak disalin: slice cuma penunjuk plus panjang, 16 byte.- Parameter
&[u32]menerima&pesanan(Vec),&pesanan[1..], dan&papan(array). Begitu juga&strmenerima¬adan teks"es teh". Karena itufn hitung_panjang(s: &str)lebih lazim daripada&String. - Slice juga pinjaman, jadi ikut aturan yang sama: selama
porsimasih dipakai,nota.push_str(...)ditolak dengan E0502.
⚠️ Jebakan yang sering ketemu
- Mengubah sesuatu yang masih dipinjam.
pushataupush_strsaat pinjaman&masih dipakai sesudahnya: E0502. Pakai dulu pinjamannya, baru ubah. - Dua
&mutsekaligus. E0499cannot borrow ... as mutable more than once at a time. Selesaikan pinjaman pertama sebelum membuat yang kedua. mutcuma di satu tempat. Butuhlet mut,&mutdi pemanggilan, dan&mut Tdi parameter. Kurang satu: E0596 atau E0308.- Mengembalikan pinjaman ke data lokal.
fn buat_nota() -> &Stringditolak (E0106), karena datanya dibuang saat fungsi selesai. KembalikanString.
TL;DR: ¬a meminjam untuk membaca, &mut nota meminjam untuk mengubah, dan pemiliknya tetap nota, jadi gak ada move dan gak ada drop saat pinjaman selesai. Pada satu waktu boleh banyak & atau tepat satu &mut (E0502, E0499), dan selama dipinjam pemiliknya pun gak boleh mengubah (E0506). Aturan ini mencegah dangling reference, misalnya pinjaman ke isi Vec yang pindah saat push. Pinjaman berakhir di pemakaian terakhirnya (NLL), bukan di }. Reference gak boleh hidup lebih lama dari datanya (E0597), dan fungsi yang mengembalikan reference dari dua parameter menulis lifetime-nya: fn terpanjang<'a>(a: &'a str, b: &'a str) -> &'a str. Slice (&str, &[u32]) meminjam sebagian isi, 16 byte, dan jadi tipe parameter yang paling luwes.