|
Prima di proseguire un breve recap relativo a borrowing e ownership, colonne portanti del nostro Rust in modo da proseguire nel nostro percorso avendo ben chiari questi concetti. Come abbiamo detto, in questo linguaggio abbiamo alcuni concetti importanti: Ownership (Proprietà): In Rust, ogni valore ha un proprietario singolo. Il proprietario è responsabile per la deallocazione della memoria associata al valore quando non è più necessario. Questo principio aiuta a evitare problemi di doppia deallocazione o memory leak. In sintesi, il sistema di ownership di Rust permette di scrivere codice sicuro e performante, riducendo al minimo gli errori legati alla gestione della memoria potendo anche evitare l'uso di un garbage collector. Borrowing (Prestito): Invece di trasferire la proprietà di un valore quando viene passato a una funzione o assegnato a un'altra variabile, Rust consente di "prendere in prestito" temporaneamente il valore. Quando un valore viene preso in prestito, il proprietario originale continua a detenere la proprietà, ma il prestatore temporaneo può accedere al valore per la sua durata di vita. Questo prestito può essere sia mutabile (mutabile borrowing) che immutabile (immutable borrowing), e può avvenire a livello di riferimento (borrowing di riferimento) o a livello di proprietà (borrowing di proprietà). Il concetto di “borrowing” è fondamentale per garantire la sicurezza e la gestione della memoria. Il borrowing permette di prestare una variabile a un’altra parte del programma senza trasferirne la proprietà. Questo meccanismo assicura che ci siano regole chiare su chi può accedere e modificare i dati, prevenendo problemi come la concorrenza non sicura o la modifica simultanea di dati condivisi. In pratica, Rust utilizza il borrowing per evitare situazioni in cui più parti del programma tentano di modificare gli stessi dati contemporaneamente, il che potrebbe portare a comportamenti imprevedibili o errori. Grazie a queste regole, Rust riesce a garantire un alto livello di sicurezza e affidabilità nel codice. Un aspetto importante del borrowing in Rust è che rispetta le regole di mutabilità e immutabilità. Non è consentito avere contemporaneamente più riferimenti mutabili allo stesso dato, in modo da evitare potenziali condizioni di gara (race conditions) o problemi di sincronizzazione. Questo approccio, insieme alla gestione statica della durata di vita (lifetime) dei riferimenti, aiuta Rust a garantire che i programmi siano liberi da errori di accesso alla memoria, deadlock e altre problematiche comuni nei linguaggi che non dispongono di un sistema di tipi forte come Rust. Come abbiamo detto il borrowing si presenta nelle due forme mutabile o immutabile. Borrowing mutabile: Un riferimento immutabile permette solo la lettura dei dati referenziati e non consente la modifica dei dati stessi. Puoi avere più riferimenti immutabili allo stesso dato contemporaneamente e possono coesistere con altri riferimenti immutabili o un riferimento mutabile. Borrowing immutabile: Un riferimento mutabile consente sia la lettura che la modifica dei dati referenziati, ma non consente di avere contemporaneamente altri riferimenti (mutabili o immutabili) allo stesso dato. Questo evita condizioni di gara (race conditions) e garantisce che ci sia un'unica fonte di modifica per i dati in un dato momento. Vediamo due esempi con queste modalità di borrowing iniziando da quello immutabile:
e ora quello mutabile:
che presenta un errore come sempre illuminante: error[E0499]: cannot borrow `x` as mutable more than once at a time --> r674.rs:5:14 | 4 | let y1 = &mut x; | ------ first mutable borrow occurs here 5 | let y2 = &mut x; // Questo causerà un errore! | ^^^^^^ second mutable borrow occurs here 6 | 7 | *y1 += 5; | -------- first borrow later used here error: aborting due to 1 previous error Tutto questo dovrebbe essere già charo ma sono dell'idea che un riassuntino non faccia mai male. Il concetto di ownership lo ribadiamo invece attraverso il più semplice esempio possibile, a mio avviso:
|