Salta ai contenuti

Ownership in Rust

L’ownership (possesso) è l’idea che rende Rust Rust. È il modo in cui il linguaggio garantisce la sicurezza della memoria senza un garbage collector e senza che tu debba liberare la memoria a mano. All’inizio sembra strana, ma il concetto di base è semplice. Costruiamolo un passo alla volta.

Quando un programma gira, salva i dati in memoria. Prima o poi quella memoria va ripulita, altrimenti il programma perde memoria (memory leak) e alla fine si blocca. I linguaggi affrontano il problema in due modi comuni:

  • A mano (C/C++): sei tu a liberare la memoria. Se te ne dimentichi, hai un leak. Se la liberi troppo presto, ottieni crash e falle di sicurezza.
  • Garbage collector (Java, Go, Python): un processo in background fa pulizia al posto tuo. Comodo, ma costa in prestazioni e prevedibilità.

Rust sceglie una terza strada: decide in fase di compilazione esattamente quando ogni valore va ripulito, seguendo alcune regole sull’ownership. Niente memoria da liberare a mano, niente garbage collector.

Questo è tutto il sistema. Il resto viene di conseguenza:

  1. Ogni valore ha un solo proprietario (una variabile che lo «possiede»).
  2. Può esserci un solo proprietario alla volta.
  3. Quando il proprietario esce dal suo scope (ambito), il valore viene ripulito automaticamente.

Vediamo ogni regola in azione.

Regola 3: i valori vengono rilasciati alla fine dello scope

Sezione intitolata “Regola 3: i valori vengono rilasciati alla fine dello scope”

Uno scope è la zona tra { e }. Quando lo scope di una variabile finisce, Rust libera automaticamente il suo valore: non scrivi mai free().

fn main() {
{
let message = String::from("ciao"); // qui message è valida
println!("{message}");
} // <- lo scope finisce, `message` viene ripulita automaticamente
// println!("{message}"); // ❌ errore: qui message non esiste più
}
▶ Provalo tu

Non hai dovuto liberare niente: Rust l’ha fatto nel momento in cui message è uscita dallo scope.

Qui le cose si fanno interessanti. Guarda cosa succede quando assegni una variabile a un’altra:

fn main() {
let s1 = String::from("ciao");
let s2 = s1; // l'ownership SI SPOSTA da s1 a s2
println!("{s2}"); // ✅ funziona
// println!("{s1}"); // ❌ errore: s1 è stata spostata
}
▶ Provalo tu

Quando scrivi let s2 = s1, Rust non copia la stringa: sposta l’ownership da s1 a s2. Siccome la regola 2 dice che può esserci un solo proprietario, ora s1 non è più valida. Se provi a usarla, ottieni:

error[E0382]: borrow of moved value: `s1`

Il compilatore ti dice, in inglese, che stai usando (prendendo in prestito) un valore già spostato.

Se ti serve davvero una seconda copia indipendente, chiama .clone():

fn main() {
let s1 = String::from("ciao");
let s2 = s1.clone(); // fa una copia completa
println!("s1 = {s1}, s2 = {s2}"); // ✅ ora funzionano entrambe
}
▶ Provalo tu

clone() è esplicito apposta: copiare può costare caro, quindi Rust vuole che tu lo chieda.

Forse ti chiedi perché questo codice non dà errore:

fn main() {
let x = 5;
let y = x;
println!("x = {x}, y = {y}"); // ✅ funzionano entrambe
}

I valori piccoli e di dimensione fissa, come interi, numeri in virgola mobile, booleani e char, costano pochissimo da copiare, quindi Rust li copia invece di spostarli. Da principiante, la regola pratica è:

  • Valori semplici (numeri, bool, char) → copiati. L’originale resta valido.
  • Dati posseduti (come String) → spostati. L’originale non è più valido.

Anche passare un valore a una funzione lo sposta, proprio come assegnarlo a una variabile:

fn take_ownership(s: String) {
println!("Ora possiedo: {s}");
} // qui s viene rilasciata
fn main() {
let text = String::from("ciao");
take_ownership(text);
// println!("{text}"); // ❌ errore: text è stata spostata nella funzione
}

È proprio per questo che esiste il prossimo argomento, il prestito (borrowing). Il prestito permette a una funzione di usare un valore senza prenderne l’ownership, così puoi continuare a usarlo dopo.

Prova prima da solo ogni esercizio, nel Rust Playground o in un progetto locale. Gli errori di compilazione fanno parte del gioco: leggili, di solito indicano proprio lo spostamento.

Questo programma non compila. Correggilo in modo che stampi bozza -> bozza v2, senza togliere nessuna delle due variabili.

fn main() {
let original = String::from("bozza");
let mut edited = original;
edited.push_str(" v2");
println!("{original} -> {edited}");
}
error[E0382]: borrow of moved value: `original`
--> src/main.rs:5:16
|
2 | let original = String::from("bozza");
| -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait
3 | let mut edited = original;
| -------- value moved here
4 | edited.push_str(" v2");
5 | println!("{original} -> {edited}");
| ^^^^^^^^ value borrowed here after move

Suggerimento: ti servono due stringhe indipendenti, così modificarne una non tocca l’altra.

Mostra la soluzione
fn main() {
let original = String::from("bozza");
let mut edited = original.clone(); // una copia completa e separata
edited.push_str(" v2");
println!("{original} -> {edited}");
}

Output:

bozza -> bozza v2
▶ Provalo tu

let mut edited = original; sposta la stringa, quindi original non è più valida. .clone() crea una seconda stringa con la sua memoria. Ora push_str modifica solo edited, e original dice ancora bozza.

Scrivi una funzione add_signature che prende l’ownership di una String, aggiunge " -- Ferris" alla fine e restituisce la stringa. Chiamala da main e stampa il risultato.

Suggerimento: una funzione può restituire il valore che ha ricevuto. Scrivi -> String nella firma e prendi il parametro come mut text: String, così hai il permesso di modificarlo.

Mostra la soluzione
fn add_signature(mut text: String) -> String {
text.push_str(" -- Ferris");
text // l'ownership torna fuori, al chiamante
}
fn main() {
let letter = String::from("A presto!");
let letter = add_signature(letter);
println!("{letter}");
}

Output:

A presto! -- Ferris
▶ Provalo tu

L’ownership entra nella funzione con la chiamata ed esce di nuovo con il valore di ritorno. La raccogliamo in una nuova variabile letter (che oscura la vecchia, cioè fa shadowing). Funziona, ma passare valori avanti e indietro di continuo diventa faticoso. È proprio il problema che risolve il prestito.

Questo programma non compila. Correggilo in modo che stampi Ana è entrata nella lista e poi ["Ana"].

fn main() {
let mut names = Vec::new();
let name = String::from("Ana");
names.push(name);
println!("{name} è entrata nella lista");
println!("{:?}", names);
}
error[E0382]: borrow of moved value: `name`
--> src/main.rs:5:16
|
3 | let name = String::from("Ana");
| ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait
4 | names.push(name);
| ---- value moved here
5 | println!("{name} è entrata nella lista");
| ^^^^ value borrowed here after move

Suggerimento: push prende l’ownership, proprio come una chiamata di funzione. Chi possiede la stringa dopo la riga 4? Usa name prima dello spostamento, oppure chiedila al nuovo proprietario.

Mostra la soluzione
fn main() {
let mut names = Vec::new();
let name = String::from("Ana");
println!("{name} è entrata nella lista"); // usala finché è ancora nostra
names.push(name); // ora il proprietario è il vettore
println!("{:?}", names);
}

Output:

Ana è entrata nella lista
["Ana"]
▶ Provalo tu

Dopo names.push(name), il proprietario è il vettore e name non si può più usare. Stampare prima è una soluzione. Un’altra è tenere l’ordine originale e stampare names[0], perché il vettore ha ancora la stringa. Anche clonare funzionerebbe, ma qui non serve copiare niente.

  • L’ownership permette a Rust di gestire la memoria in modo sicuro, senza garbage collector.
  • Tre regole: un proprietario per valore, un proprietario alla volta, il valore viene rilasciato quando il proprietario esce dallo scope.
  • Assegnare dati posseduti (come una String) li sposta: l’originale non è più valido.
  • Usa .clone() per una copia indipendente; i tipi semplici (numeri, bool, char) vengono copiati automaticamente.
  • Anche passare un valore a una funzione lo sposta, e questo ci porta al prestito.

Verifica veloce

1. Quanti proprietari può avere un valore nello stesso momento, in Rust?

2. Quale metodo chiami per fare una copia indipendente di una String invece di spostarla?

3. Quale di questi tipi viene copiato automaticamente, invece che spostato, quando lo assegni a un'altra variabile?

4. Cosa succede quando passi una String a una funzione che la prende per valore (non per riferimento)?

5. Secondo questa pagina, quando Rust ripulisce (rilascia) automaticamente un valore?

Punteggio: 0 / 5