Home Rhyylen
Contatto
 
 
 
Rust Language
Capitolo 17
Tipo union

Forse non il più usato nel linguaggio, il tipo union è anche lui in qualche modo simile ad una struct per alcuni versi e, tra le varie definizioni disponibili mi sento di abbracciare la seguente: è un costrutto che permette di definire una struttura dati in cui diversi tipi di dati possono occupare la stessa posizione in memoria. È simile alle unioni in C e viene utilizzato principalmente proprio per l’interoperabilità con il codice C o per situazioni in cui si vuole gestire manualmente la rappresentazione dei dati in memoria. Questo tipo, come facile capire, definito dall'utente, quindi permette di sovrapporre più dati nello stesso spazio di memoria e pertanto la sua dimensione è data dall'elemento più grande. Questa convivenza non è esente da rischi tanto che il codice che manipola variabili di tipo unione è marcato con la keyword unsafe, che dice tutto e che ci deve far pensare ad un argomento piuttosto avanzato, quale in realtà è. In conclusione, deve essere manipolato con attenzione ed evidentemente solo in situazioni particolari.
La keyword che definisce questo tipo è union e la sintassi è la seguente:

union Identificatore {
campo-1: tipo,
campo-2: tipo,
....
campo-n: tipo,
}

Vediamo un esempio che dimostra anche uno dei rischi che si corrono con questo particolare tipo:

  Esempio 17.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
union MyUnion {
    f1: u32,
    f2: f32,
}

fn main() {
    let mut u = MyUnion { f1: 1 };
    unsafe {
    // Accesso sicuro perché sappiamo che `f1` è stato inizializzato
    println!("f1: {}", u.f1);

    // Modifica del valore tramite `f1` e lettura tramite `f2`
    u.f1 = 123456789;
    println!("f2: {}", u.f2); // Risultato imprevedibile e potenzialmente pericoloso
    }
}

In sostanza nell’esempio, dopo aver inizializzato il campo f1 della union MyUnion, il codice modifica il valore di f1 e poi tenta di leggere f2. Poiché f1 e f2 condividono la stessa posizione in memoria, leggere f2 dopo aver scritto f1 significa interpretare erroneamente i bit di un u32 come se fossero un f32. Questo può portare a risultati imprevedibili e potenzialmente pericolosi, poiché il valore interpretato potrebbe non avere senso come f32 o potrebbe addirittura causare comportamenti indefiniti nel programma. Il compilatore in pratica non sa, all'interno di questo "contenitore" quale membro sia attivo e utilizzabile. Pertanto grande responsabilità è messa sulle spalle del programmatore usando il tipo union e quella parolina, unsafe, già citata ad inizio paragrafo e che è necessaria per accedere ai membri di una unione, lo certifica.
Un'altra limitazione è che le unioni non possono contenere dati appartenenti a tipi che implementino il trait drop, questo perchè il compilatore non saprebbe come deallocatore elementi del genere all'interno di quella "scatola". Quindi ad esempio non potrete infilarci una String, forse con qualche elaborazione tramite ManuallyDrop, come suggerisce il compilatore, ma non ho provato, francamente non so se valga la pena perderci tempo, a questo punto.
Quali sono i punti di forza delle unioni? Sostanzialmente direi l'estrema efficienza. Le union in Rust sono estremamente efficienti in termini di overhead, poiché condividono la memoria tra i diversi campi e non hanno discriminanti o controllo automatico del ciclo di vita dei dati.


Non insisto oltre su questo argomento perchè usufruiremo molto raramente del tipo union che, come detto, è meglio affrontare in momenti in cui la preparazione sarà più avanzata. L'uso di Union è riservato a casi molto specifici.