In Rust non ci sono costruttori tradizionali come in C++ o Java, ma per creare oggetti di tipo si utilizzano normalmente funzioni associate (spesso chiamate new) e i cosiddetti metodi factory. Questo è legato alla storia del linguaggio, in cui si pone particolare attenzione alla sicurezza e alla chiarezza dell'inizializzazione: solo una funzione scritta e chiamata esplicitamente è responsabile per la corretta inizializzazione di ogni campo della struttura.
Storia della questione
In origine in Rust l'inizializzazione delle strutture consentiva l'assegnazione diretta di tutti i campi (il cosiddetto "struct literal" syntax). Tuttavia, per garantire l'invarianza, nascondere i dettagli e implementare controlli aggiuntivi, si pratica l'uso di metodi factory (impl SomeStruct { fn new(...) -> Self { ... } }) o addirittura la generalizzazione tramite template (builder pattern).
Problema
Le principali sfide consistono nel non consentire oggetti parzialmente inizializzati e rendere impossibile l'uso di strutture con uno stato invalido. Questo è particolarmente critico per strutture complesse (ad esempio, relative a risorse - file, socket, ecc.), dove l'inizializzazione manuale di tutti i campi è soggetta a errori.
Soluzione
In Rust si raccomanda di creare metodi factory che restituiscono un oggetto completamente inizializzato, eseguono validazioni se necessario e nascondono i dettagli dell'istanza.
Esempio di codice:
struct User { username: String, age: u8, } impl User { pub fn new(username: String, age: u8) -> Option<Self> { if age >= 18 { Some(Self { username, age }) } else { None } } } fn main() { let user = User::new("Alice".to_string(), 20); // user: Option<User>, gestire l'errore in modo sicuro }
Caratteristiche chiave:
fn new).È possibile creare campi privati in una struttura in modo che non possano essere creati direttamente istanze al di fuori del modulo?
Sì, se si rendono privati tutti i campi di una struttura e si forniscono solo metodi factory pubblici, la struttura non può essere inizializzata direttamente al di fuori del proprio modulo.
Il metodo factory deve sempre chiamarsi new?
No, è una convenzione, ma non un obbligo. Per diverse strategie di inizializzazione vengono utilizzati nomi come "with_capacity", "from_config", "from_env", e così via.
Possono esistere costruttori privati?
Sì, se una funzione associata è dichiarata come fn new(...) -> Self senza il modificatore pub, non potrà essere chiamata al di fuori di questo modulo. Questo consente, ad esempio, di implementare un singleton, enforce factory o inizializzazione nascosta.
Uso diretto di una struttura con campi aperti, senza metodo factory:
struct Connection { fd: i32, timeout: u64, } let c = Connection { fd: -1, timeout: 10 }; // fd: -1 è invalido per un descrittore!
Vantaggi:
Svantaggi:
Uso di campi privati e metodo factory:
pub struct Connection { fd: i32, timeout: u64, } impl Connection { pub fn new(fd: i32, timeout: u64) -> Option<Self> { if fd >= 0 { Some(Self { fd, timeout }) } else { None } } }
Vantaggi:
Svantaggi: