ZIG - Hello, world!


In Zig già il semplice saluto che introduce lo studio di tutti i linguaggi, ci permette delle interessanti considerazioni per iniziare ad addentrarci nelle pieghe di questo linguaggio. Ovviamente non tutto potrà essere charo da subito ma è il prezzo da pagare in queste fasi introduttive, speicalmente quando c'è un linguaggio he ha l'ambizione di proporre delle novità. Tutto, piano piano, diventerà più chiaro.
Vediamo un primo esempio.

Esempio 2-1

const std = @import("std");
pub fn main() !void {
  try std.fs.File.stdout().writeAll("Hello world!\n");
}

Definiamo nella prima riga una costante di nome std che importa la libreria standard std. Questo nome non può più essere cambiato essendo definito appunto costante. Questo tipo di definizione è obbligatoria per il linguaggio. La @ che trovate prima di import indica che si tratta di funzioni built-in ovvero sono primitive per il linguaggio, riconosciute e gestite direttamente dal compilatore.
pub fn main() !void - questa istruzione ci presenta l'entry point del programma, una funzione, introdotta tramite la keyword fn e dal nome obbligatorio main che deve essere marcata pubblica come ci indica pub. Il compilatore lo impone pena un errore in compilazione. Da notare quel !void che è interessante e non significa come potrebbe sembrare per chi viene da altri linguaggi, "non vuoto" ma vuol dire che la funzione non restituisce nulla oppure fallisce. In pratica il ! esclamativo davanti ad un tipo (!T) significa che o si avrà quel tipo o un errore. Quindi

!T significa o si ha un risultato di tipo T o un errore. Nello specifico si parla di error union type. Esempi vari:

!u8 = o un valore u8, o un errore
!void = o niente (void), o un errore
!?i32 = o un ?i32 (opzionale), o un errore
il ? introduce quindi un valore opzionale ma avremo modo di riparlarne.

try std.fs.File.stdout().writeAll("hello world!\n"); - siamo al core di questo programma. In questa versione di Hello World il programma ci ha detto che l'operazione potrebbe fallire o non restituire nulla. Attraverso try esso è in grado restituire l'eventuale errore verso il runtime che potrà esporlo. Se togliete il try il compilatore si lamenta che non è in grado di gestire l'error-union che abbiamo visto prima.

error: error union is ignored

std come detto è la libreria standard. Essa contiene una infinità di strutture e namespace e uno di questi namespace è fs. Siamo quindi arrivati a
std.fs laddove il punto è l'operatore che ci permette di navigare nelle gerarchie degli elementi del type system di Zig. Il nostro fs sta per file system. All'interno di questo namespace abbiamo diverse strutture ( di cui parleremo in seguito) e una di queste, lo avrete già capito, è File. Siamo quindi arrivati a capire

std.fs.File che, come detto è una struttura che ha un metodo statico ovvero stdout()  che propone in output un elemento (in realtà una istanza di File) su cui applicare la funzione writeAll. A sua volta questa espone una stringa che in Zig, come in molto altri linguaggi, è una sequenza di caratteri all'interno di una coppia di doppi apici.

Come avrete certamente notato, in Zig, come in molti linguaggi si usano le parentesi graffe per racchiudere i blocchi di istruzioni, siano esse una o più. Inoltre è da osservare le singole istruzioni vengono chiuse da un ; (punto e virgola).

Il metodo che abbiamo visto è un metodo pratico che esprime molto bene, se vogliamo, la natura elaborata di questo linguaggio. E' possibile però usare una strada più semplice e lo vediamo nel codice seguente:

Esempio 2.2

const std = @import("std");
pub fn main() !void {
  std.debug.print("Hello, World!\n", .{});
}

Questa seconda via cambia sostanzialmente l'operatore di scrittura che usiamo. Ancora una volta si parte, come indica la prima riga, dal nostro std ma utilizziamo il namespace debug. Questo al suo interno, ha una funzione pronta all'uso che si chiama print e che, nota importante, scrive non sullo standard output ma sullo standard error. In casi semplici come i nostri i due canali di output visivamente coincidono ma in realtà sono due flussi diversi. Questo secondo esempio è molto semplice ed immediato, ma come suggerisce l'uso di quella parolina "debug" non è un sistema da usare in produzione proprio perchè fa uso di canale di output che non quello tipicamente deputato alla visualizzazione del risultato di un programma funzionante. Visto però che è semplice e molto usato per i test lo adopereremo spesso nell'ambito del nostro percorso. Ad ogni modo è interessante notare l'istruzione alla terza riga in particolare quel .{ }. L'istruzione di stampa ha due argomenti: il primo è una stringa il secondo è una ennupla (o tuple che viene indicata proprio con la sequenza .{ } e argomento che sarà trattato a suo tempo) che potrebbe (in questo caso no) contenere degli elementi da inserire in appositi segnaposto. Vediamo un esempio di codice che usa tali segnaposto:

Esempio 2-3

const std = @import("std");
pub fn main() !void {
  std.debug.print("Ciao, {s}!\n", .{"amico"});
}

Potete notare quindi quel {s} con il quale informiamo il compilatore che in quella posizione deve trovare una sequenza di caratteri. Dedicheremo un po' di tempo a quei segnaposto ed al loro contenuto.

Non abbiamo però ancora sviscerato completamente il discorso della stampa. Un terzo metodo parte ancora da std e torniamo un po' al primo punto usando writer invece di writeAll.

Esempio 2.4

const std = @import("std");
pub fn main() !void {
  var stdout_buffer: [1024]u8 = undefined;
  var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
  const stdout = &stdout_writer.interface;
  try stdout.print("Hello, Zig!\n", .{});
  try stdout.flush();
}

Sarà meglio tornare su questo esempio, fornito a puro titolo esemplificativo, quando avremo appreso altri concetti.
Come detto, da qui in avanti useremo il tranquillo std.debug.print.
Prima di lasciarci, due note importanti.
Per prima cosa ricordate che Zig è un linguaggio case-sensitive. Ovvero fa distinzione fra maiuscole e minuscole. Scrivere "pippo" o "Pippo" o "piPPo" equivale a definire 3 entità completamente distinte tra loro.
Seconda cosa, importante per la manutenzione dei vostri programmi, parliamo dei commenti. In breve, vale la seguente tabella:

Tipo Sintassi Note
Commento singola riga // Il più usato
Commento multilinea /* ... */ Supporta annidamento
Commento doc /// Per documentazione