Salta al contenuto principale

Zig: un linguaggio di sistema moderno (e comprensibile) per sostituire C

Inviato da tuxsa il
zig

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