Refleksi (reflection) adalah kemampuan membaca struktur sebuah tipe sebagai data, dan Zig melakukannya sepenuhnya pada waktu kompilasi melalui @typeInfo. Topik ini membahas bentuk hasil @typeInfo, pembacaan field struct, penulisan fungsi yang mencetak isi struct apa pun, serta penelusuran tag enum. Hasil akhirnya adalah kode generik yang menyesuaikan dirinya dengan tipe yang diterimanya, tanpa informasi tambahan dari pemanggil.
Konsep#
Hasil @typeInfo berupa union bertag#
@typeInfo(T) mengembalikan nilai bertipe std.lang.Type, yaitu union bertag (tagged union) yang menggabungkan deskripsi untuk setiap jenis tipe. Deskripsi struct berada pada field @"struct", deskripsi enum pada field @"enum", deskripsi integer pada field int, dan seterusnya sesuai jenis tipenya. Nama @"struct" memakai sintaks kutip karena struct adalah kata kunci bahasa yang tidak boleh menjadi pengenal biasa.
Union bertag memaksakan disiplin akses. Hanya field yang sedang aktif boleh dibaca, dan pembacaan field lain ditolak saat kompilasi. Karena hasil @typeInfo adalah konstanta waktu kompilasi, hasil tersebut dapat dipakai di dalam blok comptime, menjadi bagian ekspresi konstanta, dan menjadi iterable untuk inline for. Seluruh introspeksi berakhir saat kompilasi, sehingga tidak ada biaya pada waktu berjalan.
Introspeksi semacam ini dipakai luas di dalam pustaka standar, misalnya untuk format pencetakan yang menyesuaikan dirinya dengan tipe nilai dan untuk perbandingan struct pada fungsi pengujian. Karena deskripsi tipe tersedia sebagai konstanta, perilaku tersebut dibangun tanpa mekanisme runtime tambahan. Topik ini membangun introspeksi dari nol pada container buatan sendiri agar setiap bagian deskripsinya terlihat jelas.
Membaca field struct#
Deskripsi struct menyediakan beberapa daftar yang dibaca sebagaimana array biasa. Field field_names berisi nama setiap field, field_types berisi tipe setiap field dengan urutan yang sama, dan decl_names berisi nama deklarasi di dalam struct tersebut. Field is_tuple bernilai true hanya apabila struct itu adalah tuple, yaitu struct yang fieldnya bernama angka.
// tidak-dijalankan
const info = @typeInfo(Siswa);
const s = info.@"struct";
// s.field_names: nama tiap field, misalnya { "nama", "nilai" }
// s.field_types: tipe tiap field dengan urutan yang sama
// s.decl_names: nama fungsi dan konstanta di dalam struct
// s.is_tuple: bernilai true hanya untuk tupleKedua daftar utama itu berpasangan, sehingga field_types[0] selalu milik field_names[0]. Pasangan ini menjadi bahan introspeksi yang lengkap, misalnya memeriksa bahwa semua field bertipe numerik sebelum fungsi tertentu menerima tipe tersebut. Nama dan tipe ini tersedia pada waktu kompilasi, sehingga keputusan berdasarkan isinya ditulis sebagai percabangan yang diselesaikan kompilator.
Pembacaan daftar ini biasanya terjadi di dalam fungsi generik dengan parameter anytype, sehingga tipenya baru diketahui setelah instansiasi. Pola @typeInfo(@TypeOf(nilai)) menjadi pembuka yang lazim, yaitu tipe nilai diambil terlebih dahulu dengan @TypeOf, kemudian deskripsinya dibaca dengan @typeInfo. Pola yang sama dipakai pada fungsi cetak pada bagian berikutnya.
Contoh Lengkap#
Mencetak isi struct apa pun#
Fungsi berikut menerima struct apa pun melalui anytype, lalu mencetak setiap field beserta nilainya. Nama field dibaca dari @typeInfo, dan akses nilai per field memakai @field, yaitu builtin yang menerima nama field dalam bentuk string.
const std = @import("std");
const Siswa = struct {
nama: []const u8,
nilai: f64,
hadir: bool,
};
const Pengaturan = struct {
suhu: i16,
aktif: bool,
};
// cetakSemua menerima struct apa pun: nama field dibaca saat kompilasi
fn cetakSemua(nilai: anytype) void {
const info = @typeInfo(@TypeOf(nilai));
inline for (info.@"struct".field_names) |nama| {
const T = @TypeOf(@field(nilai, nama));
if (T == []const u8) {
std.debug.print("{s} = {s}\n", .{ nama, @field(nilai, nama) });
} else {
std.debug.print("{s} = {any}\n", .{ nama, @field(nilai, nama) });
}
}
}
pub fn main() void {
std.debug.print("-- Siswa --\n", .{});
cetakSemua(Siswa{ .nama = "Citra", .nilai = 88.5, .hadir = true });
std.debug.print("-- Pengaturan --\n", .{});
cetakSemua(Pengaturan{ .suhu = -4, .aktif = false });
}-- Siswa --
nama = Citra
nilai = 88.5
hadir = true
-- Pengaturan --
suhu = -4
aktif = falseSatu fungsi melayani dua struct yang fieldnya berbeda jumlah dan berbeda tipe. inline for membuka perulangan nama field saat kompilasi, sehingga setiap instansiasi cetakSemua memuat blok cetak untuk setiap field secara tersendiri. Percabangan if (T == []const u8) juga diselesaikan saat kompilasi karena tipe setiap field sudah diketahui, dan pada instansiasi Pengaturan cabang untuk string tidak ikut dibuat sama sekali.
# jalankan program dari direktori topik
zig run cetak-struct.zig-- Siswa --
nama = Citra
nilai = 88.5
hadir = true
-- Pengaturan --
suhu = -4
aktif = falseMenelusuri tag enum#
Deskripsi enum memuat tipe tag serta daftar nama dan nilai tag-nya. Field tag_type menyatakan tipe integer yang menampung tag, field_names berisi nama setiap tag, dan field_values berisi nilai integernya. Program berikut menelusuri ketiganya untuk enum dengan tag eksplisit u8.
const std = @import("std");
const Status = enum(u8) { menunggu, berjalan, selesai };
pub fn main() void {
const info = @typeInfo(Status);
const e = info.@"enum";
std.debug.print("tipe tag = {s}\n", .{@typeName(e.tag_type)});
inline for (e.field_names, e.field_values) |nama, nilai| {
std.debug.print("{s} = {d}\n", .{ nama, nilai });
}
}tipe tag = u8
menunggu = 0
berjalan = 1
selesai = 2Pasangan field_names dan field_values memungkinkan kode generik menyusun tabel nama-ke-nilai tanpa menuliskannya dua kali. Pemanggilan inline for dengan dua iterable mengeluarkan pasangan yang sejajar pada setiap iterasi, dan @tagName pada nilai enum biasa cukup untuk kebutuhan pencetakan nama saja. Apabila tipe tag eksplisit tidak dituliskan, kompilator memilih lebar terkecil yang menampung seluruh tag, sehingga enum dengan tiga tag berakhir bertipe u2. Introspeksi ini berhenti saat kompilasi, sehingga tabel nama tag tertanam di dalam biner sebagai konstanta.
# jalankan program dari direktori topik
zig run tag-enum.zigtipe tag = u8
menunggu = 0
berjalan = 1
selesai = 2Kesalahan umum#
Kesalahan pada topik ini umumnya berupa akses yang melanggar disiplin union atau perulangan yang salah bentuk. Tiga kasus berikut paling sering muncul.
- Membaca field union yang tidak aktif. Ekspresi
info.@"struct"pada hasil@typeInfo(u8)ditolak dengan pesanaccess of union field 'struct' while field 'int' is active. Periksa jenis tipenya terlebih dahulu, misalnya denganswitch (info)atas tag union, sebelum mengakses deskripsinya. - Mencari field bernama
fieldspada deskripsi enum. Pada Zig 0.17, deskripsi itu memuatfield_namesdanfield_valuesyang terpisah, dan nama lama sudah tidak ada. Sesuaikan kode lama dengan dua nama field ini. - Mengulang nama field dengan
forbiasa lalu meneruskannya ke@field. Nama field untuk@fieldwajib diketahui saat kompilasi, sehingga pemanggilan itu ditolak dengan pesanfield name must be comptime-known. Gunakaninline foragar setiap nama menjadi konstanta pada iterasinya.
Ringkasan#
@typeInfo(T)mengembalikan konstantastd.lang.Typeberupa union bertag yang deskripsinya diakses melalui field aktif seperti@"struct"dan@"enum".- Deskripsi struct memuat
field_names,field_types,decl_names, danis_tuple, sedangkan deskripsi enum memuattag_type,field_names, danfield_values. - Fungsi dengan parameter
anytypedapat mencetak struct apa pun dengan kombinasiinline foratasfield_namesdan akses nilai melalui@field. - Seluruh refleksi berlangsung saat kompilasi, sehingga hasil introspeksi menjadi konstanta dan tidak menambah biaya pada waktu berjalan.
Referensi#
- std.lang.Type — Dokumentasi pustaka standar Zig
— Union bertag yang menjadi hasil
@typeInfobeserta deskripsi setiap jenis tipe. - Function-Reflection — Zig Language Reference — Introspeksi fungsi sebagai kelanjutan refleksi tipe.