Lewati ke konten

Tuple di Rust

Tuple mengelompokkan beberapa nilai, yang tipenya boleh berbeda-beda, menjadi satu. Ini cara cepat untuk membungkus hal-hal yang saling berkaitan tanpa harus membuat tipe baru untuknya.

fn main() {
let person: (&str, i32, bool) = ("Alice", 30, true);
println!("{:?}", person);
}

Output:

("Alice", 30, true)
▶ Coba sendiri

Perhatikan bahwa tuple ini mencampur &str, i32, dan bool dalam satu nilai. Array tidak bisa melakukan ini, karena setiap elemen di dalam array harus bertipe sama.

Untuk mencetak seluruh tuple, pakai {:?} (format debug), bukan {}. {} biasa tidak bisa dipakai untuk tuple.

Pakai .0, .1, .2, dan seterusnya. Indeks tuple dimulai dari nol:

fn main() {
let person = ("Alice", 30, true);
println!("Nama: {}", person.0);
println!("Umur: {}", person.1);
println!("Aktif: {}", person.2);
}

Output:

Nama: Alice
Umur: 30
Aktif: true
▶ Coba sendiri

Posisinya harus berupa angka biasa yang ditulis langsung di kode. Kamu tidak bisa memakai variabel, seperti person.i, karena setiap posisi bisa menyimpan tipe yang berbeda, dan Rust harus tahu tipenya saat kompilasi.

Kamu bisa mengeluarkan setiap nilai ke variabelnya masing-masing dalam satu baris. Ini disebut destructuring (membongkar):

fn main() {
let person = ("Alice", 30, true);
let (name, age, is_active) = person;
println!("{name} berumur {age}, aktif: {is_active}");
}

Output:

Alice berumur 30, aktif: true
▶ Coba sendiri

Cara ini sering lebih jelas daripada menulis .0, .1, .2 berulang kali, terutama kalau tuple-nya punya lebih dari dua atau tiga elemen.

Tuple adalah cara alami untuk mengembalikan lebih dari satu nilai, karena sebuah fungsi hanya bisa menuliskan satu tipe kembalian:

fn min_max(numbers: &[i32]) -> (i32, i32) {
let min = *numbers.iter().min().unwrap();
let max = *numbers.iter().max().unwrap();
(min, max)
}
fn main() {
let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]);
println!("min: {smallest}, maks: {largest}");
}

Output:

min: 1, maks: 9
▶ Coba sendiri

Jangan khawatir soal &[i32], .iter(), atau .unwrap() dulu. Semuanya dibahas di halaman Slice, Iterator, dan Option. Yang perlu kamu perhatikan di sini adalah tipe kembalian (i32, i32) dan bagaimana main langsung membongkarnya menjadi dua variabel.

Kalau sebuah tuple dideklarasikan dengan mut, kamu bisa mengubah elemennya satu per satu, memakai posisi .0, .1 yang sama. Nilainya boleh berubah, tapi bentuknya tidak: sebuah tuple selalu mempertahankan jumlah dan tipe elemen yang dimilikinya sejak awal.

Destructuring juga bisa dipakai ke arah sebaliknya: kamu bisa memasukkan nilai ke dalam variabel yang sudah ada. Hasilnya adalah cara menukar nilai yang rapi dalam satu baris, tanpa variabel sementara. Kalau kamu hanya butuh sebagian elemen, tulis _ untuk elemen yang tidak kamu perlukan:

fn main() {
let mut point = (3, 4);
point.0 += 10; // ubah satu elemen secara langsung
println!("point sekarang {:?}", point);
let mut a = 1;
let mut b = 2;
(a, b) = (b, a); // tukar kedua nilai
println!("a = {a}, b = {b}");
let (x, _) = point; // simpan x, abaikan elemen kedua
println!("x = {x}");
}

Output:

point sekarang (13, 4)
a = 2, b = 1
x = 13
▶ Coba sendiri

Penukaran ini berhasil karena Rust membuat sisi kanan, (b, a), lebih dulu. Baru setelah itu Rust membongkarnya ke a dan b. Masih banyak lagi yang bisa kamu lakukan dengan _ dan pola di halaman Pattern matching.

Sebenarnya kamu sudah pernah bertemu sebuah tuple tanpa menyadarinya. () disebut unit type, dan itulah yang dikembalikan sebuah fungsi kalau fungsi itu tidak mengembalikan apa pun yang bermakna. Ini adalah tipe kembalian tersirat dari fn main().

Kapan memakai tuple dan kapan memakai struct

Section titled “Kapan memakai tuple dan kapan memakai struct”

Tuple sangat cocok untuk pengelompokan cepat tanpa nama, terutama untuk mengembalikan dua atau tiga nilai dari fungsi. Begitu sekelompok nilai mewakili konsep nyata dengan elemen bernama (seperti Person dengan nama dan umur yang akan kamu sebut berdasarkan namanya di seluruh kode), struct biasanya pilihan yang lebih baik dan lebih mudah dibaca.

Tulis fungsi sum_and_product(a: i32, b: i32) -> (i32, i32) yang mengembalikan jumlah sekaligus hasil kali dua angka. Di main, panggil dengan 5 dan 6, lalu bongkar hasilnya. Petunjuk: baris terakhir fungsinya adalah tuple itu sendiri, (a + b, a * b), tanpa titik koma.

Lihat solusi
fn sum_and_product(a: i32, b: i32) -> (i32, i32) {
(a + b, a * b)
}
fn main() {
let (sum, product) = sum_and_product(5, 6);
println!("jumlah = {sum}, hasil kali = {product}");
}

Output:

jumlah = 11, hasil kali = 30
▶ Coba sendiri

Sebuah persegi panjang bisa disimpan sebagai tuple berisi dua titik, dan setiap titik juga berupa tuple: let rect = ((1, 2), (5, 5));. Hitung lebar, tinggi, dan luasnya. Petunjuk: tuple bisa bersarang, dan rect.1.0 artinya “elemen pertama dari titik kedua”.

Lihat solusi
fn main() {
let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0;
let height = rect.1.1 - rect.0.1;
println!("lebar {width}, tinggi {height}, luas {}", width * height);
}

Output:

lebar 4, tinggi 3, luas 12
▶ Coba sendiri

Kamu juga bisa membongkar kedua titik sekaligus: let ((x1, y1), (x2, y2)) = rect;. Memang lebih panjang untuk diketik, tapi x2 - x1 lebih mudah dibaca daripada rect.1.0 - rect.0.0. Kalau titik-titiknya mulai menumpuk seperti ini, itu tanda bahwa struct dengan elemen bernama akan lebih jelas.

  • Tuple mengelompokkan nilai yang tipenya boleh berbeda: (name, age, is_active).
  • Akses elemen dengan .0, .1, .2, atau keluarkan semuanya sekaligus dengan destructuring.
  • Tuple adalah cara alami untuk mengembalikan beberapa nilai dari fungsi.
  • Elemen tuple mut boleh berubah, tapi ukuran dan tipenya tidak. (a, b) = (b, a); menukar dua nilai.
  • Untuk pengelompokan bernama yang dipakai berulang kali, pakai struct.

Kuis singkat

1. Apa yang bisa disimpan tuple tapi tidak bisa disimpan array?

2. Bagaimana cara mengakses elemen kedua dari tuple bernama person?

3. Setelah let mut a = 1; let mut b = 2; (a, b) = (b, a);, berapa nilai a dan b?

Skor: 0 / 3