Array di Stack atau Heap
Latihan membuat instruksi pembelajaran tentang Array dan alokasi memory, baik itu di Stack atau Heap. Dengan pertimbangan implementasi di pemrograman Rust.
Array adalah salah satu struktur data paling dasar dan penting dalam pemrograman. Dengan memahami array, kita bisa menyimpan data dalam urutan tertentu, mengaksesnya secara cepat, dan memprosesnya secara efisien. Artikel ini membahas perbedaan antara array statis dan array dinamis, bagaimana menggunakannya dalam Rust, dan konsekuensi efisiensi dari tiap pendekatan.
Tujuan Pembelajaran
Setelah membaca artikel ini, Anda diharapkan dapat:
Memahami konsep memori komputer: stack vs heap.
Menjelaskan perbedaan array statis (ukuran tetap) dan array dinamis (ukuran fleksibel).
Menjelaskan mengapa akses array dikatakan O(1) (konstan dan cepat).
Mengimplementasikan contoh penghapusan elemen dengan pergeseran pada array statis di Rust.
Menggunakan
Vec<T>
untuk operasi dinamis seperti menambah dan menghapus elemen.Mengenali situasi kapan array statis, array dinamis, atau heap-allocated fixed array lebih sesuai.
Prasyarat
Pemahaman dasar variabel dan tipe data di Rust.
Pemahaman perulangan
for
dan percabanganif
.Tidak perlu pengalaman mendalam dengan manajemen memori, karena artikel ini akan menjelaskan konsep stack dan heap.
Memori: Stack vs Heap
Untuk memahami perbedaan array statis dan dinamis, kita perlu mengenal dua area utama memori:
Stack
Struktur memori berbentuk tumpukan (stack).
Alokasi dan dealokasi terjadi secara otomatis mengikuti pola LIFO (Last In, First Out).
Sangat cepat karena hanya memindahkan "penanda stack" ke atas/bawah.
Cocok untuk data kecil dan berumur pendek.
Contoh: variabel lokal dan array statis yang ukurannya diketahui di saat kompilasi.
Heap
Area memori yang lebih fleksibel.
Alokasi dilakukan saat program berjalan (run-time).
Cocok untuk data yang ukurannya tidak diketahui saat kompilasi, atau yang berumur panjang.
Akses sedikit lebih lambat dibanding stack karena perlu pencarian alamat memori yang cukup besar.
Contoh:
Vec<T>
,Box<T>
, atau objek dinamis lainnya.
Rust secara otomatis mengelola memori heap menggunakan sistem kepemilikan (ownership) dan akan membebaskan memori (drop) saat variabel keluar dari scope, sehingga programmer tidak perlu memanggil free()
secara manual seperti di C.
Mengapa Akses Array O(1)?
Ketika kita mengakses array[i]
, komputer menghitung alamat memori elemen tersebut dengan:
alamat_elemen = alamat_awal_array + (i × ukuran_elemen)
Karena ukuran elemen tetap dan alamat awal sudah diketahui, perhitungan ini dapat dilakukan langsung, tanpa iterasi. Inilah mengapa akses array disebut O(1) — waktu akses tidak bergantung pada jumlah elemen array.
Array Statis di Rust
Array statis memiliki ukuran yang tetap, ditentukan saat kompilasi. Contoh deklarasi:
let angka: [i32; 5] = [10, 20, 30, 40, 50];
Ukuran [5]
harus diketahui di awal. Anda tidak dapat menambah atau mengurangi elemen setelah array dibuat.
Menghapus Elemen dengan Pergeseran Manual
fn main() {
// Array statis dengan Option untuk menandai slot kosong
let mut data: [Option<i32>; 5] = [Some(10), Some(20), Some(30), Some(40), Some(50)];
let mut n = 5; // ukuran logis array
println!("Array awal:");
for i in 0..n {
print!("{:?} ", data[i]);
}
println!();
let hapus_index = 2; // kita ingin hapus nilai di indeks ke-2
// Geser elemen setelahnya ke kiri
for i in hapus_index..(n - 1) {
data[i] = data[i + 1];
}
// Tandai elemen terakhir kosong
data[n - 1] = None;
n -= 1;
println!("Array setelah menghapus elemen indeks {}:", hapus_index);
for i in 0..n {
print!("{:?} ", data[i]);
}
println!();
}
Mengapa menggunakan Option<T>
? Karena array statis tidak bisa mengubah ukurannya, kita menggunakan Option
agar bisa menandai slot kosong (None
) dan slot terisi (Some(x)
). Ini membantu membuat logika "hapus" lebih eksplisit.
Array Dinamis di Rust dengan Vec<T>
Rust menyediakan vector (Vec<T>
), yang merupakan array dinamis. Kita bisa menambah atau menghapus elemen tanpa mengatur alokasi manual.
fn main() {
let mut v = vec![10, 20, 30, 40, 50];
println!("Vector awal: {:?}", v);
v.remove(2); // hapus indeks ke-2
println!("Setelah remove(2): {:?}", v);
v.push(60); // tambah elemen baru di akhir
println!("Setelah push(60): {:?}", v);
}
Rust akan menggeser elemen setelah indeks yang dihapus secara otomatis, dan akan memperluas kapasitas heap jika push
melebihi kapasitas saat ini.
Heap-Allocated Fixed Array dengan Box<[T]>
Jika Anda ingin menyimpan array berukuran tetap tetapi di heap (misalnya karena ukurannya besar), Anda bisa menggunakan Box<[T]>
:
fn main() {
let v = vec![1, 2, 3, 4];
let b = v.into_boxed_slice(); // konversi Vec menjadi Box<[T]>
println!("Boxed slice: {:?}", b);
}
Berbeda dengan Vec
, Box<[T]>
memiliki ukuran tetap sehingga tidak bisa push
atau remove
.
Tips Efisiensi: Pre-allocate dan try_reserve
Vec
secara internal memiliki kapasitas yang bisa bertambah secara bertahap. Jika Anda tahu akan menyimpan banyak elemen, gunakan with_capacity()
agar mengurangi reallocation:
let mut v: Vec<i32> = Vec::with_capacity(1000);
Dan jika ingin menangani kemungkinan kegagalan alokasi tanpa panic:
if let Err(e) = v.try_reserve(1_000_000_000) {
eprintln!("Tidak bisa mengalokasi memori: {}", e);
}
Refleksi
Apa perbedaan utama antara stack dan heap? Mengapa
Vec
menggunakan heap?Mengapa akses array bisa O(1), tetapi operasi
remove()
atauinsert()
di tengah bisa O(n)?Dalam kasus nyata (misalnya data sensor yang ukurannya terus berubah), mana yang lebih cocok: array statis atau
Vec
?Kapan sebaiknya Anda menggunakan
Box<[T]>
dibandingVec<T>
?Bagaimana pre-allocation (
with_capacity
) bisa membantu mengurangi biaya reallocation?
Referensi
Pembahasan mengenai array statis, array dinamis, serta implikasi memori dan efisiensi terinspirasi dari:
Sekolah Teknik Elektro dan Informatika, Institut Teknologi Bandung. Array statik vs dinamis.