Lezioni del modulo (2/2)
Riferimenti e Borrowing
In Rust, passare continuamente la proprietà (ownership) di una variabile a una funzione e farsela restituire può essere molto scomodo. Per risolvere questo problema, Rust utilizza i riferimenti (references).
Creare un riferimento a un valore si chiama Borrowing (prendere in prestito).
Riferimenti Immutabili
Un riferimento si dichiara anteponendo il simbolo & al tipo o alla variabile. Per impostazione predefinita, i riferimenti sono immutabili: permettono di leggere il valore ma non di modificarlo.
fn main() {
let s1 = String::from("hello");
// Passiamo un riferimento a s1, non l'ownership
let len = calculate_length(&s1);
// s1 è ancora utilizzabile qui!
println!("La lunghezza di '{}' è {}.", s1, len);
}
fn calculate_length(s: &String) -> usize { // s è un riferimento a una String
s.len()
} // Qui s esce dallo scope, ma poichè non possiede il valore, non succede nulla
Riferimenti Mutabili
Se hai bisogno di modificare un valore preso in prestito, devi usare un riferimento mutabile tramite &mut. La variabile originale deve anche essere dichiarata mutabile con mut:
fn main() {
let mut s = String::from("hello");
// Passiamo un riferimento mutabile
change(&mut s);
println!("{}", s); // Stampa "hello, world"
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
Le Regole d'Oro del Borrowing
Per prevenire corruzioni di memoria e corse ai dati (data races) a tempo di compilazione, Rust impone due regole fondamentali:
- Puoi avere un qualsiasi numero di riferimenti immutabili (
&T) a un valore nello stesso momento. - OPPURE puoi avere esattamente un solo riferimento mutabile (
&mut T) a un valore alla volta.
Non puoi assolutamente mescolare riferimenti immutabili e mutabili per lo stesso valore nello stesso scope:
let mut s = String::from("hello");
let r1 = &s; // Valido
let r2 = &s; // Valido
// let r3 = &mut s; // ERRORE DI COMPILAZIONE! Non puoi creare &mut s se s è già presa in prestito come immutabile
Lo Scope dei Riferimenti e Non-Lexical Lifetimes (NLL)
In passato, lo scope di un riferimento durava obbligatoriamente fino alla fine del blocco in cui veniva creato. Oggi il compilatore di Rust è più intelligente grazie alle Non-Lexical Lifetimes (NLL): lo scope di un riferimento termina all'ultima riga in cui viene utilizzato, non necessariamente a fine blocco.
Ciò rende valido il seguente codice:
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{} e {}", r1, r2); // Ultimo uso di r1 e r2. I riferimenti immutabili scadono qui!
let r3 = &mut s; // Valido! Nessun riferimento immutabile è attivo a questo punto
Prova tu
Passa un riferimento immutabile di s1 alla funzione calculate_length usando il simbolo &.
Mostra suggerimento
Sostituisci `/* TODO \_/`con`&s1` per passare un riferimento immutabile alla stringa.
Soluzione disponibile dopo 3 tentativi
Rendi la variabile s mutabile (let mut s) e passa un riferimento mutabile (&mut s) alla funzione change per permetterle di modificare la stringa.
Mostra suggerimento
Usa `let mut s`al posto di`let s`e chiama`change(&mut s);`.
Soluzione disponibile dopo 3 tentativi
Dichiara una variabile s contenente String::from("Rust"). Crea due riferimenti immutabili distinti r1 e r2 a s, ed infine stampa i due riferimenti separati da uno spazio usando la macro println!.
Mostra suggerimento
Assegna `let r1 = &s;` e `let r2 = &s;` per ottenere due riferimenti immutabili, poi stampali con `println!("{} {}", r1, r2);`.
Soluzione disponibile dopo 3 tentativi