DEV Community

Cover image for [pt-BR] Lifetimes em Rust: Desvendando o Tempo de Vida das Variáveis para Pythonistas
Jose Luis da Cruz Junior
Jose Luis da Cruz Junior

Posted on Originally published at desbravandorust.com.br

[pt-BR] Lifetimes em Rust: Desvendando o Tempo de Vida das Variáveis para Pythonistas

Por @zejuniortdr em Abr 16, 2026

Introdução: O Tempo de Vida das Variáveis

Em Python, você nunca precisa se preocupar explicitamente com quanto tempo uma variável vai existir na memória. O garbage collector cuida de tudo automaticamente. Mas em Rust, entender lifetimes (tempos de vida) é essencial para escrever código seguro e eficiente sem um coletor de lixo.

Imagine que você está organizando uma festa:

  • Em Python: Os convidados (objetos) podem chegar e sair quando quiserem, e um funcionário (garbage collector) limpa tudo depois
  • Em Rust: Você precisa planejar exatamente quando cada convidado chega e sai, garantindo que ninguém fique esperando por quem já foi embora

Em Rust, o compilador verifica que todos os convidados saíram antes de desmontar a mesa onde estavam — prevenindo o famoso erro de use-after-free sem precisar de um garbage collector.

Seção 1: O Problema que Lifetimes Resolvem

Referências e Borrowing

Em Rust, o sistema de ownership garante segurança na memória, mas e quando queremos compartilhar acesso sem transferir posse?

fn main() {
    let texto = String::from("Olá Rust");  // Dono do valor

    // Referência imutável (borrowing)
    let referencia = &texto;  // "Eu só quero ler, prometo!"

    println!("{}", referencia);
}
Enter fullscreen mode Exit fullscreen mode

O exemplo Python equivalente parece inofensivo nesse caso simples, mas Python não impede padrões realmente perigosos:

# Caso simples: funciona em ambas as linguagens
def main():
    texto = "Olá Python"
    referencia = texto  # Na verdade é a mesma referência
    print(referencia)

# Caso perigoso: Python permite, Rust proibiria em tempo de compilação
def obter_referencia():
    x = [1, 2, 3]
    return lambda: x[0]  # Closure captura referência a x local

f = obter_referencia()
print(f())  # Funciona em Python (GC mantém x vivo), seria erro em Rust
Enter fullscreen mode Exit fullscreen mode

O problema surge quando o dono desaparece antes da referência:

// ERRO DE COMPILAÇÃO:
// expected named lifetime parameter
// O compilador não aceita &String sem lifetime; com 'static seria
// necessário que `s` vivesse para sempre — impossível para uma local!
fn cria_referencia() -> &String {
    let s = String::from("ops!");
    &s  // ERRO: s será destruída no fim da função!
}
Enter fullscreen mode Exit fullscreen mode

Seção 2: Anotação de Lifetimes na Prática

Sintaxe Básica

Lifetimes são anotadas com apóstrofos: 'a. Veja um exemplo funcional:

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

fn main() {
    let string1 = String::from("abc");
    let string2 = "xyz";

    let resultado = maior(&string1, string2);
    println!("A maior string é {}", resultado);
}
Enter fullscreen mode Exit fullscreen mode

Lifetimes em Estruturas

Quando uma struct contém referências:

struct Exemplo<'a> {
    parte: &'a str,
}

impl<'a> Exemplo<'a> {
    fn novo(texto: &'a str) -> Exemplo<'a> {
        Exemplo { parte: texto }
    }

    fn pegar_parte(&self) -> &str {
        self.parte
    }
}
Enter fullscreen mode Exit fullscreen mode

Seção 3: Python vs Rust - Gerenciamento de Memória

Como Python Gerencia Tempo de Vida

Python usa contagem de referências e garbage collector:

def exemplo():
    x = [1, 2, 3]  # Objeto criado
    y = x          # Referência compartilhada
    return y       # x é destruído? Depende!

resultado = exemplo()
print(resultado)  # Funciona - GC mantém vivo

# Referência circular: Python lida com isso, mas pode causar vazamentos
import gc
class No:
    def __init__(self):
        self.proximo = None

a = No()
b = No()
a.proximo = b  # a referencia b
b.proximo = a  # b referencia a — ciclo!
del a, b       # GC eventualmente coleta; em Rust, ciclos assim exigem Rc<RefCell<>>
gc.collect()
Enter fullscreen mode Exit fullscreen mode

Rust: Controle Explícito

Em Rust, você precisa garantir manualmente que as referências sejam válidas:

fn exemplo() -> &Vec<i32> {  // ERRO: falta lifetime
    let x = vec![1, 2, 3];
    &x  // x morre aqui!
}
Enter fullscreen mode Exit fullscreen mode

Caso Prático: Construindo um Parser Seguro

Vamos implementar um parser simples que extrai tags de um texto:

struct TagParser<'a> {
    texto: &'a str,
    tag: &'a str,
}

impl<'a> TagParser<'a> {
    fn novo(texto: &'a str, tag: &'a str) -> Self {
        TagParser { texto, tag }
    }

    fn encontrar(&self) -> Option<&'a str> {
        let inicio = format!("<{}>", self.tag);
        let fim = format!("</{}>", self.tag);

        // Usando `restante` para evitar índices inválidos:
        // buscar `fim` a partir do início do texto retornaria
        // o índice errado caso haja tags aninhadas ou repetidas.
        self.texto.find(&inicio).and_then(|start| {
            let restante = &self.texto[start + inicio.len()..];
            restante.find(&fim).map(|end| {
                &restante[..end]
            })
        })
    }
}

fn main() {
    let html = "<div>Conteúdo importante</div>";
    let parser = TagParser::novo(html, "div");

    if let Some(conteudo) = parser.encontrar() {
        println!("Conteúdo: {}", conteudo);
    }
}
Enter fullscreen mode Exit fullscreen mode

Lifetime Elision e Múltiplos Lifetimes

O compilador Rust aplica lifetime elision rules para omitir anotações óbvias, tornando o código menos verboso:

// Com elision (o compilador infere os lifetimes automaticamente)
fn primeira_palavra(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

// Equivalente explícito
fn primeira_palavra_explicita<'a>(s: &'a str) -> &'a str {
    s.split_whitespace().next().unwrap_or("")
}
Enter fullscreen mode Exit fullscreen mode

Quando há múltiplos lifetimes, você controla relações entre entradas e saída:

// O retorno vive pelo menos enquanto `x` — independentemente de `y`
fn escolhe_primeiro<'a, 'b>(x: &'a str, _y: &'b str) -> &'a str {
    x
}
Enter fullscreen mode Exit fullscreen mode

Lifetimes em traits também são comuns ao definir abstrações sobre referências:

trait Resumivel<'a> {
    fn resumo(&'a self) -> &'a str;
}
Enter fullscreen mode Exit fullscreen mode

Erros Comuns para Pythonistas

1. Esquecer de anotar lifetimes em funções que retornam referências

Sempre que uma função retorna uma referência derivada de um parâmetro, o compilador precisa saber qual parâmetro "empresta" o dado.

2. Tentar retornar referência para valor local

Este é o erro mais frequente para quem vem do Python — retornar um ponteiro para algo que vai ser destruído ao sair do escopo da função:

// ERRO: `s` não vive o suficiente
fn problema_comum() -> &str {
    let s = String::from("ops");
    &s  // s é destruída aqui; o ponteiro ficaria inválido
}

// CORREÇÃO: retornar o valor com ownership, não uma referência
fn solucao() -> String {
    String::from("ops")
}
Enter fullscreen mode Exit fullscreen mode

3. Subestimar o escopo de variáveis emprestadas

Em Rust, o borrow checker rastreia exatamente onde cada referência pode ser usada — usar uma referência fora do escopo do dono é erro de compilação.

4. Confundir ownership com borrowing em estruturas

Structs que armazenam referências precisam de parâmetros de lifetime; structs que armazenam valores com ownership não precisam.

5. Ignorar lifetime elision

O compilador infere lifetimes simples automaticamente, mas casos com múltiplas referências ou structs exigem anotação explícita. Quando em dúvida, anote.

6. Não usar lifetimes em enums com referências

Enums também podem conter referências e precisam declarar seus lifetimes, assim como structs:

// Sem lifetime: erro de compilação se a variante carregar uma referência
enum Resultado<'a> {
    Ok(&'a str),    // referência com lifetime vinculado ao enum
    Erro(String),   // String com ownership — não precisa de lifetime
}

fn processar<'a>(texto: &'a str) -> Resultado<'a> {
    if texto.is_empty() {
        Resultado::Erro(String::from("texto vazio"))
    } else {
        Resultado::Ok(texto)
    }
}
Enter fullscreen mode Exit fullscreen mode

O Que Aprendemos

  • Lifetimes garantem que referências sejam sempre válidas
  • Em Rust, você controla explicitamente o tempo de vida das variáveis
  • Anotações 'a ajudam o compilador a verificar sua lógica
  • Python abstrai isso com garbage collector, mas com custo de performance e sem garantias em tempo de compilação
  • Estruturas com referências precisam declarar seus lifetimes
  • Lifetime elision simplifica casos comuns; múltiplos lifetimes lidam com relações complexas

Python vs Rust: resumo da abordagem de memória

Aspecto Python (GC) Rust (Lifetimes)
Gerenciamento Automático via garbage collector Explícito via borrow checker
Segurança Verificada em tempo de execução Verificada em tempo de compilação
Conveniência ✅ Alta — sem anotações ⚠️ Média — curva de aprendizado inicial
Previsibilidade ⚠️ Pausas de GC possíveis ✅ Alta — sem GC, sem pausas
Performance ⚠️ Overhead do GC ✅ Zero-cost abstractions
Erros de memória Detectados em runtime (ou silencosos) Impossíveis — garantidos em compilação

Quer dominar Rust como um verdadeiro desbravador? Adquira já o livro completo em desbravandorust.com.br e transforme-se em um expert na linguagem que está revolucionando a programação de sistemas!

Top comments (0)