E0106: Lifetime Ausente no Rust

Como resolver o erro E0106 do Rust: especificador de lifetime ausente. Aprenda o que são lifetimes, quando anotá-los e como corrigir este erro do compilador.

E0106: Lifetime Ausente

O erro E0106 ocorre quando o compilador não consegue determinar automaticamente o lifetime (tempo de vida) de uma referência. Isso geralmente acontece em assinaturas de funções e definições de structs onde há ambiguidade sobre quanto tempo uma referência deve ser válida.

A Mensagem de Erro

error[E0106]: missing lifetime specifier
 --> src/main.rs:1:24
  |
1 | fn maior(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 maior<'a>(a: &'a str, b: &'a str) -> &'a str {
  |         ++++     ++          ++           ++

O Que Significa

Todo valor referenciado em Rust tem um lifetime — o período durante o qual a referência é válida. Na maioria dos casos, o compilador infere os lifetimes automaticamente através de regras chamadas lifetime elision rules (regras de elisão).

Porém, quando uma função retorna uma referência e recebe múltiplas referências como parâmetros, o compilador não sabe qual referência de entrada está relacionada à de saída. Ele precisa que você explicite essa relação com anotações de lifetime.

As três regras de elisão são:

  1. Cada parâmetro de referência recebe seu próprio lifetime
  2. Se há exatamente um parâmetro de referência, seu lifetime é atribuído a todas as referências de saída
  3. Se um dos parâmetros é &self ou &mut self, seu lifetime é atribuído às referências de saída

Quando nenhuma regra se aplica, você precisa anotar manualmente.

Código com Erro

Função com dois parâmetros de referência retornando referência:

// ERRO: o compilador não sabe se o retorno vem de `a` ou `b`
fn maior(a: &str, b: &str) -> &str {
    if a.len() > b.len() {
        a
    } else {
        b
    }
}

Struct contendo referência:

// ERRO: struct com referência precisa de lifetime explícito
struct Trecho {
    conteudo: &str,
}

Como Resolver

Solução 1: Adicionar Anotação de Lifetime

Declare um parâmetro de lifetime genérico (convencionalmente 'a) e aplique-o:

fn maior<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a.len() > b.len() {
        a
    } else {
        b
    }
}

fn main() {
    let resultado;
    let string1 = String::from("texto longo");

    {
        let string2 = String::from("xyz");
        resultado = maior(&string1, &string2);
        println!("Maior: {}", resultado);
    }
}

O 'a diz ao compilador: “o retorno vive pelo menos tanto quanto o menor dos lifetimes de a e b”.

Para structs:

struct Trecho<'a> {
    conteudo: &'a str,
}

fn main() {
    let texto = String::from("Olá, Rust Brasil!");
    let trecho = Trecho {
        conteudo: &texto[0..3],
    };
    println!("Trecho: {}", trecho.conteudo);
}

Solução 2: Usar 'static Quando Apropriado

Se a referência aponta para dados que vivem durante toda a execução do programa (como string literals), use 'static:

fn saudacao() -> &'static str {
    "Olá, mundo!"
}

fn main() {
    println!("{}", saudacao());
}

Cuidado: Não use 'static como solução universal. Ele é apropriado apenas para dados realmente estáticos. Forçar 'static em outros cenários levará a mais erros.

Solução 3: Retornar Tipo Owned em Vez de Referência

Às vezes, a solução mais simples é retornar um tipo com ownership em vez de uma referência:

fn maior(a: &str, b: &str) -> String {
    if a.len() > b.len() {
        a.to_string()
    } else {
        b.to_string()
    }
}

fn main() {
    let resultado = maior("Rust", "Go");
    println!("Maior: {}", resultado);
}

Essa abordagem tem custo de alocação, mas elimina completamente a complexidade de lifetimes. Para muitos casos, especialmente quando a string precisa ser modificada ou armazenada, é a solução mais prática.

Lifetimes em Implementações

Quando sua struct tem lifetime, as implementações também precisam declará-lo:

struct Trecho<'a> {
    conteudo: &'a str,
}

impl<'a> Trecho<'a> {
    fn novo(conteudo: &'a str) -> Self {
        Trecho { conteudo }
    }

    fn tamanho(&self) -> usize {
        self.conteudo.len()
    }
}

Dica Prática

Se lifetimes estão deixando seu código muito complexo, considere se você realmente precisa de referências. Em muitos casos, usar tipos com ownership (String em vez de &str, Vec<T> em vez de &[T]) simplifica significativamente o código com custo mínimo de performance.

Veja Também