const, var, dan literal

Membedakan const dan var, memahami pengikatan immutable, dan tipe literal yang ditentukan konteks.

Daftar isi

Pengikatan nilai pada Zig dilakukan dengan const atau var, dan tipe sebuah literal ditentukan oleh konteks tempat literal itu dipakai. Topik ini membahas perbedaan kedua kata kunci tersebut, sifat pengikatan immutable pada const, penentuan tipe literal, serta konversi antar tipe yang aman maupun yang paksa. Setiap pernyataan dilengkapi perilaku yang dijalankan dengan Zig 0.17.0, dan program konversi satuan suhu pada bagian akhir merangkum semuanya.

Konsep#

Pengikatan const dan var#

Deklarasi const membuat pengikatan yang tidak dapat ditugaskan ulang, sedangkan var membuat pengikatan yang dapat berubah. Sifat immutable pada const berlaku untuk pengikatannya: setelah sebuah nama terikat pada nilai, nama itu tidak dapat diarahkan ke nilai lain sepanjang lingkup pengikatnya. Zig tidak menyediakan cara untuk melonggarkan sifat tersebut, sehingga keputusan const atau var bersifat tetap sejak baris deklarasinya.

Kaidah yang dianjurkan adalah memakai const secara default dan menggunakan var hanya untuk nilai yang memang berubah. Kaidah ini bukan sekadar selera penulisan, karena kompiler menolak setiap var yang tidak pernah ditugaskan ulang, sebagaimana dijelaskan pada bagian Kesalahan umum. Akibatnya, pembaca kode dapat memercayai bahwa setiap var benar-benar berubah dan setiap const benar-benar tetap. Kedua kata kunci itu berlaku untuk nilai apa pun, mulai dari integer, pecahan, hingga array dan slice.

Penugasan ulang pada const#

Percobaan menugaskan ulang const ditolak pada saat kompilasi. Contoh berikut memuat penugasan yang memicu error.

zig Contoh: penugasan ulang pada const
// tidak-dijalankan
const batas: u8 = 10;
batas = 20; // ditolak oleh kompiler

Pesan errornya adalah cannot assign to constant, disertai baris dan kolom persis tempat penugasan terjadi. Deteksi ini berlangsung sebelum program berjalan, sehingga kekeliruan semacam itu tidak pernah sampai ke pengguna. Ganti deklarasi menjadi var hanya apabila nilai memang perlu berubah, atau hitung nilai baru pada konstanta lain apabila keduanya dibutuhkan bersamaan. Penolakan yang sama menimpa percobaan mengubah elemen array yang terikat const, karena pengikatannya juga bersifat immutable.

Penolakan ini berlaku untuk nilai apa pun yang terikat, termasuk hasil panggilan fungsi. Pola yang umum adalah mengikat setiap tahap perhitungan pada konstanta tersendiri, misalnya const fahrenheit = celsius * 1.8 + 32.0; diikuti konstanta lain untuk konversi baliknya. Pola tersebut membuat aliran data terbaca sebagai deretan pengikatan yang berurutan, dan kompiler tetap mengoreksi setiap penugasan yang tidak sah. Nama konstanta yang menjelaskan tahapannya, misalnya suhu_fahrenheit dan suhu_celsius, menjaga urutan perhitungan itu tetap mudah diikuti.

Tipe literal ditentukan konteks#

Sebelum diberi tipe, literal integer bertipe comptime_int, yaitu tipe waktu kompilasi dengan presisi tanpa batas. Literal tidak membawa tipe konkret, melainkan menerima tipe dari konteks yang menampungnya, mekanisme yang dalam istilah Zig disebut result location. Deklarasi const a: u8 = 25; mengikat literal 25 pada tipe u8, sedangkan const b: i64 = 25; mengikat literal yang sama pada i64. Konteks itu dapat berupa anotasi tipe, parameter fungsi, maupun elemen array yang tipenya sudah diketahui.

Pola yang sama berlaku untuk literal pecahan, yang bertipe comptime_float sebelum diberi tipe. Apabila konteks tidak menyediakan tipe, misalnya pada const skala = 1.8;, literal mempertahankan tipe waktu kompilasinya sampai dipakai oleh operasi yang membutuhkan tipe konkret. Sebaliknya, literal yang melebihi kapasitas tipe sasarannya ditolak saat kompilasi, sehingga const terlalu_besar: u8 = 300; tidak pernah berhasil dikompilasi.

Fungsi bawaan @as menyatakan tipe sasaran secara eksplisit ketika konteksnya tidak menyediakan anotasi yang jelas. Pemanggilan @as(u32, 120) menghasilkan nilai 120 bertipe u32, dan bentuk ini sering dipakai untuk memberi tipe pada hasil ekspresi yang tidak punya anotasi alami. Apabila anotasi tipe pada deklarasi tersedia, bentuk tersebut tetap menjadi pilihan yang paling ringkas.

Konversi aman dan konversi paksa#

Zig melakukan konversi otomatis yang disebut koersi (coercion) hanya apabila setiap nilai tipe sumber pasti muat pada tipe sasaran. Pengikatan nilai u8 pada variabel u32 berlangsung tanpa kode tambahan karena seluruh rentang u8 berada di dalam rentang u32. Arah sebaliknya memerlukan konversi paksa dengan @intCast, dan pemeriksaan kecocokan nilainya berpindah ke runtime.

zig Contoh: konversi paksa dengan @intCast
// tidak-dijalankan
const data: u32 = 400;
const sempit: u8 = @intCast(data); // 400 tidak muat pada u8

Karena nilai data diketahui saat kompilasi, contoh tersebut ditolak saat kompilasi dengan pesan yang menyatakan u8 tidak dapat mewakili nilai 400. Apabila nilai baru diketahui saat runtime, misalnya berasal dari masukan pengguna, pemeriksaan yang sama berubah menjadi panic dengan pesan integer does not fit in destination type pada mode Debug. Gunakan @intCast hanya setelah rentang nilai sumber dipastikan, atau periksa batasnya secara manual terlebih dahulu.

Program berikut menutup kedua arah tersebut dalam satu berkas, yaitu koersi u8 menuju u32 yang berlangsung tanpa kode tambahan dan perkalian dua nilai yang lebarnya berbeda.

zig konversi.zig
const std = @import("std");

pub fn main() void {
    const lebar = @as(u32, 120);
    const tinggi: u32 = 75;
    std.debug.print("Luas: {d}\n", .{lebar * tinggi});

    const kecil: u8 = 200;
    const besar: u32 = kecil;
    std.debug.print("Koersi aman: {d}\n", .{besar});
}
text Keluaran
Luas: 9000
Koersi aman: 200

Nilai kecil bertipe u8 disalin ke besar bertipe u32 tanpa panggilan konversi apa pun, karena seluruh rentang u8 pasti muat pada u32. Perkalian lebar * tinggi menghasilkan 9000 dan tetap berada dalam rentang u32, sehingga tidak ada satu pun pemeriksaan runtime yang terpicu pada program ini.

Koersi juga bekerja pada ekspresi aritmetika yang mencampur lebar berbeda. Penjumlahan antara variabel u8 dan u16 sah ditulis tanpa konversi manual, karena Zig menyesuaikan kedua operan pada tipe yang cukup lebar untuk menampung keduanya. Aturan ini berlaku hanya untuk arah yang pasti aman, sehingga keputusan paling eksplisit tetap pada penulisan tipe di deklarasinya.

Contoh Lengkap#

Program konversi suhu#

Program berikut mengonversi Celsius ke Fahrenheit dan kembali lagi, dengan tipe yang dinyatakan secara eksplisit pada setiap pengikatan penting.

zig suhu.zig
const std = @import("std");

pub fn main() void {
    const celsius: f64 = 36.6;

    const skala = @as(f64, 1.8);
    var fahrenheit = celsius * skala + 32.0;

    std.debug.print("Celsius: {d:.1}\n", .{celsius});
    std.debug.print("Fahrenheit: {d:.1}\n", .{fahrenheit});

    fahrenheit = fahrenheit - 32.0;
    const kembali = fahrenheit / skala;
    std.debug.print("Kembali ke Celsius: {d:.1}\n", .{kembali});
}
text Keluaran
Celsius: 36.6
Fahrenheit: 97.9
Kembali ke Celsius: 36.6

Jalankan program tersebut dengan perintah berikut.

bash
# menjalankan program konversi suhu
zig run suhu.zig
text Keluaran
Celsius: 36.6
Fahrenheit: 97.9
Kembali ke Celsius: 36.6

Nilai 36.6 bertipe f64 sejak deklarasinya karena penanda suhu memang memerlukan pecahan. Pengikatan skala memakai @as untuk menyatakan tipe koefisien konversi secara eksplisit, sedangkan fahrenheit dideklarasikan var karena nilainya dipakai ulang dan diubah pada konversi balik. Literal 1.8 dan 32.0 tidak pernah diberi tipe secara manual karena konteksnya sudah menentukan f64. Hasil konversi balik kembali ke 36.6, yang menandakan rumusnya konsisten dua arah.

Format {d:.1} pada panggilan cetak menampilkan satu angka di belakang koma, sehingga nilai 97.88 tampil sebagai 97.9. Pemilihan f64 mengikuti praktik umum perhitungan pecahan karena presisi gandanya mengurangi akumulasi kesalahan pembulatan pada perhitungan dua arah. Apabila kebutuhannya hanya menampilkan suhu, presisi satu angka di belakang koma sudah lebih dari cukup untuk laporan harian. Nilai balik konversi yang identik dengan nilai awalnya menjadi bukti sederhana bahwa rumus dan tipe yang dipilih bekerja sebagaimana mestinya.

Kesalahan umum#

Kekeliruan pertama adalah mendeklarasikan var yang tidak pernah berubah.

zig Contoh: var yang tidak pernah ditugaskan ulang
// tidak-dijalankan
var jumlah: u32 = 40; // error: local variable is never mutated

Kompiler menolaknya dengan pesan local variable is never mutated dan menyarankan penggantian menjadi const. Perbaikannya sederhana, yaitu mengubah var menjadi const, dan perubahan itu membuat niat kode lebih tepat. Kekeliruan kedua adalah variabel lokal tanpa nilai awal, yang ditolak dengan error expected '=', found ';'. Pesan tersebut muncul langsung pada posisi titik koma, sehingga baris yang perlu diperbaiki terlihat seketika.

zig Contoh: variabel tanpa nilai awal
// tidak-dijalankan
var posisi: u32;                // error: expected '=', found ';'
var buffer: [16]u8 = undefined; // sah, diisi sebelum dipakai

Zig menuntut setiap variabel lokal diinisialisasi pada deklarasinya. Penanda undefined tersedia untuk kasus yang memang sengaja mengosongkan nilai awal, misalnya buffer yang diisi pada langkah berikutnya. Nilai undefined wajib ditimpa sebelum dibaca, karena membacanya menghasilkan perilaku yang tidak terdefinisi. Untuk variabel yang nilainya baru diketahui kemudian, beri nilai awal yang masuk akal seperti nol, dan gunakan undefined hanya apabila penimpaan penuh terjadi sebelum pembacaan pertama.

Ringkasan#

  • const mengikat nilai secara immutable dan penugasan ulangnya ditolak saat kompilasi, sedangkan var mengizinkan perubahan nilai.
  • Tipe literal ditentukan konteksnya melalui mekanisme result location, dan comptime_int menjadi tipe literal integer sebelum diberi tipe konkret.
  • Koersi otomatis hanya terjadi dari tipe yang lebih sempit ke tipe yang lebih lebar, sedangkan arah sebaliknya memerlukan @intCast dengan pemeriksaan kecocokan nilai.
  • Kompiler menolak var yang tidak pernah berubah dan variabel tanpa nilai awal, sehingga kaidah penulisan ditegakkan sejak kompilasi.

Referensi#