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 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.
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);
}
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
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!
}
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);
}
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
}
}
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()
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!
}
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);
}
}
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("")
}
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
}
Lifetimes em traits também são comuns ao definir abstrações sobre referências:
trait Resumivel<'a> {
fn resumo(&'a self) -> &'a str;
}
Sempre que uma função retorna uma referência derivada de um parâmetro, o compilador precisa saber qual parâmetro “empresta” o dado.
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")
}
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.
Structs que armazenam referências precisam de parâmetros de lifetime; structs que armazenam valores com ownership não precisam.
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.
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)
}
}
'a ajudam o compilador a verificar sua lógica| 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!