Come il borrow checker determina la durata dei `&mut`

Published: (January 15, 2026 at 02:54 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Introduzione

In questo articolo analizzeremo come il borrow checker di Rust determina la durata effettiva di un prestito mutabile (&mut) in presenza delle Non‑Lexical Lifetimes (NLL).

In particolare vedremo perché la durata di un prestito non coincide con la sua durata lessicale e perché chiamate esplicite a drop() non permettono di forzare manualmente la fine di un prestito quando esistono usi successivi nel flusso di controllo.

Problema tecnico

Durante lo studio del capitolo 4.1 – What is Ownership del Rust Book e dell’esercizio 06_move_semantics/move_semantics4.rs dei Rustlings, un programma apparentemente semplice che cerca di creare due prestiti mutabili consecutivi su x fallisce con l’errore E0499.

Il compilatore riporta:

A variable was borrowed as mutable more than once.

Erroneous code example:

let mut i = 0;
let mut x = &mut i;
let mut a = &mut i;
x;
// error: cannot borrow `i` as mutable more than once at a time

Please note that in Rust, you can either have many immutable references, or one mutable reference.

Il codice incriminato è:

#[cfg(test)]
mod tests {
    // TODO: Fix the compiler errors only by reordering the lines in the test.
    // Don't add, change or remove any line.
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        let z = &mut x;
        y.push(42);
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}

Come possiamo vedere, viene tentato un secondo &mut x mentre il primo è ancora (secondariamente) necessario.

Le regole di ownership in Rust impongono di avere una sola referenza mutabile (&mut) attiva su un valore alla volta.

Correzione

La correzione è semplice: basta spostare la seconda creazione del prestito dopo l’ultimo utilizzo del primo.

#[cfg(test)]
mod tests {
    #[test]
    fn move_semantics4() {
        let mut x = Vec::new();
        let y = &mut x;
        y.push(42);
        let z = &mut x;
        z.push(13);
        assert_eq!(x, [42, 13]);
    }
}

Perché funziona

  1. let y = &mut x; – il compilatore registra esiste un prestito mutabile attivo di x chiamato y.
  2. y.push(42); – è l’ultimo uso di y. Dopo questa riga, per il compilatore non esistono più prestiti mutabili attivi su x, quindi let z = &mut x; diventa valido.

Questa dinamica è resa possibile dalle Non‑Lexical Lifetimes (NLL): il prestito mutabile termina al termine dell’ultimo uso, non necessariamente alla fine del blocco.

Domanda

Esiste un modo per dichiarare esplicitamente un lifetime a y affinché il compilatore non utilizzi le NLL ma sia io, tramite il codice, a dirgli fin quando dura y?

Risposta

No. Non si può dichiarare un lifetime esplicito su una variabile locale per forzare il borrow checker a ignorare le NLL.
I parametri di lifetime servono a firmare tipi, funzioni o strutture, non a controllare direttamente la durata lessicale di un binding dentro una funzione.

drop() e la durata di un prestito

Pensando ai lifetimes, potremmo provare a usare drop() per terminare un prestito prima del suo “ultimo uso”.

fn main() {
    let mut x = Vec::new();
    let y = &mut x;
    y.push(42);
    drop(y);
    let z = &mut x;   // ← E0499 qui: il prestito di y è ancora vivo per via dell'uso successivo
    y.push(43);       // ← E0382 qui: uso di y dopo che è stato mosso da drop(y)
    z.push(13);
}

Il compilatore emette due errori distinti:

  • E0499cannot borrow 'x' as mutable more than once at a time
    Il secondo &mut x è dichiarato mentre il primo prestito è ancora considerato vivo.
  • E0382use of moved value 'y'
    y è stato mosso da drop(y) e viene poi usato.

Perché drop() non accorcia un borrow mutabile

  • Le NLL si basano su MIR (Mid‑level Intermediate Representation) e usano un’analisi di liveness per estendere il lifetime di un prestito fino al suo ultimo uso identificato sul flusso di controllo.
  • drop(y) è semplicemente un move: rende il binding y non più utilizzabile, ma non accorcia la regione di borrow se il compilatore rileva usi successivi di y.

In pratica, drop(y) termina un prestito solo se il binding non viene più usato dopo il drop. Se c’è un uso successivo, le NLL estendono retroattivamente la vita del prestito fino a quell’uso, rendendo il drop irrilevante ai fini del borrow checking.

Principio pratico

Assicurati che l’ultimo uso del primo &mut avvenga prima di creare un nuovo prestito mutabile. Se hai bisogno di “liberare” anticipatamente il prestito, devi strutturare il codice in modo che non ci siano usi successivi, oppure ricorrere a blocchi ({ … }) per limitare la portata lessicale del binding.

Con le NLL il compilatore fa già gran parte di questo lavoro per te, ma è importante capire che drop() non è una bacchetta magica per abbreviare i lifetimes.


È possibile gestire la creazione del successivo &mut usando ambiti espliciti, estrazione in funzioni o riprogettazione dei dati per evitare aliasing mutabile non necessario.

E tu? Hai mai incontrato un caso simile?

Fammelo sapere nei commenti!

A presto,

Mirko

Back to Blog

Related posts

Read more »