Pular para o conteúdo

Erro E0106 do Rust: missing lifetime specifier (falta o lifetime)

O erro E0106 quer dizer: você escreveu uma referência (&) no tipo de retorno de uma função ou em uma struct, e o Rust não consegue saber de onde vem esse empréstimo. Não é um problema na sua lógica. O compilador só precisa de mais uma palavra sua, ou de uma pequena mudança de plano. Quase todo mundo encontra esse erro na primeira semana de Rust, e a correção costuma ser uma única linha. (Esta página faz parte de Erros do compilador Rust explicados.)

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 {
| ++++ ++ ++ ++

Em português: “falta um especificador de lifetime”. A ajuda diz que o tipo de retorno contém um valor emprestado, mas a assinatura não diz se ele vem de a ou de b, e sugere adicionar um lifetime com nome.

O programa que causa o erro:

fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("apple", "fig"));
}

Uma referência é um empréstimo: ela aponta para dados que pertencem a outra pessoa. Sempre que uma função retorna uma referência, o Rust precisa saber de quem são os dados para onde ela aponta, para conferir se esses dados continuam vivos enquanto quem chamou usa o resultado.

Olhe as três marcas embaixo da linha 1. O compilador sublinha a e b com ---- e o tipo de retorno com ^. Ele está dizendo: “você retorna um &str emprestado, e ele pode vir de a ou de b. De qual?” Ele não vai chutar.

Pense em um livro de biblioteca. Se um amigo te entrega um livro e diz “é emprestado”, você quer saber de quem, para saber quando ele precisa voltar. Um lifetime (tempo de vida) é essa etiqueta no empréstimo.

O Rust garante que uma referência nunca vive mais do que os dados para onde ela aponta (nada de “ponteiros pendurados”). Para conferir isso em uma chamada de função, o compilador olha só a assinatura da função, não o corpo. Então a própria assinatura precisa dizer de onde vem a referência retornada.

Na maior parte do tempo você nunca escreve lifetimes, porque o Rust os preenche sozinho com três regras de elisão simples:

  1. Cada parâmetro que é uma referência ganha o seu próprio lifetime.
  2. Se existe exatamente um parâmetro que é referência, a referência retornada é emprestada dele.
  3. Se for um método com &self ou &mut self, a referência retornada é emprestada de self.

O E0106 aparece quando nenhuma dessas regras dá uma resposta: dois ou mais parâmetros que são referências (e nenhum self), nenhum parâmetro que seja referência, ou uma referência guardada em uma struct. A lição de Lifetimes aprofunda isso, e a de Borrowing explica as próprias referências.

Escolha esta quando a função realmente retorna um pedaço de uma das entradas. 'a é só um nome. Escrevê-lo nas entradas e na saída diz “o resultado vive tanto quanto essas entradas”:

// 'a: o &str retornado é emprestado de a ou de b, então vive tanto quanto os dois.
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("apple");
let second = String::from("fig");
println!("Palavra mais longa: {}", longer(&first, &second));
}

Saída:

Palavra mais longa: apple
▶ Experimente você mesmo

(As palavras ficam em inglês de propósito: a função compara o tamanho em bytes, e letras com acento ocupam 2 bytes.)

Se o resultado sempre vem de um só parâmetro, coloque 'a só nesse parâmetro: fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. A sugestão do compilador coloca 'a em tudo, o que funciona, mas é mais restritivo do que o necessário.

Escolha esta quando a função monta um texto novo, ou quando você simplesmente não quer prender quem chamou a um empréstimo. Uma String própria pertence a quem chamou, então não há nada para rastrear:

fn greeting(name: &str, lang: &str) -> String {
if lang == "es" {
format!("Hola, {name}!")
} else {
format!("Hello, {name}!")
}
}
fn main() {
println!("{}", greeting("Ana", "es"));
println!("{}", greeting("Sam", "en"));
}

Saída:

Hola, Ana!
Hello, Sam!
▶ Experimente você mesmo

Isso custa uma pequena alocação de memória. Para a maioria dos programas, tudo bem. Não se sinta mal por isso.

Correção 3: Guarde dados próprios na sua struct

Seção intitulada “Correção 3: Guarde dados próprios na sua struct”

Um campo de struct como title: &str dá o mesmo erro:

error[E0106]: missing lifetime specifier
--> src/main.rs:2:12
|
2 | title: &str,
| ^ expected named lifetime parameter

O compilador espera um lifetime com nome no campo title.

Para structs, a melhor correção quase sempre é ser dono dos dados: use String em vez de &str, e Vec<T> em vez de &[T]. Assim a struct pode viver o quanto você quiser:

#[derive(Debug)]
struct Book {
title: String, // própria, não &str
pages: u32,
}
fn main() {
let book = Book { title: String::from("Dune"), pages: 412 };
println!("{} ({} páginas)", book.title, book.pages);
}

Saída:

Dune (412 páginas)
▶ Experimente você mesmo

Só dê um lifetime a uma struct quando ela for uma visão de curta duração de dados que vivem em outro lugar, como um parser olhando um texto grande. Aí você escreve <'a> na struct e no campo:

struct Excerpt<'a> {
text: &'a str, // emprestado do romance abaixo
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first };
println!("Trecho: {}", excerpt.text);
}

Saída:

Trecho: Call me Ishmael
▶ Experimente você mesmo

Quando uma função não tem nenhum parâmetro que seja referência, o erro diz que “não há nenhum valor de onde emprestar”:

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 você só retorna literais de string como "OK", escreva &'static str. Os literais ficam gravados dentro do seu programa, então vivem durante toda a execução:

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
fn main() {
println!("200 significa {}", status_text(200));
println!("404 significa {}", status_text(404));
}

Saída:

200 significa OK
404 significa Not Found
▶ Experimente você mesmo

Use 'static só para literais e constantes de verdade. Se o texto é montado durante a execução (com format!, ou lido de um arquivo), 'static não ajuda: retorne uma String (Correção 2).

  • Funções auxiliares com duas entradas, como fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Correção: ligue a saída só a map com 'a (Correção 1).
  • Structs que guardam texto, como um User { name: &str } montado a partir de uma entrada lida. Correção: use campos String (Correção 3).
  • Funções que retornam mensagens fixas, como fn error_label() -> &str. Correção: &'static str (Correção 4).

Os métodos são a boa notícia: fn name(&self) -> &str não precisa de lifetime nenhum, graças à regra 3.

Teste o que aprendeu

1. Por que fn first_word(s: &str) -> &str compila sem nenhum lifetime?

2. Sua struct Config { path: &str } dá E0106. Qual costuma ser a melhor correção?

3. Uma função monta uma mensagem com format! e a retorna. Qual deve ser o tipo de retorno?

Pontuação: 0 / 3