ZIG - Il tipo Optional


In Zig, un optional rappresenta un valore che può essere presente oppure assente in qualche modo può essere visto come l'equivalente di null in altri linguaggi, ma in modo type-safe. In pratica rappresentano un valore di un dato tipo T oppure null.

La definizione formale è molto semplice, basta anteporre un ? davanti al tipo di dato che vorremmo fosse contenuto nella variabile o nella costante che andamo a definire:

Quindi un tipo ?T è un union speciale con due stati:
-- null
-- un valore di tipo T

Il valore null è un singleton del tipo Null. Va evidenziato che il tipo ?T è distinto da T.
NB: gli Optional non sono da confondere con gli Error Union di cui parleremo un apposita sezione.

Come esempi pratici possiamo ad esempio scrivere cose tipo:

var x: ?i32 = 42; // optional con valore
var y: ?i32 = null; // optional vuoto

Vediamo un piccolo programma di prova:

Esempio 19.1

const std = @import("std");

pub fn main() !void {
    const x: ?i32 = 10;
    if (x) |v| {
        std.debug.print("Valore: {}\n", .{v});
    } else {
        std.debug.print("Null!\n", .{});
    }
}

In cui gestiamo in due casi in cui può trovarsi la costante x ovvero contenere un valore di tipo i32 oppure essere null.

Usare if-else è la strada più comune per estrarre e gestire il valore da un Optional. Un altro modo fa uso della keyword orelse:

Esempio 19.2

const std = @import("std");

pub fn main() !void {
    const a: ?i32 = null;
    const b = a orelse 30; // b = 30
    std.debug.print("valore  {}\n", .{b});
    const c: ?i32 = 20;
    const d = c orelse 40; // d = 20
    std.debug.print("valore  {}", .{d});
}

Con questo output:

valore 30
valore 20

In pratica una scrittura tipo:

const y = x orelse 0;

equivale a:

if x == null
usa 0
else
usa il valore di x

Come si può notare non ci sono complessi costruttori o altro, anzi, uno degli aspetti che rende gli Optional di Zig particolarmente eleganti è che si integrano direttamente nella sintassi del linguaggio (if, while, orelse, .?) invece di essere una libreria o un tipo speciale da manipolare con metodi. Questo permette di scrivere codice molto conciso, ma con la sicurezza di un controllo esplicito dell'assenza di valore.

Una particolarità è il cosiddetto unwrap forzato che genera un panic se il valore è null. Da usare solo in casi partciolari e se si è sicuri che il valore sia diverso da null, in pratica qualcuno dice che serve da allarme per situazioni imprevedibili:

const a: ?i32 = null;
const b = a orelse 42; // b = 42

Chiudiamo con una tabella riassuntiva che richiama anche l'argomento dei puntatori:

Concetto Significato Esempio
Optional Valore T o null ?i32
Unwrap forzato Panic se null x.?
Default Sostituisce null x orelse 0
Optional pointer Puntatore può essere null ?*T
Pointer to optional Puntatore valido, contenuto opzionale *?T