Tipe Data

Tipe Data #

Rust adalah bahasa statically typed — setiap nilai punya tipe yang diketahui pasti saat kompilasi, dan tidak ada konversi implisit antar tipe. Tidak ada angka yang tiba-tiba menjadi string, tidak ada integer yang secara diam-diam diperlebar menjadi float. Ini terasa ketat di awal, tapi justru di sinilah kekuatan Rust: seluruh kelas bug yang berasal dari konversi tipe yang tak disengaja mustahil terjadi. Artikel ini membahas seluruh kategori tipe data Rust — dari skalar primitif di stack hingga koleksi dinamis di heap, dari tipe bawaan hingga tipe kustom yang kamu definisikan sendiri — beserta pola penggunaan dan jebakan yang perlu dihindari.

Peta Tipe Data Rust #

Sebelum membahas detail masing-masing, penting memahami bagaimana tipe-tipe di Rust dikategorikan:

flowchart TD
    T[Tipe Data Rust]

    T --> S[Skalar\nSatu nilai tunggal]
    T --> C[Komposit\nGabungan beberapa nilai]
    T --> R[Referensi\nPinjaman ke data lain]
    T --> K[Koleksi\nKumpulan data dinamis]
    T --> X[Tipe Khusus\nSemantics Rust]

    S --> S1[Integer\ni8 i16 i32 i64 i128 isize\nu8 u16 u32 u64 u128 usize]
    S --> S2[Float\nf32 f64]
    S --> S3[Boolean\nbool]
    S --> S4[Karakter\nchar]

    C --> C1[Tuple\n Tipe berbeda boleh]
    C --> C2[Array\nTipe sama ukuran tetap]

    R --> R1[Immutable &T]
    R --> R2[Mutable &mut T]
    R --> R3[Slice &str &[T]]

    K --> K1[Vec-T]
    K --> K2[String]
    K --> K3[HashMap dll]

    X --> X1[Option-T]
    X --> X2[Result-T-E]
    X --> X3[Unit Type]

Tipe Integer #

Integer adalah tipe yang paling sering digunakan. Rust menyediakan dua kelompok: signed (bisa negatif) dan unsigned (selalu nol atau positif), masing-masing dalam enam ukuran berbeda.

TipeUkuranNilai MinimumNilai Maksimum
i81 byte-128127
i162 byte-32.76832.767
i324 byte-2.147.483.6482.147.483.647
i648 byte-9,2 × 10¹⁸9,2 × 10¹⁸
i12816 byte-1,7 × 10³⁸1,7 × 10³⁸
isizePlatformBergantung arsitekturBergantung arsitektur
u81 byte0255
u162 byte065.535
u324 byte04.294.967.295
u648 byte01,8 × 10¹⁹
u12816 byte03,4 × 10³⁸
usizePlatform0Bergantung arsitektur

Tipe default untuk integer literal adalah i32 — ukuran yang paling efisien di mayoritas arsitektur modern.

fn main() {
    // Literal integer dengan berbagai basis
    let desimal = 1_000_000;      // underscore untuk keterbacaan
    let heksadesimal = 0xFF;      // awalan 0x
    let oktal = 0o77;             // awalan 0o
    let biner = 0b1111_0000;      // awalan 0b
    let byte = b'A';              // hanya u8, nilai ASCII dari 'A' = 65

    // Suffix tipe eksplisit
    let kecil = 100u8;
    let besar = 9_000_000_000i64;

    println!("{} {} {} {} {}", desimal, heksadesimal, oktal, biner, byte);
}

Integer Overflow #

Rust menangani integer overflow dengan cara yang berbeda tergantung build profile:

fn main() {
    let maks: u8 = 255;

    // Debug build: panic saat runtime dengan pesan yang jelas
    // Release build: wrapping — 255 + 1 = 0 (seperti aritmetika modular)
    // let overflow = maks + 1;

    // BENAR: gunakan method eksplisit jika wrapping/saturating/checked memang diinginkan
    let wrapping = maks.wrapping_add(1);        // 0
    let saturating = maks.saturating_add(1);    // 255 (tetap di nilai maks)
    let checked = maks.checked_add(1);          // None — tidak bisa
    let overflowing = maks.overflowing_add(1);  // (0, true) — nilai + apakah overflow

    println!("wrapping: {}", wrapping);
    println!("saturating: {}", saturating);
    println!("checked: {:?}", checked);
    println!("overflowing: {:?}", overflowing);
}

isize dan usize #

isize dan usize ukurannya mengikuti pointer di platform target — 32 bit pada sistem 32-bit, 64 bit pada sistem 64-bit. usize wajib digunakan sebagai tipe indeks array dan ukuran koleksi karena sistem memori menggunakan unit yang sama.

fn main() {
    let arr = [10, 20, 30, 40, 50];

    // BENAR: indeks array bertipe usize
    let indeks: usize = 2;
    println!("Elemen ke-{}: {}", indeks, arr[indeks]);

    // Vec::len() mengembalikan usize
    let v = vec![1, 2, 3];
    let panjang: usize = v.len();
    println!("Panjang: {}", panjang);

    // ANTI-PATTERN: menggunakan i32 sebagai indeks lalu cast
    let i: i32 = 2;
    // println!("{}", arr[i]); // error: expected usize, found i32
    println!("{}", arr[i as usize]); // harus cast eksplisit — pertanda desain kurang tepat
}

Tipe Float #

Rust memiliki dua tipe floating-point, keduanya mengikuti standar IEEE 754:

TipeUkuranPresisiKeterangan
f324 byte~7 digit desimalSingle precision
f648 byte~15 digit desimalDouble precision — default
fn main() {
    let x = 3.14;           // f64 — default
    let y: f32 = 3.14;      // f32 eksplisit
    let z = 2.0f64;         // suffix tipe

    // Konstanta matematika dari standard library
    let pi = std::f64::consts::PI;
    let e = std::f64::consts::E;
    let sqrt2 = std::f64::consts::SQRT_2;

    println!("π = {:.10}", pi);
    println!("e = {:.10}", e);
    println!("√2 = {:.10}", sqrt2);

    // Operasi float
    println!("sin(π/2) = {}", (pi / 2.0).sin()); // 1.0
    println!("log₂(8) = {}", 8f64.log2());        // 3.0
    println!("2^10 = {}", 2f64.powi(10));          // 1024.0
}

Perbandingan Float — Jebakan Umum #

fn main() {
    // ANTI-PATTERN: membandingkan float dengan == secara langsung
    let a = 0.1 + 0.2;
    let b = 0.3;
    println!("0.1 + 0.2 == 0.3: {}", a == b); // false! karena representasi biner

    // BENAR: bandingkan dengan epsilon (toleransi galat)
    let epsilon = f64::EPSILON;
    let hampir_sama = (a - b).abs() < epsilon * 10.0;
    println!("Hampir sama: {}", hampir_sama); // true

    // Nilai-nilai khusus float
    let tak_hingga = f64::INFINITY;
    let negatif_tak_hingga = f64::NEG_INFINITY;
    let bukan_angka = f64::NAN;

    println!("∞ > 1000: {}", tak_hingga > 1000.0); // true
    println!("NaN == NaN: {}", bukan_angka == bukan_angka); // false! NaN tidak sama dengan dirinya sendiri
    println!("NaN is NaN: {}", bukan_angka.is_nan()); // true — cara yang benar
}
Jangan pernah membandingkan nilai float dengan == langsung untuk logika bisnis penting. Representasi biner tidak bisa merepresentasikan semua pecahan desimal secara tepat — 0.1 + 0.2 tidak persis sama dengan 0.3 di hampir semua bahasa pemrograman. Gunakan perbandingan berbasis epsilon atau library seperti ordered-float untuk kasus yang membutuhkan presisi.

Boolean #

bool hanya punya dua nilai: true dan false. Ukurannya 1 byte meski hanya butuh 1 bit — ini keputusan desain untuk alignment memori.

fn main() {
    let aktif: bool = true;
    let nonaktif = false;

    // Operasi logika
    println!("AND: {}", aktif && nonaktif);   // false
    println!("OR:  {}", aktif || nonaktif);   // true
    println!("NOT: {}", !aktif);              // false

    // bool di kondisi — tidak perlu == true
    // ANTI-PATTERN: perbandingan eksplisit yang berlebihan
    if aktif == true {
        println!("Ini berlebihan");
    }

    // BENAR: cukup gunakan nilai bool langsung
    if aktif {
        println!("Lebih idiomatis");
    }

    // bool sebagai integer — bisa di-cast tapi jarang dibutuhkan
    let satu = true as i32;   // 1
    let nol = false as i32;   // 0
    println!("{} {}", satu, nol);

    // Fungsi yang mengembalikan bool sering menggunakan konvensi nama is_/has_/can_
    let angka = -5i32;
    println!("Negatif: {}", angka.is_negative());
    println!("Nol: {}", angka == 0);
}

Char #

char di Rust merepresentasikan satu Unicode Scalar Value — bukan satu byte, melainkan satu titik kode Unicode. Ukurannya selalu 4 byte, mendukung semua karakter dari semua bahasa, simbol, dan emoji.

fn main() {
    let huruf = 'A';            // ASCII, tapi tetap 4 byte
    let aksara = 'あ';          // Hiragana Jepang
    let arab = 'ع';             // huruf Arab
    let cina = '中';            // karakter CJK
    let emoji = '🦀';           // emoji kepiting Ferris, maskot Rust

    println!("{} {} {} {} {}", huruf, aksara, arab, cina, emoji);

    // char menggunakan single quote — BUKAN double quote
    // ANTI-PATTERN: double quote menghasilkan &str, bukan char
    // let salah: char = "A"; // error: expected `char`, found `&str`

    // Konversi char ke/dari u32
    let kode = 'A' as u32;
    println!("Kode ASCII 'A': {}", kode); // 65

    let dari_kode = char::from_u32(9829); // ♥
    println!("Dari kode 9829: {:?}", dari_kode); // Some('♥')

    // Iterasi string berdasarkan char — bukan byte
    let kata = "halo";
    for c in kata.chars() {
        print!("[{}]", c);
    }
    println!(); // [h][a][l][o]
}

Tuple #

Tuple mengelompokkan sejumlah nilai dengan tipe yang boleh berbeda-beda menjadi satu unit. Ukurannya tetap dan tipe setiap posisi sudah diketahui saat kompilasi.

fn main() {
    // Deklarasi dengan anotasi tipe eksplisit
    let koordinat: (f64, f64, f64) = (1.5, -2.3, 0.0);

    // Akses via indeks (dimulai dari .0)
    println!("x={}, y={}, z={}", koordinat.0, koordinat.1, koordinat.2);

    // Destructuring — cara yang lebih idiomatis
    let (x, y, z) = koordinat;
    println!("Destructured: {}, {}, {}", x, y, z);

    // Partial destructuring dengan _
    let (penting, _, juga_penting) = (1, 2, 3);
    println!("{} {}", penting, juga_penting);

    // Tuple sebagai return value — mengembalikan beberapa nilai
    fn min_maks(data: &[i32]) -> (i32, i32) {
        let min = *data.iter().min().unwrap();
        let maks = *data.iter().max().unwrap();
        (min, maks)
    }

    let angka = [5, 2, 8, 1, 9, 3];
    let (min, maks) = min_maks(&angka);
    println!("Min: {}, Maks: {}", min, maks);

    // Unit type () — tuple kosong, return type fungsi tanpa nilai
    let unit: () = ();
    println!("Unit: {:?}", unit); // ()
}

Tuple paling tepat digunakan untuk mengembalikan dua atau tiga nilai dari fungsi yang hubungannya jelas tanpa perlu membuat struct khusus. Untuk empat nilai atau lebih, struct dengan field bernama jauh lebih mudah dibaca.


Array #

Array menyimpan sejumlah nilai dengan tipe yang sama dalam ukuran yang tetap sejak kompilasi. Seluruh datanya ada di stack — tidak ada alokasi heap.

fn main() {
    // Deklarasi dengan tipe dan ukuran eksplisit
    let bulan: [&str; 12] = [
        "Januari", "Februari", "Maret", "April",
        "Mei", "Juni", "Juli", "Agustus",
        "September", "Oktober", "November", "Desember",
    ];

    // Inisialisasi dengan nilai yang sama
    let buffer = [0u8; 1024]; // 1024 elemen, semua 0

    println!("Bulan ke-3: {}", bulan[2]); // Maret
    println!("Ukuran buffer: {}", buffer.len()); // 1024

    // Iterasi array
    for (i, nama) in bulan.iter().enumerate() {
        if i < 3 {
            println!("Bulan {}: {}", i + 1, nama);
        }
    }

    // ANTI-PATTERN: akses indeks tanpa validasi di kode produksi
    let indeks: usize = 15;
    // let elemen = bulan[indeks]; // panic: index out of bounds at runtime

    // BENAR: gunakan .get() yang mengembalikan Option
    match bulan.get(indeks) {
        Some(nama) => println!("Bulan: {}", nama),
        None => println!("Indeks {} tidak valid", indeks),
    }
}

Array vs Vec — Kapan Memilih #

Gunakan Array jika:
  ✓ Ukuran sudah diketahui dan tetap saat kompilasi
  ✓ Data kecil dan ingin di stack (tanpa alokasi heap)
  ✓ Performa kritis dan ukuran tidak berubah
  ✓ Digunakan sebagai buffer dengan ukuran tetap

Gunakan Vec jika:
  ✓ Ukuran tidak diketahui saat kompilasi
  ✓ Perlu menambah atau menghapus elemen di runtime
  ✓ Membaca data dari input pengguna atau file
  ✓ Hasil dari operasi iterator (.collect())

String dan &str #

Rust memiliki dua tipe utama untuk teks, dan perbedaan di antara keduanya adalah salah satu hal yang paling penting dipahami:

AspekString&str
AlokasiHeap (dimiliki)Stack / bagian dari String / static
OwnershipOwned — punya datanyaBorrowed — meminjam dari tempat lain
MutabilitasBisa diubah (jika mut)Tidak bisa diubah
UkuranDinamis, bisa bertambahTetap — hanya view ke data
Kapan digunakanPerlu modifikasi atau return owned stringParameter fungsi, string literal, slice
fn main() {
    // &str — string literal, ada di segment data program (lifetime 'static)
    let literal: &str = "halo dunia";

    // String — dialokasikan di heap, bisa dimodifikasi
    let mut owned = String::from("halo");
    owned.push_str(" dunia");
    owned.push('!');

    println!("{}", literal);
    println!("{}", owned);

    // Konversi
    let dari_literal: String = literal.to_string();       // &str → String
    let juga_string = String::from(literal);               // &str → String
    let sebagai_slice: &str = &owned;                      // String → &str
    let slice_sebagian: &str = &owned[0..4];               // "halo"

    println!("{} {}", dari_literal, sebagai_slice);

    // Operasi String umum
    let mut s = String::new();
    s.push_str("baris pertama\n");
    s.push_str("baris kedua");

    println!("Panjang: {} byte", s.len());
    println!("Kosong: {}", s.is_empty());
    println!("Mengandung 'pertama': {}", s.contains("pertama"));

    // Formatting — cara paling idiomatis membuat String
    let nama = "Budi";
    let usia = 30;
    let perkenalan = format!("Nama: {}, Usia: {}", nama, usia);
    println!("{}", perkenalan);
}
// ANTI-PATTERN: parameter &String — terlalu spesifik
fn cetak_panjang(s: &String) -> usize {
    s.len()
}

// BENAR: parameter &str — lebih fleksibel, menerima &String dan &str sekaligus
fn cetak_panjang(s: &str) -> usize {
    s.len()
}

fn main() {
    let owned = String::from("halo");
    let literal = "dunia";

    println!("{}", cetak_panjang(&owned));   // &String → &str otomatis
    println!("{}", cetak_panjang(literal));  // &str langsung
    println!("{}", cetak_panjang(&owned[1..3])); // slice juga valid
}

Vec<T> #

Vec<T> adalah array dinamis — seperti array tapi ukurannya bisa berubah di runtime. Ini adalah koleksi yang paling sering digunakan di Rust.

fn main() {
    // Membuat Vec
    let mut v1: Vec<i32> = Vec::new();  // kosong
    let v2 = vec![1, 2, 3, 4, 5];       // macro vec! — cara paling ringkas
    let v3: Vec<i32> = (1..=10).collect(); // dari iterator

    // Menambah elemen
    v1.push(10);
    v1.push(20);
    v1.push(30);

    // Mengakses elemen
    println!("Elemen pertama: {}", v2[0]);       // panic jika out of bounds
    println!("Safe access: {:?}", v2.get(10));   // None — tidak panic

    // Mengubah elemen
    let mut v4 = vec![1, 2, 3];
    v4[1] = 99;
    println!("{:?}", v4); // [1, 99, 3]

    // Menghapus elemen
    let terakhir = v4.pop();         // hapus dan kembalikan elemen terakhir
    let dua = v4.remove(0);          // hapus di indeks, geser elemen lain
    println!("Pop: {:?}, Remove: {}", terakhir, dua);

    // Iterasi
    for elemen in &v2 {              // immutable borrow
        print!("{} ", elemen);
    }
    println!();

    for elemen in &mut v4 {          // mutable borrow — bisa modifikasi
        *elemen *= 2;
    }
    println!("{:?}", v4);

    // Kapasitas dan panjang
    let mut v5: Vec<i32> = Vec::with_capacity(100); // alokasi untuk 100 elemen
    println!("Panjang: {}, Kapasitas: {}", v5.len(), v5.capacity());
}

Option<T> #

Option<T> adalah enum bawaan Rust yang merepresentasikan nilai yang mungkin ada (Some(T)) atau tidak ada (None). Ini adalah pengganti null yang aman — compiler memaksa kamu menangani kedua kemungkinan.

fn cari_pengguna(id: u32) -> Option<String> {
    match id {
        1 => Some(String::from("Budi")),
        2 => Some(String::from("Sari")),
        _ => None,
    }
}

fn main() {
    // Pattern matching — cara paling eksplisit
    match cari_pengguna(1) {
        Some(nama) => println!("Ditemukan: {}", nama),
        None => println!("Tidak ditemukan"),
    }

    // if let — lebih ringkas jika hanya butuh kasus Some
    if let Some(nama) = cari_pengguna(2) {
        println!("Pengguna: {}", nama);
    }

    // unwrap_or — nilai default jika None
    let nama = cari_pengguna(99).unwrap_or(String::from("Anonim"));
    println!("Nama: {}", nama);

    // unwrap_or_else — nilai default dari closure (lazy evaluation)
    let nama2 = cari_pengguna(99).unwrap_or_else(|| format!("Tamu-{}", 99));
    println!("Nama2: {}", nama2);

    // map — transformasi nilai di dalam Some, None tetap None
    let panjang = cari_pengguna(1).map(|n| n.len());
    println!("Panjang nama: {:?}", panjang); // Some(4)

    // ANTI-PATTERN: unwrap tanpa pemeriksaan di kode produksi
    // cari_pengguna(99).unwrap(); // panic: called `Option::unwrap()` on a `None` value

    // ? operator — propagasi None ke atas (dalam fungsi yang return Option)
    fn nama_uppercase(id: u32) -> Option<String> {
        let nama = cari_pengguna(id)?; // jika None, langsung return None
        Some(nama.to_uppercase())
    }
    println!("{:?}", nama_uppercase(1)); // Some("BUDI")
    println!("{:?}", nama_uppercase(99)); // None
}

Result<T, E> #

Result<T, E> adalah enum untuk operasi yang bisa berhasil (Ok(T)) atau gagal (Err(E)). Ini adalah cara idiomatis Rust menangani error yang bisa dipulihkan.

use std::num::ParseIntError;

fn parse_positif(s: &str) -> Result<u32, ParseIntError> {
    s.trim().parse::<u32>()
}

fn main() {
    // Pattern matching
    match parse_positif("42") {
        Ok(n) => println!("Berhasil: {}", n),
        Err(e) => println!("Gagal: {}", e),
    }

    // unwrap_or — nilai default jika error
    let n = parse_positif("abc").unwrap_or(0);
    println!("Default: {}", n);

    // map dan map_err — transformasi Ok atau Err
    let dikali_dua = parse_positif("21").map(|n| n * 2);
    println!("{:?}", dikali_dua); // Ok(42)

    // is_ok() dan is_err()
    println!("Valid: {}", parse_positif("5").is_ok());     // true
    println!("Invalid: {}", parse_positif("x").is_err()); // true

    // ? operator dalam fungsi yang return Result
    fn hitung(a: &str, b: &str) -> Result<u32, ParseIntError> {
        let x = parse_positif(a)?; // jika Err, return Err langsung ke pemanggil
        let y = parse_positif(b)?;
        Ok(x + y)
    }

    println!("{:?}", hitung("10", "32")); // Ok(42)
    println!("{:?}", hitung("10", "xx")); // Err(...)
}

Tipe Generik #

Generik memungkinkan kamu menulis fungsi, struct, dan enum yang bekerja untuk berbagai tipe tanpa duplikasi kode. Compiler menghasilkan versi spesifik untuk setiap tipe yang digunakan — monomorphization — sehingga tidak ada overhead runtime.

// Fungsi generik dengan trait bound
fn terbesar<T: PartialOrd>(daftar: &[T]) -> &T {
    let mut maks = &daftar[0];
    for item in daftar {
        if item > maks {
            maks = item;
        }
    }
    maks
}

// Struct generik
struct Pasangan<T, U> {
    pertama: T,
    kedua: U,
}

impl<T: std::fmt::Display, U: std::fmt::Display> Pasangan<T, U> {
    fn cetak(&self) {
        println!("({}, {})", self.pertama, self.kedua);
    }
}

fn main() {
    // Fungsi generik bekerja untuk i32 dan f64
    let angka = vec![34, 50, 25, 100, 65];
    println!("Terbesar: {}", terbesar(&angka));

    let huruf = vec!['y', 'm', 'a', 'q'];
    println!("Terbesar: {}", terbesar(&huruf));

    // Struct generik dengan tipe berbeda
    let p1 = Pasangan { pertama: 5, kedua: "halo" };
    let p2 = Pasangan { pertama: 3.14, kedua: true };
    p1.cetak(); // (5, halo)
    p2.cetak(); // (3.14, true)
}

Struct dan Enum sebagai Tipe Kustom #

Untuk data yang lebih kompleks, Rust menyediakan struct dan enum untuk mendefinisikan tipe kustom yang bermakna dalam domain masalahmu.

// Struct dengan named fields
struct Pengguna {
    nama: String,
    email: String,
    usia: u8,
    aktif: bool,
}

impl Pengguna {
    fn baru(nama: &str, email: &str, usia: u8) -> Self {
        Pengguna {
            nama: nama.to_string(),
            email: email.to_string(),
            usia,
            aktif: true,
        }
    }

    fn sapa(&self) -> String {
        format!("Halo, {}!", self.nama)
    }
}

// Enum dengan data di setiap variant
enum Bentuk {
    Lingkaran(f64),                       // radius
    Persegi(f64),                         // sisi
    PersegPanjang { lebar: f64, tinggi: f64 }, // named fields
}

impl Bentuk {
    fn luas(&self) -> f64 {
        match self {
            Bentuk::Lingkaran(r) => std::f64::consts::PI * r * r,
            Bentuk::Persegi(s) => s * s,
            Bentuk::PersegPanjang { lebar, tinggi } => lebar * tinggi,
        }
    }
}

fn main() {
    let user = Pengguna::baru("Budi", "[email protected]", 28);
    println!("{}", user.sapa());
    println!("Aktif: {}", user.aktif);

    let bentuk_list = vec![
        Bentuk::Lingkaran(5.0),
        Bentuk::Persegi(4.0),
        Bentuk::PersegPanjang { lebar: 6.0, tinggi: 3.0 },
    ];

    for bentuk in &bentuk_list {
        println!("Luas: {:.2}", bentuk.luas());
    }
}

Ringkasan #

  • Default integer adalah i32, default float adalah f64 — gunakan tipe yang lebih kecil hanya jika ada alasan memori atau interoperabilitas yang jelas.
  • isize/usize untuk indeks dan ukuran — seluruh sistem indexing Rust menggunakan usize; jangan gunakan i32 sebagai indeks array atau Vec.
  • Jangan bandingkan float dengan == — gunakan epsilon ((a - b).abs() < toleransi) atau library khusus untuk presisi kritis.
  • char adalah 4 byte Unicode Scalar Value — bukan byte tunggal. Gunakan single quote ('a'), bukan double quote.
  • String vs &strString adalah owned string yang bisa dimodifikasi di heap; &str adalah borrowed view ke data string yang sudah ada. Gunakan &str untuk parameter fungsi.
  • Array untuk ukuran tetap di stack, Vec<T> untuk ukuran dinamis di heap — keduanya bisa diiterasi dan di-slice dengan cara yang sama.
  • Option<T> menggantikan null — compiler memaksa kamu menangani None. Gunakan map, unwrap_or, if let, atau ? untuk menghindari verbose match.
  • Result<T, E> untuk error yang bisa dipulihkan — operator ? menyederhanakan propagasi error secara dramatis.
  • Generik tanpa overhead runtime — Rust menggunakan monomorphization: compiler menghasilkan kode spesifik per tipe, hasil performa sama dengan kode non-generik.
  • struct untuk data dengan field bernama, enum untuk data yang bisa berupa beberapa bentuk berbeda — keduanya bisa punya method lewat impl.

← Sebelumnya: Konstanta   Berikutnya: Operator →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact