Este contenido solo está disponible en Portugués.
Aún sin traducción para este idioma.
Gerenciamento de memória no Rust: ownership, borrowing e lifetimes sem mistério
C te dá controle total — e uma arma apontada pro próprio pé. Go resolve com Garbage Collector — e picos de latência imprevisíveis. O Rust encontrou um terceiro caminho: segurança de memória garantida pelo compilador, sem custo em runtime.

O problema que cada linguagem resolve diferente
Toda linguagem precisa responder a uma pergunta fundamental: quando a memória que um valor ocupava pode ser liberada? A resposta define tudo — performance, segurança, previsibilidade.
C / C++ — controle manual
Você aloca e libera. Máxima performance, máxima responsabilidade. Esqueceu um free(): memory leak. Liberou duas vezes: double-free. Acessou depois de liberar: use-after-free. 70% das vulnerabilidades da Microsoft vieram daqui.
Go — Garbage Collector
O runtime decide. Você não gerencia memória — o GC varre e libera o que não está mais em uso. Seguro, mas com custo: pausas periódicas imprevisíveis. O Discord migrou porque o GC causava picos a cada 2 minutos.
Rust — Ownership
O compilador decide. Regras verificadas em tempo de compilação garantem que a memória seja liberada exatamente quando deve — sem GC, sem gerenciamento manual, sem surpresas em runtime.
Ownership: cada valor tem um dono
O sistema de ownership do Rust tem três regras simples. Toda a segurança de memória da linguagem deriva delas.
1
Cada valor tem exatamente um dono
Uma variável é a dona do valor que ela armazena. Não existe valor sem dono.
2
Só pode existir um dono por vez
Quando você atribui um valor a outra variável, a propriedade se transfere — a variável original deixa de ser válida.
3
Quando o dono sai de escopo, o valor é liberado
Sem GC, sem free(). O compilador insere a liberação automaticamente quando a variável deixa de existir.
Na prática, isso fica assim:
let s1 = String::from("blueprint");
let s2 = s1; // ownership se transfere pra s2
println!("{}", s1); // ❌ erro de compilação: s1 não é mais válida
println!("{}", s2); // ✅ s2 é a nova donaEm C ou Go, esse código funcionaria — e você teria dois ponteiros apontando pro mesmo dado na memória. Em Rust, o compilador recusa na hora. Não existe chance de double-free ou uso acidental de um valor já transferido.
Por que String e não &str? Tipos que vivem na stack (como inteiros) são copiados automaticamente — são baratos o suficiente. Tipos que vivem na heap (como String) transferem ownership. Se você quer copiar uma String, precisa chamar .clone() explicitamente — o que deixa o custo da operação visível no código.
Borrowing: usar sem ser dono
Se ownership fosse a única forma de acessar dados, você teria que transferir a propriedade toda vez que passasse algo pra uma função — e receber de volta depois. Inviável.
É aí que entra o borrowing: você empresta uma referência ao valor sem transferir o ownership.
fn tamanho(s: &String) -> usize { // recebe uma referência, não o valor
s.len()
}
let minha_string = String::from("blueprint");
let tam = tamanho(&minha_string); // passa a referência
println!("{} tem {} caracteres", minha_string, tam);
// ✅ minha_string ainda é válida — ownership não foi transferidaReferências mutáveis — e a regra mais importante do borrowing
Você pode emprestar mutabilidade também — mas com uma restrição crucial:
let mut s = String::from("blueprint");
let r1 = &mut s;
let r2 = &mut s; // ❌ erro: não pode ter duas referências mutáveis ao mesmo tempolet mut s = String::from("blueprint");
{
let r1 = &mut s;
r1.push_str(" blog");
} // r1 sai de escopo aqui
let r2 = &mut s; // ✅ agora pode — r1 já não existe
Essa regra elimina data races em tempo de compilação. Em Go ou C, dois threads podendo escrever no mesmo dado ao mesmo tempo é uma bomba-relógio. Em Rust, o compilador simplesmente não deixa isso acontecer.
A regra completa do borrowing: você pode ter qualquer número de referências imutáveis ou exatamente uma referência mutável — nunca os dois ao mesmo tempo. Isso é suficiente pra eliminar toda uma categoria de bugs de concorrência.
Lifetimes: quando o compilador precisa de ajuda
O compilador do Rust consegue inferir a maioria das situações de ownership e borrowing automaticamente. Mas existe um caso onde ele precisa de uma dica explícita: quando uma função retorna uma referência, e o compilador precisa saber de qual dos parâmetros ela vem.
// Sem lifetime — o compilador não sabe de onde vem a referência retornada
fn maior(x: &str, y: &str) -> &str { // ❌ erro de compilação
if x.len() > y.len() { x } else { y }
}// Com lifetime — o compilador sabe que o retorno vive tanto quanto x e y
fn maior<'a>(x: &'a str, y: &'a str) -> &'a str { // ✅
if x.len() > y.len() { x } else { y }
}O 'a é uma anotação de lifetime. Ela não cria nada — só diz ao compilador: "a referência retornada vive pelo mesmo tempo que os dois parâmetros." Com essa informação, ele consegue verificar que você nunca vai retornar uma referência pra algo que já foi liberado.
Lifetimes são o ponto de maior atrito em Rust. A boa notícia: o compilador precisou de anotações explícitas em casos bem mais simples antigamente. Versões modernas do Rust inferem lifetimes na maioria dos casos com as lifetime elision rules — você só precisa anotar quando a situação é genuinamente ambígua.
Comparando na prática: o mesmo bug em três linguagens
Use-after-free é uma das vulnerabilidades mais comuns e perigosas. Veja como cada linguagem lida com ela:
/* C — compila, executa, comportamento indefinido */
char *s = malloc(10);
free(s);
printf("%s", s); // acessa memória liberada — undefined behavior// Go — GC previne liberação prematura, mas com custo em runtime
// O GC garante que s não será liberada enquanto ainda houver referências
s := "blueprint"
fmt.Println(s) // sempre seguro, mas o GC adiciona overhead// Rust — erro em tempo de compilação, zero custo em runtime
let s = String::from("blueprint");
drop(s); // libera explicitamente
println!("{}", s); // ❌ erro de compilação — o compilador recusa
// esse código nunca chega em produçãoEssa é a diferença fundamental: C descobre o problema em produção (quando já causou dano). Go previne em runtime com GC (mas paga o custo de latência). Rust previne em compile time — sem nenhum custo em runtime.
O que o compilador garante pra você
- Ownership — cada valor tem um dono. Quando o dono sai de escopo, a memória é liberada automaticamente.
- Move semantics — transferir um valor invalida a variável original. Double-free é impossível.
- Borrowing — referenciar sem transferir. Imutável: quantas quiser. Mutável: só uma por vez.
- Sem data race — duas referências mutáveis simultâneas não compilam. Concorrência segura por design.
- Lifetimes — o compilador verifica que nenhuma referência sobrevive ao valor que ela aponta. Dangling pointers são impossíveis.
- Zero custo em runtime — tudo isso é verificado em compilação. Em produção, o binário é tão rápido quanto C.



