Belajar Rust - Memory Safety, Borrowing, dan Lifetimes
Episode 6 of 19

Belajar Rust - Memory Safety, Borrowing, dan Lifetimes

Episode ini membedah jantung keamanan memori Rust: aturan ownership dan borrowing immutable serta mutable, anotasi lifetime dasar dan validitas referensi, plus praktik transfer kepemilikan dengan move, clone, dan copy. Kalian akan memahami mengapa banyak program Rust bisa lolos kompilasi tanpa bug memori.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Episode ini adalah materi yang paling membedakan Rust dari bahasa lain. Konsep ownership, borrowing, dan lifetime adalah alasan Rust bisa menjanjikan memory safety tanpa garbage collector. Banyak pemula menyerah di sini, tetapi dengan cara pandang yang benar, aturan-aturannya justru masuk akal dan konsisten.

Episode 6 membedah ketiga konsep tersebut satu per satu, lalu menutup dengan praktik transfer kepemilikan: move, clone, dan copy. Kalian juga akan belajar membaca error borrow checker yang selama ini menjadi sumber frustrasi.

Aturan Ownership

Tiga Aturan Dasar

Ownership dalam Rust diringkas dalam tiga aturan:

  • Setiap nilai memiliki satu pemilik.
  • Hanya boleh ada satu pemilik pada satu waktu.
  • Saat pemilik keluar dari scope, nilai dibebaskan.
Pemilik dan scope
cat > src/main.rs <<'EOF'
fn main() {
    let teks = String::from("hello");
    println!("di scope utama: {}", teks);
}
EOF
cargo run

teks dimiliki oleh main. Di akhir scope, String otomatis dibebaskan — tidak perlu free atau delete, dan tidak menunggu garbage collector. Aturan inilah yang membuat alokasi memori deterministik.

Stack dan Heap

Tipe berukuran tetap seperti i32 hidup di stack; data yang ukurannya dinamis seperti isi String dialokasikan di heap dengan pointer di stack. Saat pemilik dihapus, bagian heap ikut dibebaskan. Ownership menentukan kapan itu terjadi.

Borrowing Immutable dan Mutable

Meminjam Tanpa Memindahkan

Borrowing berarti memakai nilai tanpa mengambil kepemilikan, lewat referensi. Referensi immutable (&T) mengizinkan banyak pembaca sekaligus; referensi mutable (&mut T) mengizinkan satu penulis. Keduanya tidak boleh aktif bersamaan untuk nilai yang sama. Gunakan cargo check untuk memverifikasi aturan ini tanpa menghasilkan binary.

Dua peminjam
cat > src/main.rs <<'EOF'
fn hitung(s: &String) -> usize {
    s.len()
}
 
fn main() {
    let teks = String::from("borrowing");
    let a = &teks;
    let b = &teks;
    println!("{} dan {}", hitung(a), hitung(b));
}
EOF
cargo run

Dua referensi immutable a dan b boleh hidup bersamaan. Borrow checker memastikan tidak ada data race: data yang sedang dipinjam immutable tidak bisa dimutasi dari tempat lain.

Mutable Borrow

Untuk mengubah nilai lewat referensi, gunakan &mut:

Mutable borrow
cat > src/main.rs <<'EOF'
fn tambahkan(s: &mut String) {
    s.push_str("!");
}
 
fn main() {
    let mut teks = String::from("rust");
    tambahkan(&mut teks);
    println!("{}", teks);
}
EOF
cargo run

&mut teks meminjam secara mutable. Hanya satu mutable borrow yang boleh aktif per nilai. Jika kalian mencoba meminjam mutable dua kali atau memadukan dengan immutable, kompiler akan menolak — dan pesannya biasanya menjelaskan konfliknya dengan jelas.

Anotasi Lifetime dan Validitas Referensi

Konsep Lifetime

Lifetime adalah periode saat sebuah referensi valid. Setiap referensi punya lifetime; sebagian besar waktu compiler menyimpulkannya. Anotasi eksplisit diperlukan saat beberapa referensi dari fungsi saling berhubungan dan compiler tidak bisa menentukan hubungannya.

Anotasi lifetime
cat > src/main.rs <<'EOF'
fn pilih<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}
 
fn main() {
    let a = String::from("pendek");
    let b = String::from("lebih panjang");
    let hasil = pilih(&a, &b);
    println!("{}", hasil);
}
EOF
cargo run

<'a> mendeklarasikan lifetime 'a, dan &'a str menyatakan kedua parameter serta return type berbagi lifetime yang sama. Artinya: hasil referensi hanya valid selama kedua input masih hidup. Fungsi ini tidak bisa mengembalikan referensi yang menggantung.

Elision: Saat Anotasi Tidak Diperlukan

Rust punya aturan lifetime elision: jika sebuah fungsi memiliki satu parameter referensi, return type-nya dianggap memakai lifetime parameter tersebut. Itulah mengapa sebagian besar fungsi tidak perlu menulis 'a secara eksplisit. Anotasi hanya muncul saat ada beberapa input referensi dan hubungannya harus ditegaskan.

Move, Clone, dan Copy

Move: Memindahkan Kepemilikan

Saat sebuah nilai dikirim ke fungsi atau di-assign ke variabel baru, kepemilikannya berpindah (move). Nilai asli tidak bisa dipakai lagi:

Move ownership
cat > src/main.rs <<'EOF'
fn konsumsi(s: String) {
    println!("memakai: {}", s);
}
 
fn main() {
    let teks = String::from("data");
    konsumsi(teks);
    // println!("{}", teks); // error: value moved
}
EOF
cargo run

Setelah konsumsi(teks), variabel teks tidak lagi valid karena kepemilikannya berpindah. Compiler mencegah pemakaian nilai yang sudah dipindah — error "value moved" yang terkenal itu.

Clone dan Copy

Untuk tetap memakai nilai asli, salin: Clone membuat salinan mendalam secara eksplisit, sedangkan Copy menyalin nilai sederhana yang murah saat di-assign. Tipe seperti integer dan bool mengimplementasi Copy; String dan Vec hanya Clone.

Copy vs Clone
cat > src/main.rs <<'EOF'
#[derive(Clone, Copy)]
struct Titik {
    x: i32,
    y: i32,
}
 
fn main() {
    let a = Titik { x: 1, y: 2 };
    let b = a; // Copy: a tetap valid
    println!("{} {}", a.x, b.y);
 
    let teks = String::from("asli");
    let salinan = teks.clone(); // Clone eksplisit
    println!("{} {}", teks, salinan);
}
EOF
cargo run

let b = a menyalin Titik karena ia Copy. Untuk String, harus teks.clone() secara eksplisit — kemurahan hati Rust dalam menuntut kejelasan justru mencegah salinan tak disengaja yang mahal.

Memilih Strategi Kepemilikan

Pedoman praktis: gunakan borrowing (&) saat fungsi hanya perlu membaca; &mut saat perlu mengubah di tempat; pindahkan kepemilikan saat fungsi akan menyimpan atau membuang nilainya; dan gunakan clone hanya saat salinan benar-benar diperlukan. Urutan prioritas ini membuat kode efisien sekaligus mudah dipahami.

Penutup

Inti yang harus dibawa pulang:

  • Setiap nilai punya satu pemilik; memori dibebaskan saat pemilik keluar scope.
  • Banyak referensi immutable atau satu mutable, tidak pernah keduanya sekaligus.
  • Lifetime 'a menyatakan validitas referensi; elision menghilangkan sebagian besar anotasi.
  • Move memindahkan kepemilikan dan membuat nilai asal tidak valid.
  • Copy untuk tipe murah; Clone untuk salinan eksplisit.
  • Prioritaskan borrowing sebelum clone untuk efisiensi.

Di episode 7 selanjutnya kita akan membahas paket, crate, dan dependency management — menyusun Cargo.toml, features, dan versi dependency, workspace multi-crate untuk aplikasi besar, serta keamanan dependency dengan cargo audit. Kalian akan belajar mengelola kode di luar satu file.

Belajar Rust - Memory Safety, Borrowing, dan Lifetimes | Belajar Rust