🔍 Imagine que a memória do computador é como uma grande biblioteca. Em Python, temos um bibliotecário (o Garbage Collector) que fica organizando os livros (objetos) para nós. Já em Rust, somos nós mesmos os bibliotecários, mas com regras muito claras sobre quem pode pegar qual livro e quando devolver. Vamos desvendar essas diferenças fundamentais!
Na programação, gerenciamento de memória é como administrar os recursos de uma cidade:
Vamos explorar como essas abordagens impactam desempenho, segurança e produtividade.
Python usa um sistema automático de gerenciamento de memória baseado em:
# Exemplo de alocação em Python
lista = [1, 2, 3] # Aloca memória para a lista
outra_ref = lista # Cria nova referência para o mesmo objeto
del lista # Remove uma referência, mas o objeto persiste
# O GC eventualmente limpará a memória quando não houver mais referências
Problema clássico em Python:
# Ciclo de referências que o GC precisa resolver
class Node:
def __init__(self, value):
self.value = value
self.next = None
a = Node(1)
b = Node(2)
a.next = b
b.next = a # Ciclo criado!
# Mesmo sem referências externas, o GC precisa intervir
del a
del b
Vantagens do modelo Python:
Desvantagens:
Rust introduz um sistema único de propriedade (ownership) com 3 regras fundamentais:
// Exemplo básico de ownership
fn main() {
let s = String::from("hello"); // s é o dono da String
faz_algo(s); // Transfere a propriedade para a função
// println!("{}", s); // ERRO! s não é mais dono do valor
}
fn faz_algo(texto: String) { // texto agora é o novo dono
println!("{}", texto);
} // texto sai de escopo e a memória é liberada
Borrowing (empréstimo): Quando não queremos transferir a propriedade:
fn main() {
let s = String::from("hello");
pega_emprestado(&s); // Empresta uma referência imutável
println!("{}", s); // OK! s ainda é o dono
}
fn pega_emprestado(texto: &String) { // Recebe uma referência
println!("{}", texto);
}
Vamos implementar um exemplo prático em ambas as linguagens:
Cenário: Processar um grande conjunto de dados sem desperdiçar memória
def processar_dados():
dados = [x for x in range(1_000_000)] # Aloca lista grande
# Modifica os dados
resultados = [x * 2 for x in dados]
# Liberação da memória depende do GC
return resultados
resultado = processar_dados()
# Quando 'resultado' não for mais usado, o GC liberará a memória
fn processar_dados() -> Vec<i32> {
let dados: Vec<i32> = (1..1_000_000).collect(); // Aloca vetor
// Consome 'dados' e produz novo vetor
let resultados: Vec<i32> = dados.into_iter().map(|x| x * 2).collect();
// 'dados' foi movido e liberado automaticamente
resultados
} // 'resultados' será liberado quando sair do escopo do chamador
fn main() {
let resultado = processar_dados();
// Memória gerenciada de forma previsível
}
Principais diferenças: | Característica | Python | Rust | |———————-|————————-|————————–| | Modelo | Garbage Collected | Ownership based | | Controle | Automático | Manual (com segurança) | | Previsibilidade | Baixa | Alta | | Overhead | Alto (GC) | Praticamente zero | | Segurança | Possíveis vazamentos | Garantida em tempo de compilação |
let mut s = String::from("hello");
let ref1 = &s;
let ref2 = &mut s; // ERRO! Não pode ter referência mutável enquanto há imutáveis
let s1 = String::from("texto grande");
let s2 = s1; // Em Rust isso MOVE, não copia (a menos que implemente Clone)
let r;
{
let x = 5;
r = &x; // ERRO! x não vive o suficiente
}
Vamos implementar um sistema mais complexo em ambas as linguagens:
class DataProcessor:
def __init__(self):
self.cache = {}
def process(self, data):
# Verifica cache primeiro
if id(data) in self.cache:
return self.cache[id(data)]
# Processamento pesado
result = [x ** 2 for x in data]
# Guarda no cache (cuidado com memory leak!)
self.cache[id(data)] = result
return result
# Uso
processor = DataProcessor()
dados = list(range(100000))
resultado = processor.process(dados)
del dados # Importante para liberar memória
use std::collections::HashMap;
struct DataProcessor {
cache: HashMap<usize, Vec<i32>>,
}
impl DataProcessor {
fn new() -> Self {
DataProcessor {
cache: HashMap::new(),
}
}
fn process(&mut self, data: &[i32]) -> &Vec<i32> {
let data_ptr = data.as_ptr() as usize;
// Verifica cache primeiro (usando entry API para eficiência)
self.cache.entry(data_ptr)
.or_insert_with(|| data.iter().map(|&x| x.pow(2)).collect())
}
}
fn main() {
let mut processor = DataProcessor::new();
let dados: Vec<i32> = (0..100000).collect();
let resultado = processor.process(&dados);
// Memória gerenciada automaticamente com garantias
}
Análise:
Prefira Python quando:
Prefira Rust quando:
✔ Python usa Garbage Collector automático enquanto Rust usa ownership ✔ Contagem de referências vs verificação em tempo de compilação ✔ Rust oferece melhor desempenho e previsibilidade na gestão de memória ✔ Python prioriza produtividade em detrimento do controle fino ✔ O modelo de ownership previne bugs comuns como dangling pointers ✔ Borrowing em Rust permite segurança sem sacrificar flexibilidade
Este post é apenas um aperitivo do conteúdo completo que oferecemos no livro “Desbravando Rust”! Se você quer:
Visite nosso site em desbravandorust.com.br e garanta seu exemplar hoje mesmo! 🚀
Próximo post: “Concorrência em Rust vs Python: Threads, async e além”. Não perca!