Errore Rust E0106: missing lifetime specifier (lifetime mancante)
L’errore E0106 significa: hai scritto un riferimento (&) nel tipo di ritorno di una funzione o in una struct, e Rust non riesce a capire da che cosa è preso in prestito. Non è un errore nella tua logica. Il compilatore ha solo bisogno di una parola in più da parte tua, o di un piccolo cambio di programma. Quasi tutti incontrano questo errore nella prima settimana, e di solito la soluzione è una riga sola. (Questa pagina fa parte di Errori del compilatore Rust spiegati.)
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”error[E0106]: missing lifetime specifier --> src/main.rs:1:32 |1 | fn longer(a: &str, b: &str) -> &str { | ---- ---- ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`help: consider introducing a named lifetime parameter |1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { | ++++ ++ ++ ++Il compilatore ti dice: «manca lo specificatore di lifetime». Spiega che il tipo di ritorno contiene un valore preso in prestito, ma la firma non dice se è preso da a o da b, e ti suggerisce di introdurre un parametro di lifetime con un nome.
Il programma che lo provoca:
fn longer(a: &str, b: &str) -> &str { if a.len() >= b.len() { a } else { b }}
fn main() { println!("{}", longer("apple", "fig"));}Cosa significa
Sezione intitolata “Cosa significa”Un riferimento è un prestito: punta a dati che possiede qualcun altro. Ogni volta che una funzione restituisce un riferimento, Rust deve sapere di chi sono i dati a cui punta, così può controllare che restino vivi finché chi chiama usa il risultato.
Guarda i tre segni sotto la riga 1. Il compilatore sottolinea a e b con ---- e il tipo di ritorno con ^. Ti sta dicendo: «restituisci un &str preso in prestito, e potrebbe venire da a oppure da b. Da quale?». Non tira a indovinare.
Pensa a un libro della biblioteca. Se un amico ti passa un libro e ti dice «è in prestito», vuoi sapere da chi, così sai quando va restituito. Un lifetime è quell’etichetta sul prestito.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”Rust garantisce che un riferimento non possa mai vivere più a lungo dei dati a cui punta (niente «puntatori pendenti», dangling pointer). Per verificarlo in una chiamata di funzione, il compilatore guarda solo la firma della funzione, non il suo corpo. Quindi è la firma stessa a dover dire da dove viene il riferimento restituito.
Nella maggior parte dei casi i lifetime non li scrivi mai, perché Rust li riempie da solo con tre semplici regole di elisione:
- Ogni parametro riferimento riceve un lifetime tutto suo.
- Se c’è esattamente un parametro riferimento, il riferimento restituito è preso in prestito da lì.
- Se è un metodo con
&selfo&mut self, il riferimento restituito è preso in prestito daself.
E0106 compare quando nessuna di queste regole dà una risposta: due o più parametri riferimento (e nessun self), nessun parametro riferimento, oppure un riferimento memorizzato in una struct. La lezione Lifetime lo spiega a fondo, e Prestito (borrowing) spiega i riferimenti stessi.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: aggiungi un lifetime con un nome ('a)
Sezione intitolata “Soluzione 1: aggiungi un lifetime con un nome ('a)”Sceglila quando la funzione restituisce davvero un pezzo di uno dei suoi input. 'a è solo un nome. Scriverlo sugli input e sull’output significa «il risultato vive quanto questi input»:
// 'a: il &str restituito è preso in prestito da a o da b, quindi vive quanto entrambi.fn longer<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b }}
fn main() { let first = String::from("banana"); let second = String::from("fico"); println!("Parola più lunga: {}", longer(&first, &second));}Output:
Parola più lunga: bananaSe il risultato viene sempre e solo da un parametro, metti 'a solo su quel parametro: fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. Il suggerimento del compilatore mette 'a dappertutto, il che funziona ma è più restrittivo del necessario.
Soluzione 2: restituisci invece una String posseduta
Sezione intitolata “Soluzione 2: restituisci invece una String posseduta”Sceglila quando la funzione costruisce un testo nuovo, o quando semplicemente non vuoi legare chi chiama a un prestito. Una String posseduta appartiene a chi chiama, quindi non c’è niente da tracciare:
fn greeting(name: &str, lang: &str) -> String { if lang == "it" { format!("Ciao, {name}!") } else { format!("Hello, {name}!") }}
fn main() { println!("{}", greeting("Ana", "it")); println!("{}", greeting("Sam", "en"));}Output:
Ciao, Ana!Hello, Sam!Costa una piccola allocazione. Per la maggior parte dei programmi va benissimo. Non sentirti in colpa.
Soluzione 3: memorizza dati posseduti nella struct
Sezione intitolata “Soluzione 3: memorizza dati posseduti nella struct”Un campo di struct come title: &str dà lo stesso errore:
error[E0106]: missing lifetime specifier --> src/main.rs:2:12 |2 | title: &str, | ^ expected named lifetime parameterPer le struct, la soluzione migliore è quasi sempre possedere i dati: usa String invece di &str, e Vec<T> invece di &[T]. Così la struct può vivere quanto vuoi:
#[derive(Debug)]struct Book { title: String, // posseduta, non &str pages: u32,}
fn main() { let book = Book { title: String::from("Dune"), pages: 412 }; println!("{} ({} pagine)", book.title, book.pages);}Output:
Dune (412 pagine)Dai un lifetime a una struct solo quando è una vista di breve durata su dati che vivono da un’altra parte, come un parser che guarda un testo lungo. In quel caso scrivi <'a> sulla struct e sul campo:
struct Excerpt<'a> { text: &'a str, // preso in prestito dal romanzo qui sotto}
fn main() { let novel = String::from("Chiamatemi Ismaele. Alcuni anni fa..."); let first = novel.split('.').next().unwrap(); let excerpt = Excerpt { text: first }; println!("Estratto: {}", excerpt.text);}Output:
Estratto: Chiamatemi IsmaeleSoluzione 4: usa 'static per i letterali stringa
Sezione intitolata “Soluzione 4: usa 'static per i letterali stringa”Quando una funzione non ha nessun parametro riferimento, l’errore dice «there is no value for it to be borrowed from» (non c’è nessun valore da cui prenderlo in prestito):
error[E0106]: missing lifetime specifier --> src/main.rs:1:22 |1 | fn default_name() -> &str { | ^ expected named lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed fromSe restituisci solo letterali stringa come "OK", scrivi &'static str. I letterali sono incorporati nel tuo programma, quindi vivono per tutta l’esecuzione:
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", _ => "Sconosciuto", }}
fn main() { println!("200 significa {}", status_text(200)); println!("404 significa {}", status_text(404));}Output:
200 significa OK404 significa Not FoundUsa 'static solo per veri letterali e costanti. Se il testo viene costruito durante l’esecuzione (con format!, o letto da un file), 'static non ti aiuta: restituisci una String (Soluzione 2).
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”- Funzioni di supporto con due input, come
fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Soluzione: lega l’output solo amapcon'a(Soluzione 1). - Struct che contengono testo, come uno
User { name: &str }costruito a partire da un input letto. Soluzione: usa campiString(Soluzione 3). - Funzioni che restituiscono messaggi fissi, come
fn error_label() -> &str. Soluzione:&'static str(Soluzione 4).
La buona notizia sono i metodi: fn name(&self) -> &str non ha bisogno di nessun lifetime, grazie alla regola 3.
Errori correlati
Sezione intitolata “Errori correlati”- E0382: borrow of moved value: un altro errore di ownership, sull’uso di un valore dopo averlo ceduto.
- E0502: mutable borrow while immutably borrowed: il borrow checker al lavoro una volta che i tuoi lifetime sono a posto.
- E0499: two mutable borrows: due
&mutagli stessi dati nello stesso momento. - E0308: mismatched types: per esempio, restituire una
Stringda una funzione che dichiara&str. E0515: cannot return reference to local variable: hai provato la Soluzione 1 su dati creati dentro la funzione. Restituisci invece unaStringposseduta (Soluzione 2).- Tutti gli errori del compilatore spiegati
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Perché fn first_word(s: &str) -> &str compila senza nessun lifetime?
2. La tua struct Config { path: &str } dà E0106. Qual è di solito la soluzione migliore?
3. Una funzione costruisce un messaggio con format! e lo restituisce. Quale tipo di ritorno dovrebbe avere?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0106 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore.
- Validating References with Lifetimes — The Rust Book (in inglese): tutta la storia dei lifetime, compreso l’esempio
longeste le struct con riferimenti. - Lifetime elision — The Rust Reference (in inglese): le regole esatte che Rust usa per riempire i lifetime al posto tuo.
- Elision — Rust by Example (in inglese): brevi esempi di quando puoi omettere i lifetime.
- Lifetimes — Rust by Example (in inglese): esempi eseguibili di lifetime espliciti in funzioni, metodi e struct.