- Kompiler Zig yang menyediakan kompilasi kode C dan cross-compilation secara bawaan adalah bahasa paling menakjubkan yang pernah ditemui penulis dalam 45 tahun pengalamannya
- Dengan fitur unik seperti eksekusi saat waktu kompilasi, variabel dengan ukuran bit arbitrer, dan lingkungan blok test, Zig melampaui sekadar pengganti C/C++ dan menawarkan cara pemrograman yang benar-benar baru
- Dengan sintaks yang ringkas dan jelas seperti deklarasi variabel melalui inferensi tipe, struct anonim, dan label break, Zig dapat dipelajari dengan cepat
- Dengan pengujian modul secara independen melalui blok test dan fungsi bawaan @breakpoint Zig mendukung debugging kode teroptimasi
- Dengan dukungan pemrograman tingkat rendah melalui bit field dan operasi bit, Zig mencapai efisiensi dan ketangguhan sekaligus, sambil mengintegrasikan keunggulan bahasa interpreter ke dalam bahasa terkompilasi
Pendahuluan
- Dalam 45 tahun pengalaman, tidak ada bahasa yang semenarik Zig
- Zig bukan sekadar bahasa baru, melainkan alat yang secara mendasar mengubah cara pemrograman
- Melihatnya hanya sebagai pengganti C atau C++ adalah peremehan besar
- Tujuan tulisan ini adalah memperkenalkan fitur-fitur Zig yang sederhana namun menarik, dan membantu programmer agar bisa cepat mulai menggunakannya
- Masih ada lebih banyak fitur yang memengaruhi tingkat penerimaan Zig di industri
Kompiler Zig
- Menyediakan kompilasi kode C dan cross-compilation secara default tanpa konfigurasi tambahan, yang memberi dampak besar di industri
- Instalasi dilakukan dengan mengunduh kompiler sesuai prosesor/OS dari halaman unduhan Zinglang, lalu mengekstraknya dan menyalinnya ke direktori yang diinginkan
- Di Windows 10, file zip x86_64 dapat disalin ke "Program Files", lalu nama direktori root diubah menjadi "zig-windows-x86_64" agar tidak perlu mengubah variabel lingkungan Path saat versi diperbarui
- Setelah menambahkan jalur direktori root ke variabel lingkungan Path, kompiler dapat digunakan dalam mode CLI
- Untuk membangun program "Hello World!", disarankan merujuk ke bagian "Getting Started" di situs resmi
Konsep dan perintah utama
Deklarasi variabel
- Deklarasi variabel terdiri dari bagian pertama berupa aksesibilitas (
pub atau dihilangkan), var/const, dan nama variabel, bagian kedua berupa deklarasi tipe, serta bagian ketiga berupa inisialisasi
- Hanya bagian pertama dan ketiga yang wajib, dan tipe dapat diinferensikan dari nilai inisialisasi
- Contoh:
var sum : usize = 0;
- Variabel yang dideklarasikan tanpa
pub hanya dapat diakses di dalam modul (mirip variabel static di C)
- Deklarasi variabel
pub tidak dianjurkan, dan fungsi pub sebaiknya diminimalkan untuk menurunkan coupling dan meningkatkan cohesion
Struct, struct anonim, dan blok test
- Literal struct anonim yang dibungkus oleh
.{ dan } digunakan untuk menginisialisasi elemen struct lain atau membuat struct baru dengan elemen yang sudah diinisialisasi
.{ } adalah literal struct anonim kosong
- Bentuk
struct { } adalah deklarasi struct
- Blok test memungkinkan kompilasi dan eksekusi pengujian tanpa file executable
Bit field
- Bit field dideklarasikan sebagai field bertipe ukuran tertentu di dalam packed struct
- Pointer dapat menunjuk ke bit field tertentu
Loop for
- Sintaks Zig lebih jelas daripada C, tetapi menggunakan interval terbuka
[0..9) alih-alih [0..8]
- Deklarasi tipe, inisialisasi, pengujian, dan kenaikan variabel loop
i ditangani secara otomatis
Array
[_] mendefinisikan array dengan ukuran yang tidak diketahui, diikuti oleh tipe elemen dan inisialisasi
- Contoh:
var grid = [_]u8{0} ** 81; menginisialisasi 81 elemen u8 dengan 0
- Ukuran array diinferensikan dari argumen pengulangan inisialisasi
- Dalam lingkungan test, elemen array dapat dijumlahkan sambil diiterasi
- Variabel yang dideklarasikan di antara
| pada loop for otomatis diasumsikan bertipe sama dengan elemen array
usize adalah bilangan bulat tak bertanda alami platform (u64 di 64-bit, u32 di 32-bit)
Pointer multi-item
- Agar pointer array dapat menggunakan operasi aritmetika pointer, pointer itu harus secara eksplisit dideklarasikan sebagai pointer multi-item seperti
[*]const i32
- Meski array bersifat const, pointer tetap dapat dideklarasikan sebagai var
Dereferensi pointer
- Pointer yang diberi alamat posisi array individual tidak dapat diperbarui dengan aritmetika pointer
- Dereferensi pointer menggunakan
ptr.*
Label break
- Berbagai tugas seperti inisialisasi array dapat dilakukan pada waktu kompilasi
- Label break menambahkan
: setelah nama blok, lalu mengembalikan nilai dari blok dengan break
0.. adalah rentang tak terbatas yang dimulai dari 0
- Dalam loop for, variabel diinisialisasi dan dinaikkan secara otomatis, dan loop berhenti setelah posisi terakhir array diproses
- Array tidak harus diinisialisasi secara eksplisit dengan
undefined
Fungsi di Zig
- Fungsi dideklarasikan dengan
fn dan secara default bersifat static (hanya dipakai di dalam file)
- Jika dideklarasikan dengan
pub fn, fungsi dapat di-import dari file lain
- Fungsi dapat di-"inlined"
- Pointer fungsi menempatkan
const di depan dan prototipe fungsi di belakang
Pemrograman berorientasi objek di Zig
- Struct dapat memiliki fungsi
- Dalam contoh stack, maksimal 81 elemen bertipe
StkNode dapat disimpan
- Operator
++ dan -- tidak ada di Zig, dan sebagai gantinya digunakan += serta -=
- Pointer stack adalah bilangan bulat yang digunakan sebagai indeks array
stk
- Pointer
self tidak diteruskan secara eksplisit sebagai parameter, melainkan diasumsikan secara tidak langsung sebagai pointer ke instance stack tempat fungsi dipanggil
- Saat dipanggil seperti
stack.pop(), self adalah pointer ke stack (mirip this di Java/C++)
- Fungsi
init() adalah konstruktor stack
- Fungsi
pop dan push di-"inlined"
Membangun dan menjalankan program Zig
Membangun executable
- Untuk membuat executable, dibutuhkan fungsi
main yang menandai entry point program
- Program sederhana dapat menempatkan fungsi main di file yang sama
- Untuk debugging modul secara independen, fungsi main dapat ditambahkan di akhir file lalu dikomentari kembali setelah debugging selesai
- Perintah kompilasi:
zig build-exe -O ReleaseFast program.zig
Menjalankan blok test modul
- Ini adalah salah satu fitur terbaik Zig, digunakan untuk testing dan prototyping
- Blok test dimulai dengan
test "message" { dan diakhiri dengan }
- "message" adalah string yang ditampilkan saat test dijalankan
- Blok test dijalankan secara independen dari executable, dan executable final tidak menjalankan test
- Perintah test:
zig test module.zig
- Blok test di
example.zig menguji fungsi set dan print; set menerima string desimal sebagai parameter, dan print mencetak header "Input Grid" lalu menampilkan grid
Output di Zig
- Pernyataan
std.debug.print memanggil fungsi print di debug.zig dari pustaka standar Zig std
- Parameter pertama adalah string format, dan parameter kedua adalah struct anonim yang berisi daftar variabel untuk ditampilkan
- Jika tidak ada format, struct tersebut kosong
- Secara default ditampilkan ke stderr
- Tidak seperti
printf di C, Zig dapat memproses string literal dan daftar variabel pada waktu kompilasi
Debugging executable
- Penggunaan debugger tidak mudah kecuali dengan IDE yang memiliki debugger terintegrasi (Eclipse, IntelliJ IDEA) atau kit pengembangan terintegrasi (
w64devkit)
- Integrasi simbol membuat kode membengkak dan mengharuskan kompilasi mode Debug, sehingga menghasilkan kode eksekusi yang jauh kurang efisien
- Zig menyediakan solusi praktis untuk menghindari masalah ini
Fungsi bawaan @breakpoint
- Dengan menyisipkan
@breakpoint(); ke dalam source code, program akan berhenti di titik tersebut saat dijalankan di debugger
- Ini adalah fitur berguna untuk melakukan debugging kode Zig teroptimasi tanpa simbol
- Jika
std.debug.print digunakan tepat sebelum @breakpoint(); untuk mencetak variabel yang ingin dilacak, nilai variabel saat itu dapat diperiksa
- Dalam contoh
debug_example.zig, kode untuk mencetak grid dan variabel di dalam fungsi set, serta @breakpoint();, disisipkan
- Perintah build:
zig build-exe debug_example.zig
- Setelah memanggil
debug_example.exe dengan debugger seperti gdb, jalankan program dengan perintah r
- Gunakan perintah
c untuk melanjutkan sambil melacak isi grid dan variabel
- Jika terus menekan Enter untuk melanjutkan, dapat dipastikan bahwa nilai-nilai grid sesuai dengan blok test di
example.zig
Pemrograman tingkat rendah di Zig
Representasi matriks
- Digit desimal disimpan di matriks sebagai bilangan bulat standar
u8
- Grid input berbentuk string, tetapi karakter ASCII secara internal dikonversi menjadi bilangan bulat
u8
- Penyimpanan angka disusun secara linear per baris ke dalam array
grid yang memiliki 81 posisi: var grid = [_]u8{0} ** 81;
- Untuk memverifikasi kebenaran grid, elemen perlu diakses per baris dan kolom
- Array berisi 9 pointer dibuat, dan masing-masing pointer menunjuk ke awal tiap baris
- Label break digunakan untuk mengembalikan nilai dari blok kode:
break :fill9x9 m; menginisialisasi matrix dengan m
- Notasi akses elemen:
element = matrix[i][j]
Merepresentasikan digit desimal sebagai bit
- Konsep inti adalah mengganti digit desimal bulat
i dengan bilangan bulat code
i ∈ [1,9] → code = 2ⁱ⁻¹
i = 0 → code = 0
- Posisi satu-satunya bit
1 yang disetel dalam code adalah i-1 (saat i berada di antara 1 dan 9); jika tidak, semua bit bernilai 0
- Disediakan tabel nilai
code untuk tiap digit (1→1, 2→2, 3→4, ..., 9→256)
Menghitung code di Zig
- Saat
c tidak sama dengan 0, nilai code dihitung dengan operator shift kiri: code = @as(u9,1) << (c-1);
- Di Zig, konstanta harus memiliki ukuran yang sesuai agar operasi dapat dikompilasi dan hasilnya dapat ditugaskan ke variabel
code dideklarasikan sebagai tipe u9 (karena nilai maksimum 256 memerlukan minimal 9 bit)
- Zig dapat memiliki variabel dengan ukuran bit arbitrer
- Fungsi bawaan
@as digunakan untuk melakukan cast konstanta 1 ke tipe u9
Representasi grid dengan bit field
Grid bit field per baris
- Array
lines mencerminkan seluruh grid dengan merepresentasikan tiap baris sebagai bilangan bulat 9-bit: var lines = [_]u9{0} ** 9;
- Saat array diakses dengan baris
i, keberadaan angka tertentu di baris itu dapat diperiksa dengan operasi bit AND (&): lines[i] & code
- Jika hasil operasi adalah 0, angka tersebut belum ada di baris i; jika tidak, berarti ada duplikasi
Grid bit field per kolom
- Array
columns mencerminkan seluruh grid dengan merepresentasikan tiap kolom sebagai bilangan bulat 9-bit: var columns = [_]u9{0} ** 9;
- Saat array diakses dengan kolom
j, keberadaan angka tertentu di kolom itu dapat diperiksa dengan operasi bit AND: columns[j] & code
- Jika hasil operasi adalah 0, angka tersebut belum ada di kolom j; jika tidak, berarti ada duplikasi
Aturan Sudoku
- Saat memasukkan angka baru ke grid Sudoku kosong, angka itu tidak boleh sudah ada di seluruh baris, kolom, dan sel yang memuat elemen baru tersebut
- Sel adalah masing-masing dari 9 grid 3x3 yang dipisahkan garis tebal
- Setiap elemen tertentu dalam grid 9x9 memiliki baris, kolom, dan sel unik yang memuatnya
- Pada grid contoh, sel pertama berisi 3, 5, 6, 8, 9, sedangkan 1, 2, 4, dan 7 tidak ada
- Array
lines dan columns menangani pemeriksaan duplikasi untuk baris dan kolom
- Diperlukan array baru untuk memeriksa duplikasi pada sel
Grid bit field per sel
- Array
cells mencerminkan seluruh grid dengan merepresentasikan tiap sel sebagai bilangan bulat 9-bit: var cells = [_]u9{0} ** 9;
- Akses ke
cells akan lebih mudah jika diperlakukan sebagai matriks 3x3
- Array
cell diisi dengan cara yang mirip seperti pada matriks 9x9
- Dari baris dan kolom elemen pada grid 9x9 asli, perlu ditentukan baris dan kolom pada matriks
cell
- Karena pembagian bilangan bulat sangat lambat, array
cindx = [_]usize{ 0,0,0, 1,1,1, 2,2,2 }; digunakan untuk memberikan hasil pembagian
- Saat matriks diakses dengan baris
i dan kolom j dari elemen grid 9x9, keberadaan angka tertentu di sel elemen itu dapat diperiksa dengan operasi bit AND: cell[cindx[i]][cindx[j]] & code
- Jika hasil operasi adalah 0, angka tersebut belum ada di sel; jika tidak, berarti ada duplikasi
Menguji duplikasi elemen
- Dengan menggabungkan semua elemen sebelumnya pada baris, kolom, dan sel yang sama menggunakan bit OR (
|), lalu melakukan bit AND dengan code milik elemen tersebut, verifikasi duplikasi elemen selesai dilakukan
if (((lines[i]|columns[j]|cell[cindx[i]][cindx[j]])&code) != 0) {
unreachable;
}
- Jika hasilnya 0, elemen tersebut belum ada di baris, kolom, maupun sel
- Jika hasilnya bukan 0, program berhenti dengan menjalankan perintah
unreachable
- Ini adalah cara paling sederhana di Zig untuk secara eksplisit menunjukkan runtime error
- Kode sebenarnya juga mencetak detail lokasi terjadinya error
- Contoh: jika
0 tepat setelah 8 pertama dalam string input diganti menjadi 5, maka terjadi error karena di baris 3 kolom 1 sudah ada angka 5
Memperbarui struktur data
- Di fungsi
set, loop for ganda berinteraksi per baris untuk menyalin tiap elemen baru dari string input s ke grid
- Variabel
k menjaga indeks karakter input baru dalam string s
- Karakter dikonversi ke
u4 (variabel c) dengan mengurangkan '0'
- Jika elemen baru yang akan dimasukkan ke grid bukan 0 (
c != 0), code yang dihitung dengan operasi shift kiri disalin ke tiap grid cerminan
- Lalu dilakukan bit OR (
|=) dengan grid cerminan terkait:
lines[i] |= code;
columns[j] |= code;
cell[cindx[i]][cindx[j]] |= code;
- Tidak perlu menguji secara eksplisit apakah nilai
c berada di antara 1 dan 9 — karena overflow akan terjadi saat operasi shift dijalankan
- Contoh: jika
0 tepat setelah 8 pertama pada string input diganti menjadi :, maka terjadi runtime error
- Mengganti
0 yang sama dengan / juga akan menimbulkan runtime error serupa
- Program hanya bekerja saat nilainya berada di rentang 1 sampai 9, yaitu ketika grid input hanya berisi digit desimal
- Banyak grid Sudoku di web merepresentasikan
0 sebagai ., sehingga fungsi set memiliki baris if (s[k] == '.') c = 0;
- Ini memudahkan untuk melewati operasi shift karena nilai
c adalah 0
Prototyping dan ketangguhan
- Error yang dipaksakan pada dua bagian di atas mendemonstrasikan fitur penting Zig
- Salah satunya adalah ketangguhan Zig — pada operasi shift, perilaku yang salah tidak diizinkan dan akan ditangkap saat runtime
- Meski semua upaya tampak diarahkan ke efisiensi, ini adalah contoh khas ketika performa dipertukarkan dengan ketangguhan
- Di C, jika operasi shift kehilangan bit, itu menjadi masalah programmer, dan hal ini diterjemahkan menjadi performa lebih baik dari instruksi assembler tertentu
- Fitur lainnya adalah kemungkinan menggunakan blok test untuk prototyping
- Kemungkinan penerapannya tak terhitung banyaknya, dan penerapan yang ditunjukkan di sini hanyalah debugging situasi tertentu saat error terjadi
- Fitur-fitur ini saja sudah memberikan kemampuan menakjubkan yang sangat jarang ditemukan dalam bahasa pemrograman, terutama bahasa pemrograman terkompilasi
Kesimpulan
- Zig terdiri dari tiga elemen kunci: kompatibilitas dengan C, cross-compilation, dan instalasi yang sederhana
- Karakteristik ini menunjukkan potensinya untuk menjadi standar baru bahasa pemrograman sistem
- Banyak keunggulan yang sebelumnya hanya ditemukan pada bahasa interpreter secara bertahap berpindah ke bahasa terkompilasi untuk memberikan performa yang lebih baik
- Zig sangat menonjol kemiripannya dengan bahasa interpreter melalui konsep eksekusi saat waktu kompilasi
- Hal ini membuat Zig terasa sangat berbeda dan kuat, sekaligus juga lebih sulit dipahami
1 komentar
Komentar Hacker News
Tulisan ini awalnya mengklaim bahwa “Zig bukan sekadar bahasa yang sederhana, melainkan cara pemrograman yang sepenuhnya baru”, tetapi kenyataannya hampir tidak membahas fitur yang benar-benar khas Zig
Inferensi tipe, struct anonim, labeled break, dan sebagainya sudah lama ada di bahasa lain
Yang benar-benar unik adalah comptime, tetapi bagian ini sama sekali tidak disebutkan
Ini memang bukan konsep yang sepenuhnya baru seperti makro Lisp, tetapi cara Zig menggunakannya sebagai pengganti generik cukup menarik
Namun, klaim dalam tulisan itu terasa sangat berlebihan
Rust bisa mengekspresikan dengan jelas kapan kode dijalankan, dan desainnya yang seperti query engine yang menelusuri seluruh ruang kode terasa mengesankan
Lihat tautan dokumentasi D
Jika berupa const-expression, maka akan dijalankan secara otomatis
Karena keduanya sudah seperti bahasa yang sepenuhnya berbeda, seperti Java/Scala
Zig memang lebih rapi daripada template C++, tetapi terasa lebih sebagai alternatif praktis ketimbang sesuatu yang revolusioner
Secara pribadi, saya tidak terlalu memahami antusiasme berlebihan seperti yang dulu terjadi pada Rust
Saya bahkan sudah membaca seluruh dokumentasi Zig, tetapi tetap tidak menemukan sesuatu yang benar-benar mengejutkan
Masalah terbesar Zig adalah error tidak bisa membawa data
Error hanya dikirim lewat kanal tambahan, sehingga debugging menjadi sulit, dan pada akhirnya para pengembang cenderung menghilangkan data error
Lihat isu terkait
Hanya dengan kode sederhana seperti AccessDenied, sulit mengetahui penyebabnya
Dalam praktiknya, bahkan jika memakai objek
Erroryang kompleks, sering kali tetap dibutuhkan kanal diagnostik terpisahKarena overhead performa atau masalah status sistem, dalam beberapa situasi lebih aman menanganinya dengan lazy binding
Zig punya filosofi yang memprioritaskan presisi dan determinisme seperti ini
Lihat isu terkait
Tetapi yang benar-benar dibutuhkan adalah structured logging dan pelacakan konteks berbasis call stack
Ini bisa mencegah pengembang yang malas menempelkan data secara sembarangan ke mana-mana
Saya setuju dengan klaim bahwa “cara pengembangan Zig itu sendiri adalah cara baru dalam mengembangkan bahasa”
Proses evolusi yang lambat dengan meninjau fitur secara hati-hati dan membuang yang tidak perlu terasa mengesankan
Saya ingin mendengar lebih konkret apa yang benar-benar khas dari Zig
Saya suka bahwa Zig bisa diinstal lewat PyPI
Paket ziglang bisa langsung dipakai setelah
pip install ziglangKode C juga bisa dibangun dengan
uvxAgak disayangkan karena fitur-fitur yang sebenarnya sudah ada di bahasa seperti Ada, Object Pascal, dan Modula-2 dibungkus sebagai “inovasi” Zig
Menarik melihat bagaimana ide 40 tahun lalu bisa tampak baru lagi ketika dibungkus ulang dengan sintaks bergaya C
Bagian pembuka tulisan itu bagus, tetapi setelah itu isinya hanya menjadi daftar fitur Zig
Sintaks yang intuitif dan alur kontrol yang eksplisit (seperti defer) memang menarik
Berkat comptime, tidak perlu mempelajari sintaks makro terpisah
Semua komponennya saling pas secara alami, sehingga bahkan saat pertama kali dipakai pun terasa seperti alat yang sudah lama dikenal
Sintaks
for (0..9)di Zig memang intuitif, tetapi karena berupa interval terbuka, ini sering membingungkanSeperti range(0, 9) di Python, mudah lupa apakah nilai terakhir ikut masuk atau tidak
0..9dan0..=9Ukuran interval bisa dihitung hanya dari selisih, dan iterasi mundur juga jadi lebih sederhana
0..<5(terbuka) dan0...5(tertutup)Saya tidak suka aturan identifier di Zig
Karena snake_case dan camelCase bercampur, rasanya aneh
Tetapi build system, allocator memori, dan pengalaman kompilasinya tetap sangat bagus
Saya lebih sering memakai Rust, tetapi rasa penasaran terhadap Zig tetap ada
Aturan prefix di library C juga sama-sama terasa merepotkan
Daya tarik Zig bukan terletak pada satu fitur tertentu, melainkan pada akumulasi keputusan yang praktis
Pilihan-pilihan yang awalnya tampak radikal justru makin masuk akal seiring pemahaman yang makin dalam
Zig adalah bahasa yang memberi imbalan kepada pengembang yang penuh rasa ingin tahu
Salah satu alasan Zig bagus adalah karena ia mengakui realitas kode sistem level rendah
Banyak bahasa mengabaikan bagian seperti ini demi alasan estetika, tetapi Zig tidak begitu
Lihat dokumentasi page_allocator