SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI
Definizione e Creazione di Struct
Le strutture (struct) sono simili alle tuple, discusse nella sezione “Il Tipo Tuple”, nel senso che entrambe contengono più valori correlati. Come le tuple, i pezzi di una struct possono essere di tipi diversi. A differenza delle tuple, però, in una struct si nomina ogni pezzo di dati in modo che sia chiaro cosa significano i valori. Aggiungere questi nomi significa che le strutture sono più flessibili delle tuple: non devi fare affidamento sull’ordine dei dati per specificare o accedere ai valori di un’istanza.
Per definire una struct, si utilizza la parola chiave struct
e si dà un nome all’intera struct. Il nome di una struct dovrebbe descrivere il significato dei pezzi di dati che vengono raggruppati insieme. Poi, all’interno delle parentesi graffe, si definiscono i nomi e i tipi dei pezzi di dati, che chiamiamo campi. Ad esempio, la Lista 5-1 mostra una struct che memorizza informazioni su un account utente.
rust
struct Utente {
attivo: bool,
username: String,
email: String,
conteggio_accessi: u64,
}
Per utilizzare una struct dopo averla definita, si crea un’istanza di quella struct specificando valori concreti per ciascun campo. Si crea un’istanza specificando il nome della struct e quindi aggiungendo parentesi graffe contenenti coppie chiave: valore, dove le chiavi sono i nomi dei campi e i valori sono i dati che vogliamo memorizzare in quei campi. Non è necessario specificare i campi nell’ordine in cui li abbiamo dichiarati nella struct. In altre parole, la definizione della struct è come un modello generale per il tipo, e le istanze riempiono quel modello con dati specifici per creare valori del tipo. Ad esempio, possiamo dichiarare un utente particolare come mostrato nella Lista 5-2.
rust
fn main() {
let utente1 = Utente {
attivo: true,
username: String::from("nomedelutente123"),
email: String::from("qualcuno@esempio.com"),
conteggio_accessi: 1,
};
}
Per ottenere un valore specifico da una struct, si usa la notazione del punto. Ad esempio, per accedere all’indirizzo email di questo utente, si usa utente1.email
. Se l’istanza è mutabile, possiamo cambiare un valore usando la notazione del punto e assegnando a un particolare campo. La Lista 5-3 mostra come cambiare il valore nel campo email di un’istanza mutabile di Utente.
rust
fn main() {
let mut utente1 = Utente {
attivo: true,
username: String::from("nomedelutente123"),
email: String::from("qualcuno@esempio.com"),
conteggio_accessi: 1,
};
utente1.email = String::from("unaltro@email.com");
}
Da notare che l’intera istanza deve essere mutabile; Rust non permette di marcare solo certi campi come mutabili. Come per qualsiasi espressione, si può costruire una nuova istanza della struct come ultima espressione nel corpo della funzione per restituire implicitamente quella nuova istanza.
La Lista 5-4 mostra una funzione build_user
che restituisce un’istanza di Utente con l’email e il nome utente dati. Il campo attivo
ottiene il valore di true, e il campo conteggio_accessi
ottiene il valore di 1.
rust
fn build_user(email: String, username: String) -> Utente {
Utente {
attivo: true,
username: username,
email: email,
conteggio_accessi: 1,
}
}
Ha senso dare ai parametri della funzione lo stesso nome dei campi della struct, ma dover ripetere i nomi dei campi e delle variabili email e username è un po’ noioso. Se la struct avesse più campi, ripetere ogni nome sarebbe ancora più fastidioso. Fortunatamente, c’è una comoda scorciatoia!
Utilizzo della Scorciatoia per l’Inizializzazione dei Campi
Poiché i nomi dei parametri e i nomi dei campi della struct sono esattamente gli stessi nella Lista 5-4, possiamo usare la sintassi di inizializzazione dei campi per riscrivere build_user
in modo che si comporti esattamente allo stesso modo ma senza la ripetizione di username ed email, come mostrato nella Lista 5-5.
rust
fn build_user(email: String, username: String) -> Utente {
Utente {
attivo: true,
username,
email,
conteggio_accessi: 1,
}
}
Qui, stiamo creando una nuova istanza della struct Utente, che ha un campo chiamato email. Vogliamo impostare il valore del campo email al valore nel parametro email della funzione build_user
. Poiché il campo email e il parametro email hanno lo stesso nome, dobbiamo solo scrivere email anziché email: email.
Creazione di Istanze da Altre Istanze con la Sintassi di Aggiornamento delle Struct
È spesso utile creare una nuova istanza di una struct che include la maggior parte dei valori da un’altra istanza, ma ne cambia alcuni. Puoi fare ciò utilizzando la sintassi di aggiornamento delle struct.
Innanzitutto, nella Lista 5-6 mostriamo come creare una nuova istanza di User in user2 regolarmente, senza la sintassi di aggiornamento. Impostiamo un nuovo valore per l’email ma altrimenti usiamo gli stessi valori da user1 che abbiamo creato nella Lista 5-2.
rust
fn main() {
// --snip--
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
}
Utilizzando la sintassi di aggiornamento delle struct, possiamo ottenere lo stesso effetto con meno codice, come mostrato nella Lista 5-7. La sintassi ..
specifica che i campi rimanenti non esplicitamente impostati dovrebbero avere lo stesso valore dei campi nell’istanza data.
rust
fn main() {
// --snip--
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
Il codice nella Lista 5-7 crea anche un’istanza in user2 che ha un valore diverso per l’email ma ha gli stessi valori per i campi username, active e sign_in_count da user1. Il ..user1
deve venire per ultimo per specificare che eventuali campi rimanenti dovrebbero ottenere i loro valori dai campi corrispondenti in user1, ma possiamo scegliere di specificare valori per qualsiasi numero di campi in qualsiasi ordine, indipendentemente dall’ordine dei campi nella definizione della struct.
Nota che la sintassi di aggiornamento delle struct usa =
come un’assegnazione; questo perché sposta i dati, proprio come abbiamo visto nella sezione “Variabili e Dati che Interagiscono con Move”. In questo esempio, non possiamo più usare user1 nel suo insieme dopo aver creato user2 perché la Stringa nel campo username di user1 è stata spostata in user2. Se avessimo dato a user2 nuovi valori di String sia per l’email che per l’username, e quindi avessimo usato solo i valori di active e sign_in_count da user1, allora user1 sarebbe ancora valido dopo aver creato user2. Entrambi active e sign_in_count sono tipi che implementano il trait Copy, quindi il comportamento che abbiamo discusso nella sezione “Dati Solo sullo Stack: Copy” si applicherebbe.
Struct Unit-Like Senza Campi
È possibile definire anche delle struct che non hanno alcun campo! Queste vengono chiamate struct unit-like perché si comportano in modo simile a ()
, il tipo unit che abbiamo menzionato nella sezione “Il Tipo Tupla”. Le struct unit-like possono essere utili quando è necessario implementare un trait su un certo tipo ma non si dispone di dati che si desidera memorizzare nel tipo stesso. Discuteremo dei trait nel Capitolo 10. Ecco un esempio di dichiarazione e istanziazione di una struct unit chiamata AlwaysEqual:
rust
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
Per definire AlwaysEqual, utilizziamo la parola chiave struct
, il nome desiderato e poi un punto e virgola. Non c’è bisogno di parentesi graffe o tonde! Poi possiamo ottenere un’istanza di AlwaysEqual nella variabile subject
in modo simile: utilizzando il nome che abbiamo definito, senza parentesi graffe o tonde. Immaginate che successivamente implementeremo un comportamento per questo tipo in modo che ogni istanza di AlwaysEqual sia sempre uguale a ogni istanza di qualsiasi altro tipo, forse per avere un risultato conosciuto per scopi di test. Non avremmo bisogno di alcun dato per implementare tale comportamento! Vedrete nel Capitolo 10 come definire i trait e implementarli su qualsiasi tipo, inclusi i struct unit-like.