Saat mengolah data, kita sering mengubah bentuknya dari tahap ke tahap: teks hasil input diubah menjadi angka, angka dikalikan pajak, lalu hasilnya dibulatkan. Di PHP, kita cenderung menimpa variable yang sama ($harga = (int) $harga;). Chapter sebelumnya mengajarkan bahwa mut hanya mengizinkan penimpaan nilai bertipe sama, sehingga pola “nilai berubah bentuk, nama tetap” butuh mekanisme lain. Rust menjawab kebutuhan ini dengan shadowing: mendeklarasikan ulang variable bernama sama menggunakan let baru, sehingga variable lama tertutup oleh variable baru. Pada Chapter ini kita akan memakai shadowing untuk transformasi bertahap, memahami interaksinya dengan scope, dan membandingkannya dengan mut.
Cara Kerja Shadowing #
Shadowing terjadi ketika let baru memakai nama yang sudah dipakai variable sebelumnya di scope yang sama. Variable baru sepenuhnya terpisah dari variable lama: ia boleh bertipe berbeda, dan keberadaannya menutup (shadow) akses ke variable lama di titik tersebut ke bawah. Variable lama tetap ada di memori sampai scope berakhir, tetapi namanya sudah tidak bisa kita pakai lagi karena setiap penulisan nama itu kini mengarah ke variable baru.
Bentuk paling umum shadowing adalah mengubah tipe nilai dengan nama yang tetap, misalnya data kode pos yang awalnya berupa teks lalu diubah menjadi bilangan:
fn main() {
let kode_pos = "40115";
println!("Kode pos (teks): {}", kode_pos);
let kode_pos = 40115;
println!("Kode pos (angka): {}", kode_pos);
}Output yang Dihasilkan:
Kode pos (teks): 40115
Kode pos (angka): 40115Mari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 2 (
let kode_pos = "40115";): variablekode_pospertama lahir sebagai teks (&str) berisi"40115"; tanda kutip ganda menandakan tipe string. - Baris 4 (
let kode_pos = 40115;):letbaru mendeklarasikan variable kedua bernama sama; variable kedua ini bertipe bilangan bulati32dan kini membayangi variable pertama. - Baris 5 (
println!("Kode pos (angka): {}", kode_pos);): namakode_posdi baris ini mengarah ke variable kedua, sehingga tercetak40115sebagai angka, bukan teks.
Kedua println! mencetak tampilan yang mirip, tetapi tipe datanya berbeda: variable pertama menyimpan teks, variable kedua menyimpan bilangan yang siap dihitung. Perubahan tipe seperti ini mustahil dilakukan lewat mut, karena mut mengikat tipe sejak deklarasi pertama.
Transformasi Nilai secara Bertahap #
Kekuatan shadowing terasa ketika satu nilai melewati beberapa tahap pengolahan. Setiap tahap menghasilkan variable baru dengan nama sama, sehingga pembaca kode melihat satu nama yang nilainya “mengeras” dari baris ke baris. Contoh berikut mengolah panjang tanah yang digandakan lalu ditambah lebar jalan:
fn main() {
let panjang_tanah = 12;
println!("Panjang awal: {} meter", panjang_tanah);
let panjang_tanah = panjang_tanah * 2;
println!("Setelah digandakan: {} meter", panjang_tanah);
let panjang_tanah = panjang_tanah + 3;
println!("Panjang final: {} meter", panjang_tanah);
}Output yang Dihasilkan:
Panjang awal: 12 meter
Setelah digandakan: 24 meter
Panjang final: 27 meterMari kita bedah prosesnya:
- Baris 2 (
let panjang_tanah = 12;): variable pertama menyimpan nilai12. - Baris 5 (
let panjang_tanah = panjang_tanah * 2;): sisi kanan membaca variable lama bernilai12, mengalikannya dengan2, laluletbaru mengikat hasil24pada nama yang sama. - Baris 8 (
let panjang_tanah = panjang_tanah + 3;): variable tahap kedua bernilai24dijumlah3menjadi27, lalu diikat olehletketiga; nilai12dan24sebelumnya sudah tertutup.
Setiap tahap bersifat immutable: tidak ada penimpaan, hanya lahirnya variable baru. Jika suatu tahap salah menghitung, kita bisa menghapus let pada tahap itu tanpa merusak tahap lain, karena setiap tahap berdiri di atas hasil tahap sebelumnya.
Shadowing dan Scope #
Scope variable di Rust dibatasi oleh blok kode tempat ia dideklarasikan, ditandai pasangan tanda kurung kurawal { }. Shadowing di dalam blok hanya berlaku sampai blok ditutup; setelah itu, variable di scope luar muncul kembali dengan nilai yang tidak tersentuh:
fn main() {
let stok = 100;
println!("Stok luar: {}", stok);
{
let stok = 50;
println!("Stok dalam blok: {}", stok);
}
println!("Stok setelah blok: {}", stok);
}Output yang Dihasilkan:
Stok luar: 100
Stok dalam blok: 50
Stok setelah blok: 100Mari kita amati alurnya langkah demi langkah:
- Baris 2 (
let stok = 100;): variablestokmilik scope fungsimainlahir dengan nilai100. - Baris 6 (
let stok = 50;):letini berada di dalam blok baru, sehingga variablestokkedua hanya hidup di dalam blok dan membayangistoklama selama blok berjalan. - Baris 8 (
}): blok ditutup dan variablestokbernilai50musnah bersama scope-nya. - Baris 10 (
println!("Stok setelah blok: {}", stok);): namastokkini kembali mengacu pada variable luar yang tetap bernilai100.
Pola ini sering dipakai untuk perhitungan sementara: blok kecil menghitung versi data yang berbeda tanpa mencemari variable di scope luar.
Shadowing versus mut #
Kedua mekanisme sama-sama membuat nama variable memegang nilai berbeda, tetapi karakternya bertolak belakang. Kode berikut mencoba mengubah tipe variable mutable dan ditolak keras oleh kompilator:
fn main() {
let mut berat = 72;
berat = 72.5;
println!("Berat: {} kg", berat);
}Pesan galatnya berbunyi:
error[E0308]: mismatched types
--> main.rs:3:13
|
2 | let mut berat = 72;
| -- expected due to this value
3 | berat = 72.5;
| ^^^^ expected integer, found floating-point number
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.Kompilator menjelaskan bahwa tipe berat terkunci sebagai integer sejak 72 diberikan pada baris 2, sehingga 72.5 yang bertipe floating point ditolak. Tabel berikut merangkum perbedaan keduanya:
| Aspek | mut | Shadowing |
|---|---|---|
| Cara memakai | sekali di deklarasi, lalu ditimpa (berat = 80;) | let baru setiap kali nilai berubah |
| Tipe data | wajib tetap sama | boleh berbeda setiap let |
| Nilai lama | tertimpa dan hilang | masih ada di memori, hanya tertutup |
| Kapan dipilih | nilai benar-benar berubah (counter, saldo) | nilai berubah bentuk atau tahap (input → angka → hasil) |
Ringkasan #
Shadowing mendeklarasikan ulang variable bernama sama melalui let baru; variable baru membayangi variable lama, boleh bertipe berbeda, dan berumur sesuai scope tempat ia dideklarasikan. Pola ini ideal untuk transformasi bertahap yang mengubah bentuk data tanpa menumpuk nama perantara. Sebaliknya, mut tetap pilihan tepat untuk keadaan yang berubah dengan tipe yang tetap. Selanjutnya kita akan mengenal konstanta, kerabat variable yang nilainya dipatok sejak program dikompilasi.