Tipe di Rust sering tumbuh panjang: satu deklarasi seperti HashMap<u64, Vec<String>> sudah cukup melelahkan dibaca, dan tipe yang melibatkan trait object bisa lebih panjang lagi. Menuliskan tipe sepanjang itu berulang kali di signature fungsi, deklarasi variabel, dan nilai kembalian membuat kode sulit dipelihara — satu perubahan bentuk tipe harus dikejar ke banyak tempat. Type alias menyelesaikan kelelahan tersebut dengan satu kata kunci type: kita memberi nama ringkas untuk tipe panjang, dan nama itu dapat dipakai di seluruh kode. Penting dipahami sejak awal bahwa alias hanyalah nama lain — bukan tipe baru — sehingga perilakunya sangat berbeda dari newtype yang kita pelajari di Chapter sebelumnya.

Menyingkat Tipe yang Panjang #

Kata kunci type membuat alias dengan bentuk type Nama = Tipe;. Setelah deklarasi itu, Nama dapat menggantikan Tipe di posisi mana pun. Mari kita lihat pada kasus riwayat pesanan pelanggan:

rust
use std::collections::HashMap;

type CustomerId = u64;
type OrderHistory = HashMap<CustomerId, Vec<String>>;

fn main() {
    let mut history: OrderHistory = HashMap::new();

    history.insert(101, vec![String::from("Kopi Gayo"), String::from("Teh Melati")]);
    history.insert(202, vec![String::from("Roti Gandum")]);

    let customer: CustomerId = 101;

    match history.get(&customer) {
        Some(orders) => {
            println!("Pelanggan {} punya {} pesanan:", customer, orders.len());
            for order in orders {
                println!("- {}", order);
            }
        }
        None => println!("Pelanggan {} belum pernah memesan", customer),
    }
}

Output yang Dihasilkan:

text
Pelanggan 101 punya 2 pesanan:
- Kopi Gayo
- Teh Melati

Mari kita bedah peran setiap baris pentingnya:

  1. Baris 3 (type CustomerId = u64;): alias CustomerId menggantikan u64 pada seluruh kode setelah baris ini. Pembaca kode kini tahu bahwa u64 tersebut adalah id pelanggan, bukan angka biasa.
  2. Baris 4 (type OrderHistory = HashMap<CustomerId, Vec<String>>;): satu nama mewakili tipe panjang. Alias juga boleh merujuk alias lain, seperti CustomerId di dalam definisi ini.
  3. Baris 7 (let mut history: OrderHistory = HashMap::new();): anotasi tipe menjadi pendek dan jelas. Tanpa alias, baris ini berbunyi let mut history: HashMap<u64, Vec<String>> = HashMap::new();.
  4. Baris 9-10 (history.insert(...)): pemanggilan insert bekerja normal karena OrderHistory adalah HashMap yang sama — alias tidak mengubah perilaku tipe sedikit pun.
  5. Baris 12 (let customer: CustomerId = 101;): nilai 101 bertipe CustomerId, yang bagi kompilator identik dengan u64.
  6. Baris 14 (match history.get(&customer) {): get mengambil &u64 sebagai kunci; alias diterima tanpa konversi apa pun karena CustomerId dan u64 adalah tipe yang sama.

Alias adalah Nama Lain, Bukan Tipe Baru #

Di sinilah letak perbedaan mendasar dengan newtype. Alias tidak menciptakan tipe baru; kompilator hanya mengganti nama dengan tipe aslinya saat memeriksa kode. Dua alias yang berbeda nama tetapi merujuk tipe yang sama tetap dapat saling menggantikan. Mari kita buktikan dengan kasus satuan dari Chapter sebelumnya:

rust
type Meters = u32;
type Millimeters = u32;

fn calculate_total_length(length_a: Meters, length_b: Meters) -> u32 {
    length_a + length_b
}

fn main() {
    let base_length: Meters = 3;
    let precision: Millimeters = 500;

    let total = calculate_total_length(base_length, precision);
    println!("Total: {}", total);
}

Output yang Dihasilkan:

text
Total: 503

Program di atas dikompilasi tanpa error dan tanpa peringatan. Pada Chapter Newtype Pattern, program yang bentuknya sama tetapi memakai tuple struct ditolak keras oleh kompilator dengan E0308. Penjelasannya: setelah deklarasi type Meters = u32; dan type Millimeters = u32;, kedua nama itu adalah u32 yang sama di mata kompilator. Nilai precision bertipe Millimeters diterima parameter bertipe Meters tanpa diperiksa, dan hasil penjumlahan 3 meter + 500 (yang seharusnya milimeter) menghasilkan angka 503 yang salah makna. Alias membuat kode mudah dibaca manusia, tetapi alias tidak memberi perlindungan apa pun di sistem tipe.

Alias untuk Tipe Generik #

Kekuatan alias bertambah ketika aliasnya memuat parameter generik. Pola seperti ini sangat umum untuk menyembunyikan tipe error yang panjang di seluruh modul. Perhatikan contoh fungsi parsing usia berikut:

rust
type ParseResult<T> = std::result::Result<T, String>;

fn parse_age(input: &str) -> ParseResult<u32> {
    input
        .trim()
        .parse::<u32>()
        .map_err(|_| format!("teks '{}' bukan angka usia yang valid", input))
}

fn main() {
    match parse_age("29") {
        Ok(age) => println!("Usia tercatat: {}", age),
        Err(message) => println!("Error: {}", message),
    }
    match parse_age("tiga puluh") {
        Ok(age) => println!("Usia tercatat: {}", age),
        Err(message) => println!("Error: {}", message),
    }
}

Output yang Dihasilkan:

text
Usia tercatat: 29
Error: teks 'tiga puluh' bukan angka usia yang valid

Mari kita bedah kode tersebut:

  1. Baris 1 (type ParseResult<T> = std::result::Result<T, String>;): alias generik — ParseResult<u32> berarti Result<u32, String>, ParseResult<String> berarti Result<String, String>, dan seterusnya mengikuti parameter yang kita berikan.
  2. Baris 3 (fn parse_age(input: &str) -> ParseResult<u32> {): nilai kembalian terbaca sebagai “hasil parse u32” tanpa mengungkap detail tipe error. Signature singkat seperti ini mudah dipertahankan; jika kelak tipe error berubah menjadi enum kustom, kita cukup mengubah satu baris alias.
  3. Baris 6 (.parse::<u32>()): parsing teks menjadi u32 menghasilkan Result<u32, ParseIntError>; map_err di baris 7 mengganti error tersebut menjadi String agar cocok dengan tipe pada alias.
  4. Baris 11-18 (dua blok match): kedua pemanggilan memakai ParseResult<u32> sebagai hasil dan menangani Ok serta Err secara eksplisit.

Pola yang sama dipakai di standard library. Tipe std::io::Result<T> yang sering kita jumpai sebenarnya adalah alias untuk Result<T, std::io::Error>. Kode std memakai alias tersebut agar setiap signature berbisnis I/O tidak perlu menuliskan panjang lebar tipe errornya.

Alias untuk Trait Object yang Panjang #

Trait object yang dibungkus smart pointer menghasilkan tipe terpanjang di Rust. Alias membuatnya hidup kembali:

rust
type Transform = Box<dyn Fn(i32) -> i32>;

fn apply_all(values: &[i32], transform: &Transform) -> Vec<i32> {
    values.iter().map(|v| transform(*v)).collect()
}

fn main() {
    let double: Transform = Box::new(|v| v * 2);
    let scores = [70, 85, 90];

    let doubled = apply_all(&scores, &double);
    println!("Skor asli: {:?}", scores);
    println!("Skor baru: {:?}", doubled);
}

Output yang Dihasilkan:

text
Skor asli: [70, 85, 90]
Skor baru: [140, 170, 180]

Dua baris yang perlu diperhatikan:

  1. Baris 1 (type Transform = Box<dyn Fn(i32) -> i32>;): nama Transform kini mewakili closure pemilik di heap yang menerima dan menghasilkan i32. Bayangkan menuliskan &Box<dyn Fn(i32) -> i32> pada parameter apply_all tanpa alias — signature fungsi baris 3 akan penuh sesak.
  2. Baris 9 (let double: Transform = Box::new(|v| v * 2);): closure dikemas ke dalam Box dan diberi tipe Transform. Nilai tersebut lalu dipinjamkan sebagai &Transform ke fungsi apply_all tanpa pemindahan kepemilikan.

Memilih Antara Alias dan Newtype #

Pengalaman di atas bisa dirangkum menjadi pedoman memilih:

  • Pakai type alias ketika tujuannya murni keringkasan dan keterbacaan — misalnya menyembunyikan HashMap panjang, tipe Result dengan error spesifik, atau trait object berbungkus Box.
  • Pakai newtype ketika kebenaran program bergantung pada pembedaan tipe — misalnya memisahkan satuan (Meters vs Millimeters), id dari beberapa entitas berbeda, atau nilai yang harus melewati validasi.
  • Keduanya boleh dipakai bersama: alias untuk menyederhanakan newtype yang panjang, misalnya type CustomerIds = Vec<UserId>;.

Ringkasan #

Type alias memberi nama ringkas untuk tipe berlength berapapun dengan satu deklarasi type Nama = Tipe;, termasuk alias generik seperti ParseResult<T> dan alias untuk trait object seperti Box<dyn Fn>. Alias tidak menciptakan tipe baru: Meters dan Millimeters yang keduanya beralias ke u32 dapat saling menggantikan tanpa diperiksa kompilator, sehingga perlindungan tipe harus tetap dipercayakan pada newtype. Standard library sendiri memakai pola ini melalui std::io::Result<T>. Setelah urusan penamaan tipe selesai, kita berpindah ke tipe yang bahkan tidak punya nilai sama sekali: never type ! pada Chapter berikutnya.