Salta ai contenuti

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.

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 anni
▶ Provalo tu

Confrontala 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à.

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);
}
▶ Provalo tu

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 anni
▶ Provalo tu

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);
}
▶ Provalo tu

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).

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 }
▶ Provalo tu

#[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.

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? false
quadrato: area 25, quadrato? true
▶ Provalo tu

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 centesimi
Account { owner: "Ana", balance: 75 }
▶ Provalo tu

È 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.

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 è maggiorenne
Chen è maggiorenne
Il più anziano: Chen (52)
▶ Provalo tu

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.

  • Una struct raggruppa campi con un nome in un unico tipo: struct Person { name: String, age: u32 }.
  • Crei un’istanza con Person { name: ..., age: ... }; segnala mut per poter cambiare i campi dopo.
  • Aggiungi comportamento con un blocco impl; &self si riferisce all’istanza su cui è stato chiamato il metodo.
  • Aggiungi #[derive(Debug)] per poter stampare una struct con {:?}.

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