Topik ini membahas tipe data bawaan Rust yang paling mendasar: empat tipe skalar berupa integer, float, bool, dan char, serta dua tipe majemuk berupa tuple dan array. Pembaca yang menyelesaikan topik ini dapat memilih tipe yang tepat untuk setiap nilai, memahami batas rentang integer beserta akibat meluapnya, dan menyimpan beberapa nilai sekaligus dalam satu variabel.
Tipe skalar#
Tipe skalar menyimpan satu nilai tunggal. Rust menyediakan empat kelompok tipe skalar, dan integer memiliki pilihan paling banyak karena tiap lebar bit tersedia dalam varian bertanda dan tanpa tanda.
| Lebar | Bertanda | Tanpa tanda |
|---|---|---|
| 8 bit | i8 | u8 |
| 16 bit | i16 | u16 |
| 32 bit | i32 | u32 |
| 64 bit | i64 | u64 |
| 128 bit | i128 | u128 |
| lebar penunjuk | isize | usize |
Varian bertanda mewakili bilangan negatif dan positif dengan penyimpanan komplemen dua, yaitu format biner lazim untuk bilangan bertanda. Varian tanpa tanda hanya mewakili bilangan nol dan positif. Sebagai gambaran rentangnya, i8 mencakup -128 sampai 127 dan u8 mencakup 0 sampai 255. Tipe isize dan usize mengikuti lebar penunjuk arsitektur komputer, 64 bit pada mesin 64 bit, dan lazim dipakai sebagai indeks koleksi. Compiler memilih i32 ketika tidak ada anotasi maupun petunjuk lain, karena tipe tersebut umumnya cukup cepat diproses.
Bilangan pecahan memiliki dua tipe, yaitu f32 dengan presisi tunggal dan f64 dengan presisi ganda. Tipe f64 menjadi pilihan bawaan compiler. Tipe bool hanya memuat true atau false dan besarnya satu byte. Tipe char memuat satu karakter Unicode dan besarnya empat byte, jauh lebih besar dari satu byte pada banyak bahasa lain. Program berikut memperlihatkan keempat tipe skalar beserta ukuran sebenarnya di memori.
fn main() {
let bilangan = 42;
let suhu = 36.5;
let aktif = true;
let huruf = 'R';
println!("bilangan: {bilangan}");
println!("suhu: {suhu}");
println!("aktif: {aktif}");
println!("huruf: {huruf}");
}bilangan: 42
suhu: 36.5
aktif: true
huruf: Ruse std::mem::size_of;
fn main() {
println!("ukuran bool: {} byte", size_of::<bool>());
println!("ukuran char: {} byte", size_of::<char>());
println!("ukuran i32: {} byte", size_of::<i32>());
println!("ukuran f64: {} byte", size_of::<f64>());
}ukuran bool: 1 byte
ukuran char: 4 byte
ukuran i32: 4 byte
ukuran f64: 8 byteFungsi size_of dari modul std::mem melaporkan besar satu nilai bertipe tersebut. Nilainya pasti karena ukuran tipe primitif ditetapkan standar Rust, bukan bergantung pada mesin. Sebagai contoh, char selalu empat byte di semua platform.
Anotasi tipe#
Anotasi tipe ditulis setelah nama variabel dengan tanda titik dua ketika bentuk default tidak sesuai kebutuhan. Penulisan angka besar juga dapat memakai garis bawah sebagai pemisah agar mudah dibaca.
fn main() {
let kecil: u8 = 200;
let besar: i64 = 4_000_000_000;
let presisi: f32 = 2.5;
let omega = 'Ω';
println!("kecil: {kecil}");
println!("besar: {besar}");
println!("presisi: {presisi}");
println!("omega: {omega}");
}kecil: 200
besar: 4000000000
presisi: 2.5
omega: ΩNilai 200 memerlukan anotasi u8 ketika tipe delapan bit tanpa tanda memang dikehendaki, karena tanpa anotasi compiler memilih i32. Literal char ditulis dengan tanda kutip tunggal dan boleh berisi huruf Latin, huruf Yunani seperti Ω, maupun karakter lain yang termasuk skalar Unicode, yaitu satuan nilai pada standar Unicode. Perlu dicatat bahwa char menyimpan satu karakter saja, sedangkan teks yang lebih panjang bertipe string yang dibahas pada modul collections.
Integer overflow#
Nilai integer memiliki batas ketat, dan operasi yang melampaui batas tersebut disebut overflow. Compiler bahkan menolak operasi yang terbukti meluap pada saat kompilasi, seperti pada contoh sengaja salah berikut.
printf 'fn main() {\n let batas: u8 = 255;\n let lewat: u8 = batas + 1;\n println!("nilai: {lewat}");\n}\n' > salah.rs
rustc --edition=2021 salah.rs 2>&1 | sed -n '1,9p'error: this arithmetic operation will overflow
--> salah.rs:3:21
|
3 | let lewat: u8 = batas + 1;
| ^^^^^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
|
= note: `#[deny(arithmetic_overflow)]` on by default
error: aborting due to 1 previous errorCompiler menelusuri bahwa batas pasti bernilai 255 sepanjang program, sehingga penjumlahan dengan 1 terbukti meluap dari rentang u8. Ketika nilai baru diketahui saat program berjalan, compiler tidak dapat membuktikan bahwa operasi meluap, sehingga perilaku operasi mengikuti profil kompilasi. Profil yang lazim dipakai ada dua, yaitu profil debug saat pengembangan dan profil rilis saat distribusi. Pada profil debug, operasi yang meluap memicu panic dengan pesan attempt to add with overflow dan program berhenti. Pada profil rilis, nilai dibungkus mengikuti komplemen dua, misalnya 255 ditambah 1 menjadi 0.
Ketika hasil meluap perlu ditangani secara sadar, integer menyediakan beberapa metode eksplisit. Metode wrapping_add membungkus nilai, saturating_add menahan nilai di batas rentang, dan checked_add mengembalikan Option yang berisi None ketika meluap.
fn main() {
let batas: u8 = 255;
println!("wrapping_add: {}", batas.wrapping_add(1));
println!("saturating_add: {}", batas.saturating_add(1));
}wrapping_add: 0
saturating_add: 255Tuple#
Tuple mengelompokkan beberapa nilai yang tipenya boleh berbeda ke dalam satu tipe majemuk. Panjangnya tetap setelah dibuat, dan tiap posisi memiliki tipenya masing-masing.
fn main() {
let catatan = (24, 36.5, true);
let (nomor, suhu, lulus) = catatan;
println!("nomor: {nomor}");
println!("suhu: {suhu}");
println!("lulus: {lulus}");
println!("akses langsung: {}", catatan.0);
println!("tampilan debug: {:?}", catatan);
}nomor: 24
suhu: 36.5
lulus: true
akses langsung: 24
tampilan debug: (24, 36.5, true)Pemecahan tuple (destructuring) pada baris let (nomor, suhu, lulus) memisahkan ketiga isinya ke variabel terpisah sekaligus. Nilai juga dapat diakses per posisi dengan tanda titik, misalnya catatan.0 untuk elemen pertama, dan penomoran dimulai dari nol. Penentu pemformatan {:?} mencetak bentuk debug sebuah nilai. Sebuah tuple dapat dicetak dengan penentu tersebut apabila setiap anggotanya juga dapat dicetak dalam bentuk debug. Tuple tanpa isi ditulis () dan disebut tipe unit, yang kerap muncul sebagai nilai kembalian fungsi tanpa hasil.
Jumlah elemen turut menjadi bagian dari tipe, sehingga (i32, f64) dan (i32, f64, bool) adalah dua tipe yang berbeda dan tidak dapat dipertukarkan. Akses posisi di luar jumlah elemen, misalnya catatan.3 pada tuple tiga elemen, ditolak langsung saat kompilasi dengan error E0609 karena posisi tersebut tidak pernah ada pada tipenya. Dengan demikian, kesalahan penomoran pada tuple selalu tertangkap sebelum program berjalan.
Array#
Array menyimpan sejumlah nilai dengan tipe yang sama dan panjang yang tetap. Sintaksisnya menuliskan tipe beserta jumlah elemen sebagai [T; N], misalnya [i32; 3] untuk tiga bilangan bertanda 32 bit.
fn main() {
let bulan = ["Januari", "Februari", "Maret"];
let nilai = [80, 90, 100];
println!("bulan pertama: {}", bulan[0]);
println!("bulan terakhir: {}", bulan[2]);
println!("jumlah nilai: {}", nilai.len());
println!("semua nilai: {:?}", nilai);
}bulan pertama: Januari
bulan terakhir: Maret
jumlah nilai: 3
semua nilai: [80, 90, 100]Karena panjangnya bagian dari tipenya, compiler mengetahui jumlah elemen setiap array dan memeriksa banyak kesalahan sejak kompilasi. Metode len melaporkan panjang array, dan elemen diakses dengan indeks yang dimulai dari nol. Array cocok untuk data yang jumlahnya pasti, misalnya daftar nama bulan. Ketika jumlah data tumbuh saat program berjalan, struktur Vec yang dibahas pada modul collections menjadi pilihan yang tepat.
Panjang yang melekat pada tipe juga membuat [i32; 3] dan [i32; 4] menjadi dua tipe yang berbeda sehingga keduanya tidak dapat dipertukarkan, misalnya saat array dikirim sebagai argumen fungsi. Literal array dapat dibuat dengan sintaksis pengulangan seperti [0; 5] yang menghasilkan lima elemen bernilai nol. Seluruh elemen array tersimpan berurutan di satu blok memori, sehingga akses per elemen tidak memerlukan penelusuran tambahan.
Kesalahan umum#
Mengakses indeks di luar batas array. Ketika indeks berupa angka tetap seperti bulan[5], compiler menolak akses tersebut saat kompilasi karena panjang array sudah diketahui dari tipenya. Ketika nilai indeks baru diketahui saat program berjalan, pemeriksaan dilakukan pada saat akses terjadi. Program berhenti dengan panic index out of bounds apabila indeks berada di luar rentang nol sampai panjang dikurangi satu. Gunakan metode get yang mengembalikan Option apabila indeks di luar batas merupakan kemungkinan yang wajar dalam alur program.
Menyangka char berukuran satu byte. Ukuran char adalah empat byte karena nilainya berupa skalar Unicode, bukan satu byte ASCII. Menghitung panjang teks dengan asumsi satu karakter sama dengan satu byte menghasilkan angka yang salah untuk teks non-ASCII.
Ringkasan#
- Integer tersedia dalam varian bertanda dan tanpa tanda dari 8 sampai 128 bit, dengan
i32sebagai pilihan bawaan danf64sebagai float bawaan. boolberukuran satu byte dancharberukuran empat byte serta menyimpan satu skalar Unicode.- Overflow pada operasi yang dapat dibuktikan compiler ditolak saat kompilasi, sementara overflow saat program berjalan memicu panic pada profil debug dan membungkus pada profil rilis.
- Tuple mengelompokkan nilai dengan tipe berbeda dan panjang tetap, diakses dengan indeks titik atau dipecah dengan destructuring.
- Array bertipe
[T; N]menyimpan nilai bertipe sama dengan panjang tetap, dan akses indeks di luar batas memicu panic saat program berjalan.
Referensi#
- Data Types — The Rust Book — Rincian tipe skalar, tuple, array, serta aturan integer overflow.
- Primitives — Rust by Example — Contoh penggunaan tipe primitif beserta anotasinya.
- char — Rust standard library
— Dokumentasi tipe
chardan jangkauan skalar Unicode yang diwakilinya.