Se você vem do Python e está começando com Rust, provavelmente já se deparou com mensagens de erro misteriosas sobre “ownership”, “borrowing” ou “value moved here”. Calma, você não está sozinho nessa jornada. Vamos explorar juntos esse conceito que é o coração da segurança e performance do Rust.
Imagine que você está organizando uma festa na sua casa — sua memória RAM é a casa. Em Python, é como se você tivesse um assistente (o coletor de lixo) que fica anotando quem veio, quem saiu e limpando tudo depois. Já em Rust, você é o anfitrião responsável: precisa garantir que cada convidado (dado) tenha um lugar certo, sem bagunça e sem invasores.
Esse sistema permite ao Rust:
Direto do livro oficial do Rust, as regras são:
Parece simples, mas as implicações são profundas. Vamos entender com exemplos.
Em Rust, toda variável é dona do valor que armazena:
fn main() {
let s = String::from("hello"); // s é a dona da String "hello"
println!("{}", s); // podemos usar s aqui
} // s sai de escopo e a memória é liberada automaticamente
Compare com Python, onde os objetos têm vida própria:
def main():
s = "hello" # s referencia o objeto string
print(s) # mas não é sua "dona" exclusiva
# O coletor de lixo decide quando liberar a memória
Em Rust, você não pode ter múltiplos donos para o mesmo valor. Note também que tipos simples como inteiros e booleanos implementam o trait Copy e não sofrem move — são copiados automaticamente:
fn main() {
// Tipos que implementam Copy são copiados, não movidos
let x = 5;
let y = x;
println!("{} {}", x, y); // Ok: i32 implementa Copy
// String NÃO implementa Copy: sofre move
let s1 = String::from("hello");
let s2 = s1; // O valor é MOVIDO de s1 para s2
// println!("{}", s1); // ERRO! s1 não é mais dona de nada
println!("{}", s2); // Ok, s2 é a nova dona
}
Em Python, isso seria perfeitamente válido em ambos os casos:
s1 = "hello"
s2 = s1 # Ambos referenciam o mesmo objeto
print(s1) # Funciona
print(s2) # Funciona
Rust automaticamente libera memória quando o dono sai de escopo. Quando isso acontece, o compilador chama implicitamente o trait Drop do valor — o equivalente a um destrutor que você pode até customizar para seus próprios tipos:
fn main() {
let outer = String::from("outer");
{
// Novo escopo
let inner = String::from("inner");
println!("{}", outer); // Ok
println!("{}", inner); // Ok
} // inner é dropada aqui; Drop é chamado automaticamente
// println!("{}", inner); // ERRO! inner não existe mais aqui
println!("{}", outer); // outer ainda é válida
}
Em Python, não temos essa garantia imediata:
def main():
s = "hello"
print(s)
# O objeto pode ou não ser liberado imediatamente
# Depende do coletor de lixo
Agora que vimos os princípios, vamos aos problemas que você provavelmente enfrentará.
let s1 = String::from("texto");
let s2 = s1;
println!("{}", s1); // ERRO: value borrowed here after move
Solução: Use referências (&) ou clone explicitamente:
let s1 = String::from("texto");
let s2 = &s1; // Empresta s1 sem mover
println!("{} {}", s1, s2); // Ok
Este erro é sutil: o problema não é só tentar modificar uma referência imutável, mas tentar criar uma referência mutável enquanto referências imutáveis ainda estão ativas:
let mut s = String::from("hello");
let r = &s; // Referência imutável ativa
s.push_str(", world"); // ERRO: cannot borrow `s` as mutable
// because it is also borrowed as immutable
println!("{}", r);
Solução: Use referências mutáveis somente quando não há referências imutáveis ativas:
let mut s = String::from("hello");
// Não há referências imutáveis ativas aqui
let r = &mut s;
r.push_str(", world"); // Ok
println!("{}", r);
fn cria_referencia() -> &String {
let s = String::from("hello");
&s // ERRO: s será liberada ao final da função!
}
Solução: Retorne o valor diretamente, transferindo ownership para quem chamou:
fn cria_string() -> String {
let s = String::from("hello");
s // Transfere ownership para o caller
}
Vamos ver um exemplo completo que mostra ownership na prática, comparando Rust e Python:
fn main() {
// Passamos ownership para a função; original não pode ser usada depois
let original = String::from("Rust é seguro");
let comprimento = calcula_comprimento(original);
println!("Comprimento: {}", comprimento);
// println!("{}", original); // ERRO: original foi movida
// Com borrowing, original permanece válida
let original2 = String::from("Rust é eficiente");
let comprimento2 = calcula_comprimento_emprestado(&original2);
println!("Original2 ainda válida: {}", original2);
println!("Comprimento: {}", comprimento2);
// Clone cria uma cópia real e independente
let copia = original2.clone();
let comprimento3 = calcula_comprimento(copia);
println!("Original2 ainda válida: {}", original2);
println!("Comprimento da cópia: {}", comprimento3);
}
// Recebe ownership da String; ela é liberada ao final
fn calcula_comprimento(s: String) -> usize {
s.len()
}
// Recebe uma referência; usa &str para ser mais idiomático
fn calcula_comprimento_emprestado(s: &str) -> usize {
s.len()
}
def main():
original = "Python é dinâmico"
# Em Python, passamos referências, não ownership
comprimento = calcula_comprimento(original)
# Original ainda está acessível
print(f"Original: {original}")
# Não precisamos nos preocupar com borrowing ou moving
original2 = "Python é flexível"
comprimento2 = calcula_comprimento(original2)
print(f"Original2 ainda válida: {original2}")
def calcula_comprimento(s):
return len(s) # Não afetamos o caller
O sistema de ownership se aplica igualmente a structs e outros tipos compostos. Sempre que uma struct contém um campo como String, ela também não implementa Copy — portanto sofre move na atribuição:
struct Item {
nome: String,
}
fn main() {
let item = Item { nome: String::from("Exemplo") };
let item2 = item; // Move ocorre aqui
// println!("{}", item.nome); // ERRO: item foi movido
println!("{}", item2.nome); // Ok
}
| Característica | Python | Rust |
|---|---|---|
| Modelo | Coleta de lixo (GC) | Ownership system |
| Performance | Overhead do GC | Sem overhead de runtime |
| Segurança | Possíveis referências inválidas | Garantido em tempo de compilação |
| Controle | Automático | Manual (com garantias) |
| Concorrência | GIL pode ser limitante | Sem GIL, seguro por design |
| Operação | Python | Rust |
|---|---|---|
| Atribuição (=) | Cria nova referência | Move ou copia (Copy types) |
| Cópia profunda | copy.deepcopy() | .clone() |
| Passagem para função | Por referência | Move ou borrow (&) |
Pythonistas muitas vezes esperam que a atribuição crie cópias independentes. Em Rust, isso não acontece automaticamente para tipos alocados no heap. Se você precisa de uma cópia real, use clone() — mas use com consciência, pois ele aloca nova memória:
let s1 = String::from("texto");
let s2 = s1.clone(); // Cópia real, não só da referência
println!("s1 = {}, s2 = {}", s1, s2); // Ambos válidos
Em Python, isso seria equivalente a:
import copy
s1 = "texto"
s2 = copy.deepcopy(s1) # Para strings é desnecessário, mas ilustra a ideia
Para fixar o conceito, tente resolver o seguinte problema:
O código abaixo não compila. Modifique apenas a assinatura de exibe_tamanho para que nome permaneça válido após a chamada da função:
fn main() {
let nome = String::from("Rustacean");
exibe_tamanho(nome);
println!("{}", nome); // Como fazer isso funcionar?
}
fn exibe_tamanho(s: String) {
println!("Tamanho: {}", s.len());
}
Dica: a solução está na diferença entre mover e emprestar.
&) permitem “emprestar” valores sem transferir ownershipClone() cria cópias reais quando necessárioDominar ownership é o primeiro passo para escrever código Rust seguro e eficiente. No próximo post, exploraremos o conceito de borrowing e lifetimes em profundidade — incluindo como o compilador rastreia o tempo de vida das referências para garantir que nunca apontem para memória inválida.
Para se aprofundar ainda mais com exemplos práticos e exercícios, confira o livro Desbravando Rust em https://desbravandorust.com.br.