Immagina un linguaggio che ti dà il controllo totale di C, ma con una sintassi più pulita, meno sorprese a runtime e strumenti moderni integrati. Questo è Zig: un linguaggio open source pensato per scrivere software di sistema robusto, veloce e facile da leggere, senza garbage collector, senza runtime nascosti e senza magie.
Zig non vuole essere “il linguaggio perfetto per tutto”, ma uno strumento onesto: sai esattamente cosa fa il tuo codice, quando alloca memoria e quando esegue operazioni costose. Per questo è interessante sia per chi viene da C, sia per chi cerca un’alternativa più moderna e gestibile.
Hello World in Zig: semplice e diretto
Il classico punto di partenza è un “Hello, world!” minimale:
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, {s}!\n", .{"world"});
}
Salvi questo codice in hello.zig e lo esegui con:
zig run hello.zig
Output:
Hello, world!
Niente boilerplate, niente build system complesso: Zig include già tutto il necessario per compilare ed eseguire.
Gestione degli errori: esplicita, senza eccezioni
In Zig le funzioni che possono fallire restituiscono un error union type (es. !void). Non ci sono eccezioni nascoste: devi gestire esplicitamente gli errori con try, catch o propagandoli.
Nell’esempio sopra, main() restituisce !void: significa “può fallire”. La chiamata try stdout.print(...) dice: “se stampa fallisce, esci subito propagando l’errore”. Questo rende il flusso di controllo chiaro e prevedibile.
comptime: codice che gira in fase di compilazione
Una delle feature più potenti (e distintive) di Zig è comptime: permette di eseguire codice Zig durante la compilazione, al posto delle macro di C o dei template di C++.
Esempio: calcolare una tabella di quadrati in fase di compilazione:
const std = @import("std");
fn squaresTable(comptime N: usize) [N]u64 {
var out: [N]u64 = undefined;
comptime var i: usize = 0;
inline while (i < N) : (i += 1) {
out[i] = i * i;
}
return out;
}
pub fn main() void {
const table = squaresTable(5);
std.debug.print("Quadrati: {any}\n", .{table});
}
Output:
Quadrati: { 0, 1, 4, 9, 16 }
La funzione squaresTable viene eseguita a compile-time: il risultato è già pronto nel binario, senza costo a runtime.ziglang+2
Altro esempio pratico: comportarsi diversamente in base al sistema operativo, sempre a compile-time:
const std = @import("std");
const builtin = @import("builtin");
pub fn main() void {
switch (comptime builtin.os.tag) {
.linux => std.debug.print("Ciao, Linux!\n", .{}),
.macos => std.debug.print("Ciao, macOS!\n", .{}),
else => std.debug.print("Ciao, altro OS!\n", .{}),
}
}
Compilando su Linux vedrai “Ciao, Linux!”, su macOS “Ciao, macOS!”, tutto deciso in fase di compilazione.
Interoperabilità con C: leggere e usare header C come se fossero Zig
Uno dei punti di forza di Zig è la capacità di lavorare con l’enorme ecosistema C esistente. Zig può:
chiamare funzioni C direttamente,
importare header
.h,tradurre automaticamente codice C in Zig con
zig translate-c.
Esempio: usare printf da C
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() !void {
_ = c.printf("Ciao da C tramite Zig!\n");
}
Compili e esegui:
zig run printf.zig -lc
L’opzione -lc linka libc. Zig gestisce automaticamente tipi, costanti e funzioni dall’header stdio.h.
Esempio: tradurre un file C in Zig
Hai un vecchio file main.c e vuoi vederlo in Zig? Usa:
zig translate-c main.c > main.zig
Otterrai un file .zig equivalente, che puoi studiare, modificare o integrare gradualmente nel tuo progetto.coddykit+2
Questo è utilissimo per:
capire come mappare strutture e funzioni C in Zig,
migrare pezzo per pezzo un progetto legacy,
creare wrapper più sicuri attorno a librerie esistenti.
Toolchain integrata: compilare, testare, formattare
Zig non è solo un linguaggio, ma una toolchain completa:
zig build– sistema di build integrato,zig test– esegue test scritti in Zig,zig fmt– formatta il codice automaticamente,zig run– compila ed esegue in un passaggio,capacità di compilare anche file C, C++, Objective‑C e Objective‑C++ senza installare toolchain aggiuntive.
Esempio di test semplice:
const std = @import("std");
const testing = std.testing;
test "somma di due interi" {
const a: i32 = 2;
const b: i32 = 3;
try testing.expectEqual(@as(i32, 5), a + b);
}
Esegui i test con:
zig test somma.zig
Perché Zig può interessarti (anche se non scrivi kernel)
Zig non serve solo per sistemi operativi o embedded estremo. È utile anche per:
utility e tool da riga di comando: binari piccoli, veloci, facili da distribuire (nessuna dipendenza runtime),
server e daemon: controllo fine su memoria e I/O, senza garbage collector che introduce pause,
integrazione con progetti C esistenti: puoi riscrivere gradualmente parti critiche in Zig, mantenendo il resto in C,
apprendimento: la sintassi è pulita, il linguaggio è coerente, e ti costringe a pensare esplicitamente a memoria ed errori.
Rispetto a linguaggi come Rust, Zig rinuncia a garanzie di sicurezza a compile-time (es. borrow checker) in cambio di semplicità e controllo esplicito: sei tu a decidere quando e come allocare, deallocate e gestire gli errori. Per molti sviluppatori questo trade-off è preferibile: meno complessità cognitiva, più trasparenza.
Installare Zig: rapido e senza dipendenze
Sul sito ufficiale trovi pacchetti precompilati per Linux, macOS, Windows e BSD:
https://ziglang.org/download/
Su molte distribuzioni Linux puoi anche usare il gestore pacchetti, ad esempio:
# Esempio generico (dipende dalla distro) sudo apt install zig # Debian/Ubuntu, se disponibile nei repo # oppure sudo dnf install zig # Fedora
Oppure scarichi l’archivio da ziglang.org, lo estrai e aggiungi la cartella bin al tuo PATH.
Una volta installato, verifichi con:
zig version
Dove approfondire
Sito ufficiale e download: https://ziglang.org/download/
Overview e tutorial: https://ziglang.org/learn/
Documentazione completa (master): https://ziglang.org/documentation/master/
Guida pratica con esempi (inclusi
comptimee C): https://zig.guide/Tutorial su interoperabilità C: https://zighelp.org/chapter-4/