Salta ai contenuti

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.)

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"));
}

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.

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:

  1. Ogni parametro riferimento riceve un lifetime tutto suo.
  2. Se c’è esattamente un parametro riferimento, il riferimento restituito è preso in prestito da lì.
  3. Se è un metodo con &self o &mut self, il riferimento restituito è preso in prestito da self.

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.

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: banana
▶ Provalo tu

Se 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!
▶ Provalo tu

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 parameter

Per 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)
▶ Provalo tu

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 Ismaele
▶ Provalo tu

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 from

Se 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 OK
404 significa Not Found
▶ Provalo tu

Usa '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).

  • Funzioni di supporto con due input, come fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Soluzione: lega l’output solo a map con 'a (Soluzione 1).
  • Struct che contengono testo, come uno User { name: &str } costruito a partire da un input letto. Soluzione: usa campi String (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.

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