Panoramica
Caratteristiche di rilievo
Un linguaggio piccolo, semplice
Concentrati sul debuggare la tua applicazione, invece di debuggare la tua conoscenza del linguaggio di programmazione.
L'intera sintassi di Zig è specificata con un file di grammatica PEG di 580 righe.
Non c'è nessun controllo nascosto del flusso di esecuzione, nessuna allocazione dinamica nascosta, nessun preprocessore e nessuna macro. Se vedi del codice Zig e non sembra che stia chiamando una funzione, allora non lo sta facendo. Questo significa che il seguente codice chiamerà sicuramente solo foo()
e poi bar()
, e questo è garantito anche senza conoscere i tipi di dato utilizzati:
var a = b + c.d;
foo();
bar();
Esempi di controllo nascosto del flusso di esecuzione:
- D ha le funzioni
@property
, ovvero metodi che possono essere chiamati in un modo che sembra l'accesso a un attributo. Quindi, nell'esempio sopra,c.d
potrebbe chiamare una funzione. - C++, D e Rust supportano l'overload degli operatori, quindi l'operatore
+
potrebbe chiamare una funzione. - C++, D e Go hanno eccezioni throw/catch, quindi
foo()
potrebbe lanciare un'eccezione, e impedire l'esecuzione dibar()
.
Zig promuove la manutenibilità e leggibilità del codice permettendo di gestire il flusso di esecuzione esclusivamente con le parole chiave del linguaggio e le chiamate di funzioni.
Prestazioni e sicurezza: scegline due
Zig ha quattro modalità di build, che possono essere mescolate tra di loro e specificare la modalità di qualunque blocco o ambito di visibilità.
Parametro | Debug | ReleaseSafe | ReleaseFast | ReleaseSmall |
---|---|---|---|---|
Ottimizzazioni - migliora prestazioni, peggiora debug e tempo di compilazione | -O3 | -O3 | -Os | |
Controlli di sicurezza a runtime - peggiora prestazioni e dimensioni, crash invece di comportamenti non definiti (UB) | On | On |
Ecco come l'overflow di interi appare in fase di compilazione, a prescindere dalla modalità di build:
Ecco come appare durante l'esecuzione, nelle build con controlli di sicurezza attivi:
Questi stack trace funzionano su tutte le piattaforme, incluse quelle freestanding.
Con Zig puoi fare affidamento sulla modalità di build sicura, e disattivare selettivamente alcuni dei controlli di sicurezza se e dove sono necessarie maggiori prestazioni. Per esempio, il codice mostrato nell'esempio precedente può essere modificato in questo modo:
Zig usa i comportamenti non definiti come strumento estremamente preciso per prevenire bug e migliorare le prestazioni.
A proposito di prestazioni, Zig è più veloce rispetto a C.
- L'implementazione di riferimento usa LLVM come backend per applicare lo stato dell'arte delle ottimizzazioni.
- Ciò che altri progetti chiamano "Link Time Optimization", Zig lo fa in automatico.
- Per la piattaforma native vengono abilitate le funzioni avanzate della CPU (
-march=native
), grazie al fatto che la cross-compilazione è un caso d'uso primario. - Comportamenti non definiti (UB) accuratamente selezionati. Per esempio, in Zig sia gli interi con segno che quelli unsigned hanno comportamenti non definiti in caso di overflow, in contrasto a C dove ciò è vero solo per gli interi con segno. Questo facilita delle ottimizzazioni non disponibili in C.
- Zig espone direttamente un tipo di vettore SIMD, che rende semplice scrivere codice vettorizzato portabile.
Zig non è un linguaggio completamente sicuro. Per chi fosse interessato ad approfondire il tema della sicurezza in Zig, tieni d'occhio le seguenti discussioni:
- enumerate all kinds of undefined behavior, even that which cannot be safety-checked
- make Debug and ReleaseSafe modes fully safe
Zig compete con C invece di dipendere da C
La libreria standard di Zig comunica con libc, ma può farne a meno. Ecco un "hello world":
Quando compilato con -O ReleaseSmall
, senza simboli di debug, in modalità single-thread, il seguente codice produce un eseguibile statico di 9.8 KiB per la piattaforma x86_64-linux:
$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded
$ wc -c hello
9944 hello
$ ldd hello
not a dynamic executable
Una build per Windows è persino più piccola, occupando solo 4096 byte:
$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded -target x86_64-windows
$ wc -c hello.exe
4096 hello.exe
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
Dichiarazioni di alto livello indipendenti dall'ordine
Le dichiarazioni di alto livello come le variabili globali sono visibili indipendentemente dall'ordine con cui appaiono nel codice, e sono analizzate in modo lazy. I valori usati per inizializzare le variabili globali sono valutati in fase di compilazione.
Tipi opzionali invece di puntatori nulli
In altri linguaggi di programmazione, i riferimenti nulli sono la causa di molte eccezioni a runtime, e sono persino accusati di essere il più grave errore delle scienze informatiche.
In Zig, di base i puntatori non possono essere nulli:
Ciononostante, ogni tipo di dato può essere reso un tipo opzionale anteponendo un ?
:
Per estrarre il valore da un tipo opzionale (unwrapping), si può usare orelse
per fornire un valore di default:
In alternativa si può usare un if
:
La stessa sintassi funziona con while:
Gestione manuale della memoria
Una libreria scritta in Zig è adatta a qualunque contesto:
- Applicazioni desktop
- Server a bassa latenza
- Kernel di sistemi operativi
- Sistemi embedded
- Software real-time, come performance live, aeroplani, pacemaker
- In browser web o plugin con WebAssembly
- Utilizzo da altri linguaggi di programmazione, mediante l'ABI di C
Per permettere questo risultato, i programmatori Zig devono gestire manualmente la memoria, e devono gestire gli errori di allocazione.
Questo vale anche per la libreria standard di Zig. Ogni funzione che necessita di allocazioni dinamiche accetta come parametro un allocatore. Per questo motivo, la libreria standard di Zig può essere utilizzata anche su piattaforme hardware.
In aggiunta a un nuovo approccio alla gestione degli errori, Zig fornisce defer e errdefer per rendere tutta la gestione di risorse - non solo la memoria - semplice e facilmente verificabile.
Per un esempio di defer
, vedi la sezione Integrazione con librerie C senza FFI/binding. Ecco un esempio di utilizzo di errdefer
:
Un nuovo approccio alla gestione degli errori
Gli errori sono valori, e non possono essere ignorati:
Gli errori possono essere gestiti con catch:
La parola chiave try è un'abbreviazione di catch |err| return err
:
Nota che questa è un Error Return Trace, non uno stack trace. Il codice non ha dovuto pagare il prezzo dell'unwinding dello stack per ottenere queste informazioni.
La parola chiave switch usata su un errore garantisce che tutti i possibili errori siano gestiti:
La parola chiave unreachable viene usata per dichiarare che non si verificherà alcun errore:
Nelle build senza controlli di sicurezza, questo risulterà un comportamento non definito, quindi assicurati di usare unreachable
solo quando hai la certezza che un'operazione avrà successo.
Stack trace su ogni piattaforma
I stack trace e gli error return trace mostrati in questa pagina funzionano su tutte le piattaforme con supporto Tier 1 e alcune con supporto Tier 2. Persino piattaforme hardware!
Inoltre, la libreria standard permette di catturare uno stack trace in qualunque punto del programma e inviarlo al canale standard error in un secondo momento:
Puoi vedere un utilizzo di questa tecnica nel progetto GeneralPurposeDebugAllocator.
Strutture dati generiche e funzioni
I tipi di dato sono valori che devono essere conosciuti in fase di compilazione.
Una struttura dati generica è semplicemente una funzione che restituisce un valore di tipo type
:
Riflessione ed esecuzione in fase di compilazione
La funzione builtin @typeInfo permette la riflessione:
La libreria standard di Zig usa questa tecnica per implementare l'output di stringhe formattate. Nonostante il linguaggio sia piccolo e semplice, questa funzione è implementata interamente in Zig. Nel frattempo, in C, gli errori di compilazione per printf
sono hard-coded, scritti direttamente nel compilatore. Similmente, in Rust, la funzione equivalente è una macro implementata direttamente nel compilatore.
Zig può anche valutare funzioni e blocchi di codice in fase di compilazione. In alcuni contesti, come l'inizializzazione di variabili globali, le espressioni sono implicitamente valutate in fase di compilazione. In alternativa, la parola chiave comptime permette di eseguire esplicitamente una porzione di codice in fase di compilazione. Può essere estremamente utile se combinato con le asserzioni:
Integrazione con librerie C senza FFI/bindings
@cImport importa direttamente tipi, variabili, funzioni e macro semplici, permettendone l'utilizzo in Zig. Traduce persino le funzioni inline
da C a Zig.
Il seguente esempio emette un'onda sonora sinusoidale utilizzando libsoundio:
sine.zig
$ zig build-exe sine.zig -lsoundio -lc
$ ./sine
Output device: Built-in Audio Analog Stereo
^C
Questo codice Zig è molto più semplice dell'equivalente C, oltre ad avere migliori controlli sugli errori, e tutto questo è stato ottenuto importando direttamente un file di intestazione C - senza binding delle API.
Zig usa le librerie C meglio di quanto C usa le librerie C.
Zig è anche un compilatore C
Ecco un esempio di come Zig compila del codice C:
hello.c
#include <stdio.h>
int main(int argc, char **argv) {
printf("Hello world\n");
return 0;
}
$ zig build-exe hello.c --library c
$ ./hello
Hello world
Puoi usare --verbose-cc
per vedere il comando eseguito dal compilatore C:
$ zig build-exe hello.c --library c --verbose-cc
zig cc -MD -MV -MF .zig-cache/tmp/42zL6fBH8fSo-hello.o.d -nostdinc -fno-spell-checking -isystem /home/andy/dev/zig/build/lib/zig/include -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-gnu -isystem /home/andy/dev/zig/build/lib/zig/libc/include/generic-glibc -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-any -isystem /home/andy/dev/zig/build/lib/zig/libc/include/any-linux-any -march=native -g -fstack-protector-strong --param ssp-buffer-size=4 -fno-omit-frame-pointer -o .zig-cache/tmp/42zL6fBH8fSo-hello.o -c hello.c -fPIC
Nota che eseguendo di nuovo il comando, non c'è alcun output, e la compilazione è istantanea:
$ time zig build-exe hello.c --library c --verbose-cc
real 0m0.027s
user 0m0.018s
sys 0m0.009s
Questo è possibile grazie al caching dei risultati di compilazione. Zig legge automaticamente il file .d
e usa un robusto sistema di caching per evitare di rifare lavoro non necessario.
Non solo Zig può compilare codice C, ma ci sono anche ottime ragioni per usare Zig come compilatore C: Zig integra libc.
Esporta funzioni, variabili e tipi per permetterne l'uso a C
Uno dei casi d'uso primari di Zig è esportare una libreria con l'ABI di C che possa essere richiamata da altri linguaggi di programmazione. La parola chiave export
anteposta a funzioni, variabili e tipi di dato, li rende parte dell'API della libreria:
mathtest.zig
Per creare una libreria statica:
$ zig build-lib mathtest.zig
Per creare una libreria dinamica/condivisa:
$ zig build-lib mathtest.zig -dynamic
Un esempio con il sistema di build di Zig:
test.c
#include "mathtest.h"
#include <stdio.h>
int main(int argc, char **argv) {
int32_t result = add(42, 1337);
printf("%d\n", result);
return 0;
}
build.zig
$ zig build test
1379
la cross-compilazione è un caso d'uso primario
Zig può compilare per ognuna delle piattaforme indicate nella tabella di supporto (vedi le note di rilacio dell'ultima versione di Zig) come Tier 3 o superiore. Non è necessario installare alcuna "cross toolchain" o niente di simile. Ecco un Hello World nativo:
Ora lo compiliamo per x86_64-windows
, x86_64-macos
e aarch64-linux
:
$ zig build-exe hello.zig -target x86_64-windows
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
$ zig build-exe hello.zig -target x86_64-macos
$ file hello
hello: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ zig build-exe hello.zig -target aarch64-linux
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped
Questo funziona da qualunque piattaforma Tier 3+, verso qualunque piattaforma Tier 3+.
Zig integra libc
Puoi vedere la lista delle piattaforme supportate da libc
con il comando zig targets
:
...
"libc": [
"aarch64_be-linux-gnu",
"aarch64_be-linux-musl",
"aarch64_be-windows-gnu",
"aarch64-linux-gnu",
"aarch64-linux-musl",
"aarch64-windows-gnu",
"armeb-linux-gnueabi",
"armeb-linux-gnueabihf",
"armeb-linux-musleabi",
"armeb-linux-musleabihf",
"armeb-windows-gnu",
"arm-linux-gnueabi",
"arm-linux-gnueabihf",
"arm-linux-musleabi",
"arm-linux-musleabihf",
"arm-windows-gnu",
"mips64el-linux-gnuabi64",
"mips64el-linux-gnuabin32",
"mips64el-linux-musl",
"mips64-linux-gnuabi64",
"mips64-linux-gnuabin32",
"mips64-linux-musl",
"mipsel-linux-gnu",
"mipsel-linux-musl",
"mips-linux-gnu",
"mips-linux-musl",
"powerpc64le-linux-gnu",
"powerpc64le-linux-musl",
"powerpc64-linux-gnu",
"powerpc64-linux-musl",
"powerpc-linux-gnu",
"powerpc-linux-musl",
"riscv64-linux-gnu",
"riscv64-linux-musl",
"s390x-linux-gnu",
"s390x-linux-musl",
"sparc-linux-gnu",
"sparcv9-linux-gnu",
"wasm32-freestanding-musl",
"x86-linux-gnu",
"x86-linux-musl",
"x86-windows-gnu",
"x86_64-linux-gnu",
"x86_64-linux-gnux32",
"x86_64-linux-musl",
"x86_64-windows-gnu"
],
Questo significa che --library c
su queste piattaforme non dipende da alcun file di sistema!
Diamo nuovamente un'occhiata a quel Hello World in C:
$ zig build-exe hello.c --library c
$ ./hello
Hello world
$ ldd ./hello
linux-vdso.so.1 (0x00007ffd03dc9000)
libc.so.6 => /lib/libc.so.6 (0x00007fc4b62be000)
libm.so.6 => /lib/libm.so.6 (0x00007fc4b5f29000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00007fc4b5d0a000)
libdl.so.2 => /lib/libdl.so.2 (0x00007fc4b5b06000)
librt.so.1 => /lib/librt.so.1 (0x00007fc4b58fe000)
/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc4b6672000)
glibc non supporta la creazione di binari statici, ma musl lo può fare:
$ zig build-exe hello.c --library c -target x86_64-linux-musl
$ ./hello
Hello world
$ ldd hello
not a dynamic executable
In questo esempio, Zig ha compilato musl libc
da sorgente e poi ha linkato ad esso. La build di musl libc
per x86_64-linux
rimane disponibile grazie al sistema di cache, quindi ogni volta che ci sarà nuovamente bisogno di questo libc
sarà disponibile istantaneamente.
Questo significa che questa funzionalità è disponibile su ogni piattaforma. Gli utenti Windows e macOS possono compilare codice Zig e C, e linkare libc
, per ognuno dei target elencati sopra. Allo stesso modo, è possibile cross-compilare del codice per altre architetture:
$ zig build-exe hello.c --library c -target aarch64-linux-gnu
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped
In un certo senso, Zig compila C meglio dei compilatori C!
Questa funzionalità è più avanzata rispetto alla sola integrazione di uno strumento di cross-compilazione in Zig. Per esempio, la dimensione totale non compressa delle intestazioni libc incluse in Zig è 22 MiB. Allo stesso tempo, gli header di musl libc + header di linux per la sola architettura x86_64 sono in totale 8 MiB, e per glibc sono 3.1 MiB (a glibc mancano gli header linux), eppure Zig al momento include 40 versioni di libc. Includendoli così come sono, sarebbero 444 MiB. Però, grazie a questo strumento di elaborazione header, e del buon vecchio olio di gomito, gli archivi scaricabili di Zig rimangono a circa 50 Mib totali, nonostante supportino libc
su tutti questi target, oltre a compiler-rt, libunwind e libcxx, e nonostante sia un compilator compatibile con Clang. Per fare un confronto, la build per Windows dello stesso Clang (v8.0.0) presa da llvm.org occupa 132 MiB.
Nota che solo i target con supporto Tier 1 sono stati testati in modo esaustivo. Abbiamo già in programma di aggiungere altri libc (anche per Windows), e di aggiungere copertura dei test per la compilazione con tutte le versioni di libc.
It's planned to have a Zig Package Manager, but it's not done yet. One of the things that will be possible is to create a package for C libraries. This will make the Zig Build System attractive for Zig programmers and C programmers alike.
Il build system di Zig
Zig ha un proprio sistema di build, quindi non servono make
, cmake
o simili.
$ zig init-exe
Created build.zig
Created src/main.zig
Ora prova `zig build --help` oppure `zig build run`
src/main.zig
build.zig
Diamo un'occhiata a quel menu --help
:
$ zig build --help
Usage: zig build [steps] [options]
Steps:
install (default) Copy build artifacts to prefix path
uninstall Remove build artifacts from prefix path
run Run the app
General Options:
--help Print this help and exit
--verbose Print commands before executing them
--prefix [path] Override default install prefix
--search-prefix [path] Add a path to look for binaries, libraries, headers
Project-Specific Options:
-Dtarget=[string] The CPU architecture, OS, and ABI to build for.
-Drelease-safe=[bool] optimizations on and safety on
-Drelease-fast=[bool] optimizations on and safety off
-Drelease-small=[bool] size optimizations on and safety off
Advanced Options:
--build-file [file] Override path to build.zig
--cache-dir [path] Override path to zig cache directory
--override-lib-dir [arg] Override path to Zig lib directory
--verbose-tokenize Enable compiler debug output for tokenization
--verbose-ast Enable compiler debug output for parsing into an AST
--verbose-link Enable compiler debug output for linking
--verbose-ir Enable compiler debug output for Zig IR
--verbose-llvm-ir Enable compiler debug output for LLVM IR
--verbose-cimport Enable compiler debug output for C imports
--verbose-cc Enable compiler debug output for C compilation
--verbose-llvm-cpu-features Enable compiler debug output for LLVM CPU features
Come vedi uno degli step diponibili è run
:
$ zig build run
All your base are belong to us.
Ecco alcuni esempi di build script:
- Build script di un gioco Tetris in OpenGL
- Build script di un gioco arcade per Raspberry Pi 3 (solo hardware)
- Build script del compilatore self-hosted di Zig
Concurrency via Async Functions
Zig 0.5.0 introduced async functions. This feature has no dependency on a host operating system or even heap-allocated memory. That means async functions are available for the freestanding target.
Zig infers whether a function is async, and allows async
/await
on non-async functions, which means that Zig libraries are agnostic of blocking vs async I/O. Zig avoids function colors.
The Zig Standard Library implements an event loop that multiplexes async functions onto a thread pool for M:N concurrency. Multithreading safety and race detection are areas of active research.
Vasta gamma di piattaforme supportate
Zig usa un sistema "grado di supporto" per indicare fino a che punto Zig è supportato su una certa piattaforma.
Tabella di supporto aggiornata a Zig 0.11.0
Semplifica la vita ai mantenitori di pacchetti
The reference Zig compiler is not completely self-hosted yet, but no matter what, it will remain exactly 3 steps to go from having a system C++ compiler to having a fully self-hosted Zig compiler for any target. Come fa notare Maya Rashish, rendere Zig disponibile su altre piattaforme è divertente e richiede poco tempo.
Ad eccezione della modalità debug, le modalità di build sono riproducibili/deterministiche.
C'è una versione JSON della pagina dei download.
Diversi membri del team di Zig hanno esperienza nel mentenimento di pacchetti.
- Daurnimator mantiene il pacchetto per Arch Linux.
- Marc Tiehuis mantiene il pacchetto per Visual Studio Code.
- Andrew Kelley ha passato circa un anno gestendo pacchetti per Debian e Ubuntu, e ogni tanto contribuisce a nixpkgs.
- Jeff Fowler mantiene il pacchetto Homebrew e ha creato il pacchetto Sublime Text (ora mantenuto da emekoi).