- Zig berbasis sintaks berbasis kurung kurawal yang mirip Rust, tetapi ditingkatkan dengan semantik bahasa yang lebih sederhana dan pilihan sintaks yang lebih rapi
- Literal bilangan bulat semuanya dimulai sebagai
comptime_int dan dikonversi secara eksplisit saat ditugaskan, sementara literal string menggunakan notasi string mentah ringkas berbasis \\
- Literal record dalam bentuk
.x = 1 memudahkan pencarian penulisan field, dan semua tipe direpresentasikan secara konsisten dengan notasi prefiks
and·or digunakan sebagai keyword alur kontrol, dan konstruksi if·loop dapat menghilangkan kurung kurawal secara opsional, dengan formatter yang menjamin keamanan
- Tanpa namespace, semua hal diperlakukan sebagai ekspresi untuk menyatukan sintaks tipe·nilai·pola, serta memanfaatkan generic·literal record·fungsi bawaan (
@import, @as, dll.) secara ringkas
Ikhtisar
- Zig memiliki tampilan yang mirip Rust, tetapi mengadopsi struktur bahasa yang lebih sederhana
- Desain sintaksnya berfokus pada ramah grep, konsistensi sintaks, dan mengurangi noise visual yang tidak perlu
Literal bilangan bulat
const an_integer = 92;
assert(@TypeOf(an_integer) == comptime_int);
const x: i32 = 92;
const y = @as(i32, 92);
- Semua literal bilangan bulat bertipe
comptime_int
- Saat ditugaskan ke variabel, tipe harus ditentukan secara eksplisit atau dikonversi dengan
@as
- Bentuk
var x = 92; tidak akan berfungsi dan memerlukan tipe eksplisit
Literal string
const raw =
\\Roses are red
\\ Violets are blue,
\\Sugar is sweet
\\ And so are you.
\\
;
- Setiap baris adalah token terpisah sehingga tidak ada masalah indentasi
- Tidak perlu melakukan escape pada
\\ itu sendiri
Literal record
const p: Point = .{
.x = 1,
.y = 2,
};
- Format
.x = 1 menguntungkan untuk membedakan baca/tulis
- Notasi
.{} membedakannya dari blok sambil otomatis dikonversi ke tipe hasil
Notasi tipe
u32 // bilangan bulat
[3]u32 // array panjang 3
?[3]u32 // array yang bisa null
*const ?[3]u32 // pointer konstan
- Semua tipe memakai notasi prefiks
- Dereferensi memakai notasi sufiks (
ptr.*)
Identifier
const @"a name with space" = 42;
- Dapat mencegah konflik keyword atau memberi nama khusus
Deklarasi fungsi
pub fn main() void {}
fn add(x: i32, y: i32) i32 {
return x + y;
}
- Keyword
fn dan nama fungsi berdempetan sehingga mudah dicari
- Tidak memakai
-> untuk menuliskan tipe kembalian
Deklarasi variabel
const mid = lo + @divFloor(hi - lo, 2);
var count: u32 = 0;
- Menggunakan
const dan var
- Penulisan tipe mengikuti urutan
nama: tipe
Alur kontrol: and/or
while (count > 0 and ascii.isWhitespace(buffer[count - 1])) {
count -= 1;
}
and, or adalah keyword alur kontrol
- Operasi bit menggunakan
&, |
Pernyataan if
.direction = if (prng.boolean()) .ascending else .descending;
- Tanda kurung wajib, kurung kurawal opsional
zig fmt menjamin format yang aman
Perulangan
for (0..10) |i| {
print("{d}\n", .{i});
} else @panic("loop safety counter exceeded");
for dan while sama-sama mendukung klausa else
- Iterator dan nama elemen ditempatkan secara intuitif
Namespace dan resolusi nama
const std = @import("std");
const ArrayList = std.ArrayList;
- Shadowing variabel dilarang
- Tidak ada namespace maupun glob import
Semua adalah ekspresi
const E = enum { a, b };
const e: if (true) E else void = .a;
- Menyatukan sintaks tipe·nilai·pola
- Ekspresi kondisional dapat ditempatkan di posisi tipe
Generic
fn ArrayListType(comptime T: type) type {
return struct {
fn init() void {}
};
}
var xs: ArrayListType(u32) = .init();
- Generic diekspresikan dengan sintaks pemanggilan fungsi (
Type(T))
- Argumen tipe selalu eksplisit
Fungsi bawaan
const foo = @import("./foo.zig");
const num = @as(i32, 92);
- Memanggil fitur yang disediakan compiler dengan prefiks
@
@import menampilkan jalur file dengan jelas
- Argumennya harus berupa literal string
Kesimpulan
- Sintaks Zig adalah contoh bagaimana sekumpulan pilihan kecil dapat membentuk bahasa yang enak dibaca
- Jika jumlah fitur dikurangi, sintaks yang dibutuhkan juga berkurang, dan kemungkinan konflik antarsintaks pun menurun
- Meminjam ide bagus dari bahasa yang sudah ada, tetapi berani memperkenalkan sintaks baru saat diperlukan
1 komentar
Komentar Hacker News
Tulisan ini membahas dengan sangat mendalam berbagai trade-off yang muncul dalam perancangan sintaks, dan saya merasa sangat terkesan dengan minimalisme, konsistensi, serta fokus Zig yang nyaris tanpa ampun pada keterbacaan. Yang saya suka, ini bukan keindahan yang abstrak, melainkan semacam 'brutalisme' yang tidak menghadirkan kejutan dalam penggunaan industri. Perancangan sintaks yang seimbang seperti ini benar-benar langka, dan menurut saya Zig berhasil melakukannya dengan baik
Agak disayangkan artikel ini tidak menyinggung penanganan error. Pendekatan
try/catchdi Zig sangat bagus, sampai-sampai itu menjadi cara penanganan error favorit saya di antara banyak bahasa. Akan lebih baik kalau bagian ini juga diperkenalkanDaya tarik sejati Zig bukanlah 'keterbacaan yang indah di permukaan', melainkan keindahan yang konsisten yang didapat melalui abstraksi. Seperti analogi S-expression dan M-expression, pendekatan yang baik untuk kasus umum sering kali lebih unggul dalam jangka panjang daripada desain khusus untuk banyak situasi pengecualian. Jika Anda terus menambahkan kasus pengecualian seperti di C++, pada akhirnya beban menghafal semua aturan hanya akan makin besar. Dalam desain bahasa, jika kita mengejar kesederhanaan dan konsistensi, kompleksitas bisa saja berpindah ke pengguna hingga jatuh ke 'Turing tarpit', jadi yang penting adalah pendekatan di mana kasus khusus terselesaikan secara alami dari aturan umum. Contoh seperti ini juga bisa dilihat di komik XKCD New Pet
Kalau ada contoh yang menurut Anda berkesan, saya penasaran apakah Anda bisa membagikannya
Soal Zig yang menggunakan gaya anotasi tipe 'nama:tipe' seperti Rust, saya justru lebih menyukai cara tradisional di mana tipe ditulis lebih dulu. Saat saya kembali memeriksa deklarasi variabel, hal yang paling ingin saya ketahui adalah tipe variabel itu, dan kalau saya tidak bisa menemukannya dengan cepat rasanya tidak nyaman. Terutama di Rust, ada banyak elemen yang terasa berulang dan tidak perlu seperti
let mut, sehingga malah merepotkan, dan saya juga suka gaya C atau C++ yang menempatkan tipe lebih dulu. Dalam praktiknya, menurut saya idealnya inferensi tipe dipakai seminimal mungkin hanya di tempat yang memang membutuhkannyaKata kunci
letmemang juga berguna karena dengan jelas menunjukkan bahwa itu adalah sebuah pernyataan deklarasi. Tanpa itu, Anda bisa mengalami masalah parsing sintaks ambigu seperti di C++Saya juga selalu cenderung memeriksa tipe variabel lebih dulu, jadi saya lebih suka gaya dengan tipe di depan. Dari sudut pandang parser, memproses nama lebih dulu memang lebih mudah, dan saya paham TypeScript mengadopsi struktur ini demi kompatibilitas dengan JavaScript. Pada akhirnya yang penting menurut saya adalah standard library yang mudah digunakan. Seperti pada contoh penyalahgunaan sistem tipe secara berlebihan, menyampaikan niat dengan jelas lebih penting daripada memaksakan semua state diekspresikan sebagai tipe
Saya memang sering menggulir ke atas untuk memeriksa tipe variabel di kode, tetapi justru kalau tipe ditulis lebih dulu, lebih sulit menemukan deklarasi variabel yang saya cari. Nama tipe berada paling depan dan panjangnya bervariasi, jadi mata harus bergerak bolak-balik ke kiri dan kanan, yang terasa tidak efisien
Dalam banyak kasus editor akan langsung menampilkan informasi tipe saat kursor diarahkan ke sana, jadi posisi tipe di kode mungkin tidak terlalu penting. Rust verbose sebagian besar karena alasan implementasi untuk menghindari ambiguitas parsing. Kalau tipe ditulis di depan seperti C atau C++, variabel yang dideklarasikan dengan nama tertentu jadi lebih sulit dicari dengan grep, dan gaya menaruh return type di depan memang diperkenalkan karena template, tetapi dalam beberapa kasus justru membuat kode lebih mudah dibaca dan dicari
Secara pribadi saya lebih menyukai gaya anotasi tipe ala Pascal. Bahkan saat memakai inferensi tipe pun tidak perlu fitur pelengkap seperti 'auto', dan dari sudut pandang parsing juga lebih tidak ambigu. Dalam
MyClass x, sulit langsung tahu apakah MyClass itu tipe atau nama variabel, jadi pendekatan ini membantu mengurangi ambiguitas semacam ituUntuk sintaks raw/multiline string Zig, cara yang mengharuskan menulis
\\berkali-kali terasa terlalu membingungkan dan ekstremKalau Anda pernah memformat multiline string di Python, C++, atau Rust, Anda akan mengerti kerepotannya. Selalu ada masalah apakah indentasi ikut masuk ke isi string, dan kalau seperti YAML yang punya mode penghapusan indentasi, itu justru bisa menambah kebingungan. Pendekatan Zig sangat jelas dalam hal indentasi
Awalnya sintaks ini terasa sangat tidak nyaman, tetapi setelah memakai Zig, pelan-pelan jadi terbiasa dan justru mulai terlihat kelebihannya. Zig memang menarik karena saat pertama kali ditemui bisa menimbulkan kesan tidak suka, tetapi setelah benar-benar dipakai, Anda jadi menyadari keunggulannya
Sebenarnya ini bukan sintaks yang gila, melainkan masalahnya yang gila dan rumit: bagaimana menyisipkan multiline string lain dengan aman di dalam multiline string. Di Zig, saya suka karena tidak perlu escape tambahan dan tidak perlu khawatir soal indentasi
trimIndentdi Kotlin, text block di Go atau Java, dan terutama raw string dengan backtick di Go terasa lebih mulus bagi saya. Di Zig, karena\\, saya malah mengakalinya dengan memakai pendekatan@embedFileSecara visual saya memang tidak terlalu suka
\\, tetapi menurut saya itu cara yang rapi untuk menyelesaikan masalah multiline literal dan indentasi. Saya juga tidak tahu bahasa lain yang bisa menyelesaikan masalah ini tanpa bantuan fungsiSintaks Zig terasa agak ramai. Konstruksi yang diawali
@seperti@TypeOfatau sintaks inisialisasi seperti.{.x}terasa canggung. Mungkin karena saya belum mahir memakai Zig, tetapi secara keseluruhan saya punya kesan bahwa kodenya agak sulit dibacaSaya lebih suka sintaks Odin karena jauh lebih minimal dan lebih rapi. Zig terasa agak ramai
.di Zig berperan sebagai placeholder untuk tipe yang diinferensikan. Misalnya Anda bisa menginisialisasi objek seperti iniAtau kalau ingin menyatakan inferensi tipe secara eksplisit
Tipe juga bisa dihilangkan pada argumen fungsi sehingga lebih ringkas. Di Rust, tipe harus ditulis secara eksplisit dalam situasi seperti ini
Dalam inisialisasi struct bertingkat pun, cara inferensi Zig jauh lebih berguna. Di Rust, keharusan menulis tipe secara eksplisit di mana-mana bisa cepat membuat kode terasa ramai. Meski begitu, saya pribadi tetap merasa akan lebih nyaman jika notasi dot di depan dihilangkan, tetapi sepertinya dipertahankan demi menyederhanakan implementasi parser. Notasi
x: 123atau.x = 123masing-masing dipinjam dari JS dan C99. Secara pribadi saya cukup sering memakai keduanya, jadi tidak terasa anehSaya jauh lebih menyukai raw string literal C# 11. Indentasi pada baris pertama dijadikan acuan untuk otomatis menyelaraskan indentasi di baris-baris sisanya. Selain itu, kurung kurawal juga bisa dipakai sebagai karakter literal. Jika
$muncul beberapa kali, kurung kurawal diperlakukan sepenuhnya sebagai nilai literal"""terakhir, dan baris pertama juga boleh diberi indentasi. Senang rasanya fitur ini disukai, dan saya cukup bangga karena menurut saya ini fitur yang bagusSintaks Zig memang bagus, tetapi dibanding Go yang bisa ditulis cukup rapi tanpa titik koma atau
:, saya rasa belum sampai level 'lovely'. Kalau harus dibandingkan, memang jauh lebih baik daripada Rust, tetapi Go juga sudah sangat bagusJustru sintaks yang terlalu minimal seperti Go kadang lebih sulit ditafsirkan saat dibaca. Waktu membaca kode lebih banyak daripada menulisnya, jadi keringkasan yang berlebihan justru bisa memicu kesalahan dan membuat debugging lebih sulit. CoffeeScript atau J adalah contoh khas sintaks yang terlalu dipadatkan
Saya tidak merasa bahwa menghapus elemen sintaks otomatis membuat sintaks jadi lebih baik. Kalau memang begitu, semua orang pasti akan menulis seperti Lisp, dan teks pun akan ditulis seperti scriptio continua, yaitu gaya tulisan kuno tanpa spasi. Lihat Wikipedia scriptio continua
Secara keseluruhan saya puas dengan Zig, tetapi ada beberapa hal yang masih disayangkan
a?.b?.c) tidak dimungkinkan. Kalau ada dukungan tipe monadik, chaining yang lebih umum akan memungkinkan, tetapi untuk saat ini masih kurangcatch, jadi rasanya akan lebih fleksibel jika lambda juga didukungSoal penggunaan
voidsebagai nama tipe, sebenarnya dalam teori tipe, void bukan berperan sebagai 'unit' melainkan berarti tipe 'uninhabited' yang tidak memiliki nilai. Secara tradisional,()atauunitadalah tipe yang memiliki satu anggota.voidadalah tipe kembalian untuk fungsi sepertiabortDi C dan C++,
voidsudah digunakan cukup baik dan sangat akrab bagi banyak programmer sistem. Menurut saya, perdebatan istilah dalam teori formal tidak terlalu berarti dalam penggunaan nyata. Banyak orang yang datang ke Zig punya latar belakang C atau C++, jadivoidsudah cukup masuk akalabortitu lebih cocok untuk tipe keadaan 'tak terjangkau' seperti tipe!di Rust.voidjustru lebih dekat keunitatau(), yaitu tipe ketika tidak ada nilai yang berarti. Sebagai trik yang menarik, di TypeScript, jikavoiddipakai dalam generic constraint, parameter tersebut bisa dibuat menjadi opsionalTipe
voidpunya tradisi yang sangat panjang, sampai bisa ditelusuri kembali ke ALGOL 68. Di sana, tipe VOID didefinisikan sebagai tipe yang hanya memiliki satu anggota, yaitu EMPTYSaya kaget mendengar bahwa "Zig tidak punya lambda". Di C++, lambda dipakai hampir di mana-mana, jadi saya penasaran bagaimana comparator untuk hal seperti sort array didefinisikan
Karena biasanya perlu mendeklarasikan fungsi secara terpisah, saya merasa bagian itu kurang nyaman di Zig
Anda bisa mereferensikan inline struct anonim beserta fungsi di dalamnya. Memang fitur capture yang umum dipakai pada lambda tidak ada di Zig, tetapi itu bisa digantikan dengan meneruskan parameter konteks, biasanya berupa struct
Pada dasarnya sama seperti di C: mendeklarasikan fungsi pembanding secara terpisah lalu mengirim pointer-nya ke fungsi pengurutan
Orang sering berkata "sintaks tidak penting", tetapi pada praktiknya itu sering berarti "karena sintaks tidak penting, mari pakai gaya yang saya sukai". Saya sendiri juga terbiasa dengan sintaks turunan C seperti Rust/Zig/Go, dan gaya seperti Haskell/OCaml yang membedakan pemanggilan fungsi lewat spasi masih terasa asing sehingga menurut saya menghambat adopsi massal. Seperti keberhasilan Rust, bahasa lain juga bisa belajar dari bagaimana ia berhasil mencampurkan 'bayam' pemrograman fungsional ke dalam 'brownie' bahasa sistem
Saya tidak setuju dengan pernyataan bahwa sintaks tidak penting. Pada akhirnya, sintaks adalah antarmuka utama yang dipakai pengguna untuk berinteraksi dengan bahasa. Setiap kali saya membaca suatu bahasa, elemen sintaksnya selalu terasa menonjol secara bawah sadar
Jika Anda menginginkan bahasa fungsional dengan sintaks bergaya C, saya merekomendasikan Gleam: gleam.run Kodenya juga sangat cantik
Reason juga layak direkomendasikan. Berbasis OCaml, tetapi memiliki sintaks bergaya C: reasonml.github.io