Struct menyimpan beberapa field yang saling terkait, dan mengaksesnya satu per satu lewat operator titik — point.x, point.y — cepat terasa bertele-tele ketika setiap field justru ingin kita masukkan ke variabel lokal. Tindakan menguraikan data menjadi komponennya lewat pola disebut destructuring, dan pola struct melakukannya sekali jalan: satu baris pola mengikat seluruh field ke variabel lokal. Chapter ini membahas empat bentuk penguraian struct — shorthand nama field, penggantian nama binding, pengabaian field dengan .., serta pola bersarang untuk struct di dalam struct — lengkap dengan dua galat kompilator yang paling sering muncul ketika jumlah field pada pola tidak sesuai.

Menguraikan Semua Field Sekaligus #

Bentuk paling ringkas memakai shorthand: nama field ditulis langsung di dalam pola, dan setiap nama tersebut sekaligus menjadi nama variabel lokal. Pola Point { x, y } adalah kependekan dari Point { x: x, y: y } — bagian di kiri titik dua adalah nama field pada struct, bagian di kanan adalah pola untuk nilai field tersebut. Ketika keduanya sama, Rust mengizinkan satu nama saja.

rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 3, y: -7 };

    let Point { x, y } = point;

    println!("x = {}, y = {}", x, y);
}

Output yang Dihasilkan:

text
x = 3, y = -7

Mari kita bedah kode di atas:

  1. Baris 1–4 (struct Point { ... }): struct Point memuat dua field bertipe i32; definisi inilah yang menjadi acuan pola.
  2. Baris 7 (let point = Point { x: 3, y: -7 };): variabel point dibuat dengan nilai x sebesar 3 dan y sebesar -7.
  3. Baris 9 (let Point { x, y } = point;): pola struct menguraikan point menjadi dua variabel lokal x dan y; karena i32 bertipe Copy, nilainya tersalin ke masing-masing variabel dan point tetap sah.
  4. Baris 11 (println!("x = {}, y = {}", x, y);): kedua variabel hasil uraian dipakai langsung tanpa menyentuh point lagi.

Penguraian pada let bersifat wajib berhasil — pola struct yang menyebut semua field selalu cocok untuk nilai bertipe struct tersebut, sehingga tidak ada cabang gagal yang perlu ditangani.

Mengganti Nama Binding #

Nama field tidak selalu cocok dengan konteks lokal: struct Rectangle menyimpan width dan height, tetapi kode yang memproses layar mungkin ingin memakai nama pendek w dan h. Pola mendukung hal ini dengan bentuk field: binding — field tetap disebut menurut definisi struct, sedangkan variabel lokal memakai nama pilihan kita.

rust
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let screen = Rectangle { width: 1280, height: 720 };

    let Rectangle { width: w, height: h } = screen;

    println!("Lebar {} x tinggi {}", w, h);
}

Output yang Dihasilkan:

text
Lebar 1280 x tinggi 720

Mari kita amati baris-baris kuncinya:

  1. Baris 7 (let screen = ...): variabel screen menyimpan data layar dengan lebar 1280 dan tinggi 720.
  2. Baris 9 (let Rectangle { width: w, height: h } = screen;): bagian width: w menyatakan “field width diuraikan ke variabel w”; pola yang sama berlaku untuk height: h sehingga variabel lokal bertukar nama tanpa mengubah struct.
  3. Baris 11 (println!("Lebar {} x tinggi {}", w, h);): keluaran memakai nama baru w dan h yang hasilnya sama dengan nilai field aslinya.

Menguji Nilai Field di Dalam Pola #

Posisi field pada pola tidak harus berupa binding — posisi tersebut dapat diisi pola lain seperti literal, sehingga satu pola struct sekaligus menguji isi field dan mengikat field sisanya. Kombinasi ini berguna pada match: arm pertama dapat menampung struct yang field x-nya tepat 0, arm berikutnya mengikat nilai sisanya. Perhatikan urutan arm dari yang paling ketat syaratnya:

rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let points = [
        Point { x: 0, y: 0 },
        Point { x: 0, y: 5 },
        Point { x: 2, y: 0 },
        Point { x: 1, y: 4 },
    ];

    for point in points {
        match point {
            Point { x: 0, y: 0 } => println!("Titik asal."),
            Point { x: 0, y } => println!("Di sumbu Y, tinggi {}.", y),
            Point { x, y: 0 } => println!("Di sumbu X, jarak {}.", x),
            Point { x, y } => println!("Titik bebas ({}, {}).", x, y),
        }
    }
}

Output yang Dihasilkan:

text
Titik asal.
Di sumbu Y, tinggi 5.
Di sumbu X, jarak 2.
Titik bebas (1, 4).

Mari kita bedah pencocokannya untuk setiap elemen:

  1. Baris 14 (for point in points {): setiap elemen array dipindahkan ke variabel point satu per satu, lalu diperiksa oleh match.
  2. Baris 16 (Point { x: 0, y: 0 } => ...): dua field diuji literal — pola hanya cocok bila kedua nilai tepat nol; elemen pertama (0, 0) memenuhinya sehingga tercetak “Titik asal.”.
  3. Baris 17 (Point { x: 0, y } => ...): field x diuji literal 0 sedangkan field y diikat ke variabel y; elemen (0, 5) lolos uji x dan mengikat y = 5.
  4. Baris 18 (Point { x, y: 0 } => ...): kebalikan arm sebelumnya — y diuji 0, x diikat; elemen (2, 0) memenuhinya.
  5. Baris 19 (Point { x, y } => ...): arm murni binding menampung semua kombinasi tersisa seperti (1, 4) sekaligus melengkapi cakupan pola agar match exhaustive.

Urutan arm menentukan hasil: elemen (0, 0) juga cocok dengan pola baris 17, tetapi arm baris 16 berada di atas sehingga didahulukan. Apabila arm penampung dipindah ke urutan pertama, seluruh elemen akan jatuh ke arm tersebut.

Mengabaikan Field dengan .. #

Struct dengan banyak field membuat penulisan pola melelahkan ketika hanya satu dua field yang dibutuhkan. Pola .. menampung seluruh field yang tidak disebut tanpa mengikatnya. Satu catatan penting: field yang diabaikan tidak ikut berpindah — kepemilikan tetap berada pada variabel struct asal, sehingga field tersebut masih sah diakses lewat variabel lama.

rust
struct Book {
    title: String,
    author: String,
    pages: u32,
    rating: u8,
}

fn main() {
    let book = Book {
        title: String::from("Belajar Rust untuk Pemula"),
        author: String::from("Siti Rahma"),
        pages: 320,
        rating: 4,
    };

    let Book { title, rating, .. } = book;

    println!("Judul: {}.", title);
    println!("Rating: {} dari 5.", rating);
    println!("Penulis: {}, {} halaman.", book.author, book.pages);
}

Output yang Dihasilkan:

text
Judul: Belajar Rust untuk Pemula.
Rating: 4 dari 5.
Penulis: Siti Rahma, 320 halaman.

Mari kita bedah baris per baris:

  1. Baris 16 (let Book { title, rating, .. } = book;): pola hanya menyebut title dan rating; pola .. meloloskan author dan pages tanpa mengikat keduanya.
  2. Baris 18–19 (println!("Judul: {}.", title); ...): field title bertipe String yang tidak Copy, sehingga uraian ini memindahkan isinya ke variabel lokal title — book.title tidak dapat dipakai lagi.
  3. Baris 20 (println!("Penulis: {}, {} halaman.", book.author, book.pages);): field yang diabaikan .. tetap dimiliki book, sehingga book.author dan book.pages masih sah diakses setelah penguraian.

Melupakan satu field tanpa .. langsung ditolak kompilator dengan galat E0027, sedangkan menuliskan field yang tidak ada pada struct memicu E0026. Kode berikut memperlihatkan keduanya secara berurutan:

rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 3, y: -7 };

    let Point { x } = point;

    println!("x = {}", x);
}

Pesan error kompilator:

text
error[E0027]: pattern does not mention field `y`
 --> main.rs:9:9
  |
9 |     let Point { x } = point;
  |         ^^^^^^^^^^^ missing field `y`
  |
help: include the missing field in the pattern
  |
9 |     let Point { x, y } = point;
  |                  +++
help: if you don't care about this missing field, you can explicitly ignore it
  |
9 |     let Point { x, y: _ } = point;
  |                  ++++++
help: or always ignore missing fields here
  |
9 |     let Point { x, .. } = point;
  |                  ++++

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0027`.

Pesan E0027 menawarkan tiga perbaikan sekaligus: melengkapi field y, mengabaikannya secara eksplisit dengan y: _, atau memakai ... Varian kesalahan yang berlawanan terjadi ketika pola menyebut field yang tidak pernah ada:

rust
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point = Point { x: 3, y: -7 };

    let Point { x, y, z } = point;

    println!("x = {}, y = {}, z = {}", x, y, z);
}

Pesan error kompilator:

text
error[E0026]: struct `Point` does not have a field named `z`
 --> main.rs:9:23
  |
9 |     let Point { x, y, z } = point;
  |                       ^ struct `Point` does not have this field

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0026`.

Galat E0026 biasanya muncul akibat salah ketik atau salah ingat definisi struct; kompilator menunjuk kolom tepat di nama field yang bermasalah.

Pola Struct Bersarang #

Field struct dapat berisi struct lain, dan pola dapat menggali lapisan tersebut sekaligus. Contoh berikut memeriksa garis yang tersusun dari dua titik: arm pertama khusus untuk garis yang berawal di titik asal, arm kedua menangani sisanya dengan penggantian nama agar keempat koordinat tidak bertabrakan nama.

rust
struct Point {
    x: i32,
    y: i32,
}

struct Line {
    start: Point,
    end: Point,
}

fn main() {
    let line = Line {
        start: Point { x: 0, y: 0 },
        end: Point { x: 6, y: 8 },
    };

    match line {
        Line { start: Point { x: 0, y: 0 }, end: Point { x, y } } => {
            println!("Garis dari titik asal ke ({}, {}).", x, y)
        }
        Line { start: Point { x, y }, end: Point { x: x2, y: y2 } } => {
            println!("Garis dari ({}, {}) ke ({}, {}).", x, y, x2, y2)
        }
    }
}

Output yang Dihasilkan:

text
Garis dari titik asal ke (6, 8).

Mari kita uraikan kedua armnya:

  1. Baris 17 (match line {): nilai line dicocokkan; karena Line tidak Copy, nilai ini berpindah ke match.
  2. Baris 18 (Line { start: Point { x: 0, y: 0 }, end: Point { x, y } } => ...): field start diisi pola struct Point yang menguji dua literal nol, sedangkan field end diisi pola binding Point { x, y } — dua lapis pola dievaluasi dalam satu arm.
  3. Baris 21 (Line { start: Point { x, y }, end: Point { x: x2, y: y2 } } => ...): arm kedua mengikat keempat koordinat; nama x2 dan y2 dipilih lewat bentuk x: x2 agar berbeda dari x dan y titik awal.
  4. Baris 18 dan 21 (kedua arm): cakupan gabungan keduanya mencakup seluruh kemungkinan Line, sehingga tidak dibutuhkan wildcard.

Kedalaman bersarang tidak dibatasi — struct dalam struct dalam struct dapat diuraikan terus sepanjang pola mengikuti bentuk tipe data.

Ringkasan #

Pola struct menguraikan seluruh field sekaligus: shorthand Point { x, y } mengikat field ke variabel bernama sama, bentuk width: w mengganti nama binding, posisi field dapat diisi literal untuk menguji isi, dan .. meloloskan field yang tidak dibutuhkan tanpa memindahkannya. Melupakan field memicu E0027, sedangkan menyebut field yang tidak ada memicu E0026 — keduanya lengkap dengan saran perbaikan dari kompilator. Pola juga dapat menggali struct bersarang lapis demi lapis. Chapter berikutnya menerapkan teknik yang sama pada enum, tempat setiap varian membawa bentuk data yang berbeda-beda.