distinct dan using

Pembungkus tipe distinct dan using untuk komposisi.

Daftar isi

Kata kunci distinct membentuk tipe baru dari tipe dasar sehingga nilainya tidak dapat dicampur dengan tipe asalnya, sedangkan using menyisipkan field dari satu tipe ke dalam lingkup akses tipe lain. Topik ini membahas distinct sebagai pembungkus satuan pengukuran beserta procnya, using untuk komposisi struct, penyebaran enum ke lingkup proc, dan kesalahan umum yang menyertai keduanya. Setelah membaca, satuan seperti meter dan kilogram dapat dipisahkan dari bilangan polos, dan komposisi tipe dapat ditulis tanpa meniru warisan.

distinct: tipe baru dari tipe dasar#

Deklarasi Meter :: distinct f64 membuat tipe Meter yang tata letaknya sama dengan f64, tetapi berdiri sebagai tipe berbeda. Nilai Meter tidak dapat dijumlahkan langsung dengan f64, dan Meter tidak dapat ditukar dengan Kilogram walau keduanya berbasis f64. Konversi antartipe memerlukan cast eksplisit seperti Meter(3.5) untuk membungkus nilai atau f64(x) untuk membuka nilai kembali. Aturan ini memindahkan pemeriksaan satuan dari ingatan pengembang ke compiler, sehingga penjumlahan jarak dengan berat langsung terdeteksi sebagai error.

odin main.odin
package main

import "core:fmt"

Meter :: distinct f64
Kilogram :: distinct f64

total_jarak :: proc(jarak: []Meter) -> Meter {
        total := Meter(0)
        for m in jarak {
                total += m
        }
        return total
}

main :: proc() {
        segmen := []Meter{Meter(3.5), Meter(1.25), Meter(2.0)}
        fmt.println("total jarak:", total_jarak(segmen))

        berat := Kilogram(2.5)
        fmt.println("berat:", berat)

        // operasi antarnilai bertipe sama sah dilakukan
        ganda := berat * Kilogram(2)
        fmt.println("berat ganda:", ganda)

        // nilai dasar diperoleh lewat cast eksplisit
        fmt.println("segmen pertama:", f64(segmen[0]), "meter")
        fmt.println("size_of(Meter):", size_of(Meter))
}
bash
odin run main.odin -file
text Keluaran
total jarak: 6.75
berat: 2.5
berat ganda: 5
segmen pertama: 3.5 meter
size_of(Meter): 8

Proc total_jarak hanya menerima []Meter, sehingga pemanggil dengan slice []f64 ditolak compiler. Di dalam proc, penjumlahan total += m sah karena kedua operannya bertipe Meter, dan hasilnya kembali sebagai Meter. Ukuran size_of(Meter) yang tetap 8 byte menunjukkan bahwa distinct tidak menambah pembungkus pada memori, hanya menambah aturan tipe pada waktu kompilasi.

using untuk komposisi struct#

Deklarasi using orang: Orang di dalam struct menyisipkan field milik Orang seolah-olah menjadi field milik struct tersebut. Akses k.nama dan k.umur sah tanpa menulis k.orang.nama, dan akses yang sama berlaku melalui pointer. Tata letak memori tidak berubah karena field tertanam tetap disimpan di dalam struct pembungkusnya, yang terlihat pada keluaran struct lengkap berikut.

odin komposisi.odin
package main

import "core:fmt"

Orang :: struct {
        nama: string,
        umur: u8,
}

Karyawan :: struct {
        using orang: Orang,
        id:     u32,
        divisi: string,
}

main :: proc() {
        k := Karyawan{orang = Orang{nama = "Dewi", umur = 30}, id = 5001, divisi = "Analitik"}
        fmt.println(k)
        fmt.println("akses langsung:", k.nama, k.umur)
        fmt.println("field sendiri :", k.id, k.divisi)

        // lewat pointer pun aksesnya sama
        p := &k
        fmt.println("lewat pointer :", p.nama, p.id)
}
bash
odin run komposisi.odin -file
text Keluaran
Karyawan{orang = Orang{nama = "Dewi", umur = 30}, id = 5001, divisi = "Analitik"}
akses langsung: Dewi 30
field sendiri : 5001 Analitik
lewat pointer : Dewi 5001

Pola ini meringkas akses pada struct yang berlapis, misalnya entitas grafis yang menyimpan posisi Vector3. Tanpa using, setiap pembacaan posisi memerlukan dua tingkat nama field, sedangkan dengan using pembacaannya menjadi satu tingkat. Field tertanamnya sendiri tidak hilang, karena k.orang.nama tetap sah dan bermanfaat ketika struct Orang perlu dikirim utuh ke proc lain. Field using juga boleh lebih dari satu, sehingga struct yang menggabungkan data identitas dan alamat tetap dapat diakses dengan satu tingkat nama. Komposisi dengan using selalu diizinkan pada field struct, berbeda dengan using sebagai pernyataan pada proc yang bersifat opsional dan dibahas di bawah.

using pada enum#

Kata kunci using juga dapat menyebarkan anggota enum ke lingkup proc, sehingga anggota dapat ditulis tanpa awalan nama tipe. Kemampuan ini dinonaktifkan secara bawaan dan perlu diaktifkan per berkas dengan direktif #+feature using-stmt pada baris pertama berkas. Setelah diaktifkan, using Warna membuat Merah, Hijau, dan Biru dapat digunakan langsung di dalam proc tersebut.

odin sebar-enum.odin
#+feature using-stmt
package main

import "core:fmt"

Warna :: enum {
        Merah,
        Hijau,
        Biru,
}

main :: proc() {
        // menyebarkan anggota enum ke lingkup prosedur
        using Warna
        fmt.println(Merah, Hijau, Biru)
        a := Hijau
        fmt.println("a =", a)
}
bash
odin run sebar-enum.odin -file
text Keluaran
Merah Hijau Biru
a = Hijau

Dokumentasi resmi menyarankan pemilih implisit seperti .Hijau daripada menyebarkan enum dengan using, karena penyebaran menambah nama ke lingkup proc, dan nama tersebut dapat berbenturan dengan nama lain. Pemilih implisit tidak memerlukan fitur apa pun dan tetap pendek untuk ditulis, sehingga penyebaran enum sebaiknya digunakan hanya ketika anggota enum muncul sangat sering dalam satu proc.

Kesalahan umum#

Kesalahan yang paling lazim adalah menganggap using sebagai warisan. using hanya menyisipkan nama field ke lingkup akses, tanpa polimorfisme dan tanpa penimpaan proc, sedangkan field tertanam tetap tersimpan sebagai field tersendiri dan tetap terlihat pada keluaran struct. Tidak ada relasi subtipe yang terbentuk antara Karyawan dan Orang, sehingga nilai Karyawan tidak dapat digunakan di tempat yang menuntut Orang menurut aturan tipe. Ketika perilaku berbeda per tipe diperlukan, mekanisme Odin adalah proc dan polimorfisme parametrik, bukan warisan.

Kesalahan berikutnya pada sisi distinct adalah lupa melakukan cast ketika bertukar data dengan tipe dasarnya. Ekspresi jarak := Meter(3.5) + 2.0 ditolak compiler karena operan kedua bertipe f64, dan perbaikannya adalah menjelaskan maksud penulisannya, misalnya Meter(3.5) + Meter(2.0) untuk menambah jarak atau f64(Meter(3.5)) + 2.0 bila bilangan polos memang diinginkan. Terakhir, using sebagai pernyataan yang menyebarkan field pointer parameter ke lingkup proc juga dinonaktifkan secara bawaan dengan fitur using-stmt, sedangkan using pada contoh komposisi struct di atas tidak memerlukan direktif apa pun.

Perbedaan kedua kata kunci tersebut dapat dirangkum dalam tabel berikut.

Aspekdistinctusing
Tujuantipe baru yang berdiri sendiriakses field tertanam yang lebih pendek
Tata letak memorisama dengan tipe dasarnyafield tertanam tetap tersimpan di dalam struct
Aturan tipeantartipe distinct tidak boleh dicampur tanpa casttidak ada relasi subtipe antar struct
Polimorfismetidak adatidak ada

Ringkasan#

  • distinct membentuk tipe baru dari tipe dasar dengan tata letak yang sama, tetapi nilai antartipe tidak dapat dicampur tanpa cast eksplisit.
  • Tipe distinct cocok sebagai pembungkus satuan, dan procnya dapat menolak tipe satuan yang salah pada waktu kompilasi.
  • using orang: Orang menyisipkan field struct tertanam ke lingkup akses struct pembungkus tanpa mengubah tata letak memori.
  • using bukan warisan, karena tidak ada polimorfisme, penimpaan proc, maupun relasi subtipe yang terbentuk.
  • Penyebaran enum dengan using memerlukan #+feature using-stmt, sedangkan pemilih implisit seperti .Hijau adalah saran dokumentasi resmi.

Referensi#