Rust è un linguaggio di programmazione moderno che combina prestazioni elevate con sicurezza della memoria. Uno dei concetti fondamentali in Rust è quello dei struct, che permettono di creare tipi di dati personalizzati per organizzare e gestire informazioni complesse. In questo articolo, esploreremo cosa sono gli struct, come si dichiarano, come si utilizzano e alcune delle loro caratteristiche avanzate.

1. Cosa sono gli Struct?

Gli struct (abbreviazione di “structure”) sono tipi di dati personalizzati che permettono di raggruppare più valori sotto un unico nome. Ogni valore all’interno di uno struct è chiamato campo, e ogni campo ha un nome e un tipo. Gli struct sono simili alle classi in altri linguaggi, ma in Rust sono più leggeri e non supportano l’ereditarietà.

1.1. Dichiarazione di uno Struct

Per dichiarare uno struct, si utilizza la parola chiave struct seguita dal nome dello struct e dai suoi campi. Ecco un esempio:

struct Persona {
    nome: String,
    eta: u8,
    attivo: bool,
}

In questo esempio, abbiamo definito uno struct chiamato Persona con tre campi: nome (di tipo String), eta (di tipo u8) e attivo (di tipo bool).

1.2. Creazione di un’Istanza di Struct

Per creare un’istanza di uno struct, si utilizza la sintassi seguente:

let persona1 = Persona {
    nome: String::from("Alice"),
    eta: 30,
    attivo: true,
};

In questo caso, persona1 è un’istanza dello struct Persona con i valori specificati per ciascun campo.

1.3. Accesso ai Campi di uno Struct

Per accedere ai campi di uno struct, si utilizza la notazione del punto (.):

println!("Nome: {}", persona1.nome);
println!("Età: {}", persona1.eta);
println!("Attivo: {}", persona1.attivo);

Output:

Nome: Alice
Età: 30
Attivo: true

2. Struct Mutabili

Per modificare i campi di uno struct, l’istanza deve essere dichiarata come mutabile:

let mut persona1 = Persona {
    nome: String::from("Alice"),
    eta: 30,
    attivo: true,
};

persona1.eta = 31; // Modifica del campo "eta"
persona1.attivo = false; // Modifica del campo "attivo"

Se l’istanza non è dichiarata come mutabile, non sarà possibile modificare i suoi campi.

3. Struct con Campi Opzionali

In Rust, non esiste un concetto di “campi opzionali” direttamente negli struct. Tuttavia, è possibile utilizzare il tipo Option<T> per rappresentare campi che possono essere assenti:

struct Persona {
    nome: String,
    eta: u8,
    indirizzo: Option<String>,
}

let persona1 = Persona {
    nome: String::from("Bob"),
    eta: 25,
    indirizzo: Some(String::from("Via Roma 123")),
};

let persona2 = Persona {
    nome: String::from("Charlie"),
    eta: 22,
    indirizzo: None,
};

In questo esempio, il campo indirizzo è opzionale e può contenere un valore di tipo String o None.

4. Metodi e Funzioni Associate

Gli struct in Rust possono avere metodi e funzioni associate, che sono definiti all’interno di un blocco impl.

4.1. Definizione di Metodi

I metodi sono funzioni che operano su un’istanza di uno struct. Il primo parametro di un metodo è sempre &self (riferimento immutabile) o &mut self (riferimento mutabile). Ecco un esempio:

impl Persona {
    fn saluta(&self) {
        println!("Ciao, mi chiamo {}!", self.nome);
    }

    fn compie_anni(&mut self) {
        self.eta += 1;
        println!("Ora ho {} anni!", self.eta);
    }
}

let mut persona1 = Persona {
    nome: String::from("Alice"),
    eta: 30,
    attivo: true,
};

persona1.saluta(); // Output: Ciao, mi chiamo Alice!
persona1.compie_anni(); // Output: Ora ho 31 anni!

4.2. Funzioni Associate

Le funzioni associate sono simili ai metodi, ma non prendono self come parametro. Sono spesso utilizzate come costruttori. Ecco un esempio:

impl Persona {
    fn nuova(nome: String, eta: u8) -> Persona {
        Persona {
            nome,
            eta,
            attivo: true,
        }
    }
}

let persona2 = Persona::nuova(String::from("Bob"), 25);

In questo caso, nuova è una funzione associata che crea una nuova istanza di Persona.

5. Struct Tuple

Rust supporta anche gli struct tuple, che sono simili agli struct tradizionali ma senza nomi per i campi. I campi sono identificati solo dalla loro posizione. Ecco un esempio:

struct Colore(u8, u8, u8);

let rosso = Colore(255, 0, 0);
println!("Rosso: {}, {}, {}", rosso.0, rosso.1, rosso.2);

Output:

Rosso: 255, 0, 0

6. Struct Unitari

Gli struct unitari sono struct senza campi. Sono utili per implementare tratti o per creare tipi marker. Ecco un esempio:

struct Marker;

let m = Marker;

7. Pattern Matching con Struct

Rust supporta il pattern matching sugli struct, che permette di estrarre i valori dei campi in modo conciso. Ecco un esempio:

let persona1 = Persona {
    nome: String::from("Alice"),
    eta: 30,
    attivo: true,
};

match persona1 {
    Persona { nome, eta, attivo } => {
        println!("Nome: {}, Età: {}, Attivo: {}", nome, eta, attivo);
    }
}

Output:

Nome: Alice, Età: 30, Attivo: true

8. Conclusioni

Gli struct sono uno strumento potente in Rust per organizzare e gestire dati complessi. Con questa guida, hai imparato come dichiarare, creare e utilizzare struct, oltre a esplorare funzionalità avanzate come metodi, funzioni associate e pattern matching. Gli struct sono fondamentali per scrivere codice Rust modulare e sicuro, e ora sei pronto per utilizzarli nei tuoi progetti!