Struct in Rust
Una struct raggruppa valori collegati in un unico tipo con un nome. Se una tupla è un pacchetto veloce e senza nome, una struct dà un nome a ogni campo, e questo rende molto più facile da leggere il codice che la usa.
Definire e creare una struct
Sezione intitolata “Definire e creare una struct”struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{} ha {} anni", alice.name, alice.age);}Output:
Alice ha 30 anniConfrontala con una tupla ("Alice", 30): con una struct, alice.name e alice.age si spiegano da soli. Non devi ricordarti che il campo 0 è il nome e il campo 1 è l’età.
Rendere mutabile una struct
Sezione intitolata “Rendere mutabile una struct”Come le variabili, le istanze di una struct sono immutabili di default. Aggiungi mut per poter cambiare un campo dopo averla creata:
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{} ora ha {} anni", alice.name, alice.age);}Aggiungere comportamento con i metodi
Sezione intitolata “Aggiungere comportamento con i metodi”Puoi collegare delle funzioni a una struct con un blocco impl. Al suo interno, &self si riferisce all’istanza specifica su cui è stato chiamato il metodo:
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("Ciao, sono {} e ho {} anni", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}Output:
Ciao, sono Alice e ho 30 anniFunzioni in stile costruttore
Sezione intitolata “Funzioni in stile costruttore”Uno schema comune è una funzione new dentro impl che costruisce l’istanza per te, così chi la usa non deve scrivere a mano ogni campo:
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{} ha {} anni", bob.name, bob.age);}Nota age da solo dentro Person { name: name.to_string(), age }: quando una variabile ha esattamente lo stesso nome del campo, puoi evitare di scrivere age: age e scrivere solo age. Rust la chiama field init shorthand (inizializzazione abbreviata dei campi).
Stampare una struct con derive
Sezione intitolata “Stampare una struct con derive”Prova println!("{:?}", alice) su una struct scritta come sopra, e non compilerà: Rust non sa come stampare in modalità debug un tipo personalizzato, a meno che tu non glielo dica. Aggiungi #[derive(Debug)] sopra la struct e lo ottieni gratis:
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}Output:
Person { name: "Alice", age: 30 }#[derive(...)] è il modo in cui Rust genera in automatico funzionalità comuni per un tipo: vedrai aggiungere così continuamente Debug, e più avanti Clone e altri.
Pratica
Sezione intitolata “Pratica”1. I metodi di un rettangolo
Sezione intitolata “1. I metodi di un rettangolo”Crea una struct Rectangle con i campi width e height (u32). Dalle due metodi: area, che restituisce l’area, e is_square, che restituisce true quando i due lati sono uguali. Suggerimento: entrambi i metodi leggono soltanto i campi, quindi ricevono &self.
Mostra la soluzione
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("largo: area {}, quadrato? {}", wide.area(), wide.is_square()); println!("quadrato: area {}, quadrato? {}", square.area(), square.is_square());}Output:
largo: area 24, quadrato? falsequadrato: area 25, quadrato? true2. Un conto in banca
Sezione intitolata “2. Un conto in banca”Crea una struct Account con un owner (String) e un balance in centesimi (u32). Scrivi Account::new("Ana"), che parte con il saldo a 0. Poi deposita 50 e 25 centesimi modificando il campo, e stampa il conto in due modi: con una frase normale e con {:?}. Suggerimento: la variabile deve essere let mut prima che tu possa modificare account.balance, e {:?} ha bisogno di #[derive(Debug)].
Mostra la soluzione
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{} ha {} centesimi", account.owner, account.balance); println!("{:?}", account);}Output:
Ana ha 75 centesimiAccount { owner: "Ana", balance: 75 }È new stessa a impostare balance: 0, quindi chi la usa non può dimenticarselo. È uno dei motivi principali per cui le funzioni costruttore sono così comuni.
3. Una lista di persone
Sezione intitolata “3. Una lista di persone”Metti tre valori Person in un vettore: Ana (34), Ben (16) e Chen (52). Stampa il nome di tutti quelli che hanno 18 anni o più, poi stampa chi è il più anziano. Suggerimento: scorri con for person in &people, e tieni traccia del più anziano trovato finora con let mut oldest = &people[0];.
Mostra la soluzione
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} è maggiorenne", person.name); } if person.age > oldest.age { oldest = person; } }
println!("Il più anziano: {} ({})", oldest.name, oldest.age);}Output:
Ana è maggiorenneChen è maggiorenneIl più anziano: Chen (52)oldest è un riferimento (&Person) dentro il vettore, non una copia, quindi non viene clonato niente. Ogni volta che troviamo qualcuno più anziano, facciamo semplicemente puntare oldest a lui.
Riepilogo
Sezione intitolata “Riepilogo”- Una struct raggruppa campi con un nome in un unico tipo:
struct Person { name: String, age: u32 }. - Crei un’istanza con
Person { name: ..., age: ... }; segnalamutper poter cambiare i campi dopo. - Aggiungi comportamento con un blocco
impl;&selfsi riferisce all’istanza su cui è stato chiamato il metodo. - Aggiungi
#[derive(Debug)]per poter stampare una struct con{:?}.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Qual è il vantaggio principale di una struct rispetto a una tupla?
2. Che cosa ti permette di fare #[derive(Debug)] su una struct?
3. In Person { name: name.to_string(), age }, che cosa fa age da solo?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- Defining and Instantiating Structs — The Rust Book (in inglese): parla anche della sintassi di aggiornamento (
..other), delle tuple struct e delle struct unit. - An Example Program Using Structs — The Rust Book (in inglese): trasforma un programma sui rettangoli da variabili sparse a tupla a struct, così vedi perché le struct aiutano.
- Method Syntax — The Rust Book (in inglese):
&selfe&mut self, metodi con parametri in più e funzioni associate comenew. - Structures — Rust by Example (in inglese): esempi eseguibili di ogni tipo di struct, con un piccolo esercizio alla fine.
- Derive — Rust by Example (in inglese): cosa può generare per te
#[derive(...)], comeDebug,CloneePartialEq.