Ownership: O Fundamento Invisível do Rust
Ownership é o sistema de gerenciamento de memória que diferencia Rust de praticamente todas as outras linguagens de programação. Em vez de usar garbage collection (como Python e Java) ou exigir que você gerencie memória manualmente (como C e C++), Rust enforça regras de propriedade em tempo de compilação. Essas regras garantem segurança de memória sem overhead de runtime, resultando em código que é simultaneamente seguro e rápido.
A ideia central é simples: cada valor em Rust tem um único proprietário. Quando o proprietário sai de escopo, o valor é automaticamente liberado. Compreender isso é compreender 80% dos problemas que você enfrentará no Rust. Não se trata apenas de sintaxe — é um novo paradigma de pensamento sobre dados e sua vida útil.
As Três Regras Imutáveis do Ownership
A Regra Fundamental
Cada valor tem exatamente um proprietário por vez. Quando o proprietário sai de escopo, o valor é descartado.
fn main() {
let s1 = String::from("hello");
let s2 = s1; // propriedade é MOVIDA de s1 para s2
println!("{}", s1); // ERRO: s1 não é mais válido
println!("{}", s2); // OK: s2 agora é o proprietário
}
Quando você atribui s1 a s2, a propriedade se move. Rust invalida s1 para garantir que apenas um proprietário exista. Isso é radicalmente diferente de linguagens que copiam valores automaticamente. Tipos simples como números são copiáveis por padrão, mas tipos complexos como String são movidos por padrão.
Borrowing (Empréstimo)
Às vezes você precisa usar um valor sem transferir propriedade. Rust permite isso através de referências. Uma referência é um "empréstimo" — você pode emprestar um valor, usá-lo, mas a propriedade permanece com o proprietário original.
fn main() {
let s1 = String::from("hello");
let len = calcular_comprimento(&s1); // passa uma REFERÊNCIA
println!("A string '{}' tem comprimento {}", s1, len);
}
fn calcular_comprimento(s: &String) -> usize {
s.len()
} // s sai de escopo, mas não descarta String porque não é proprietário
Existem dois tipos de referências. Referências imutáveis (&T) permitem múltiplos empréstimos simultâneos, mas você não pode modificar o valor. Referências mutáveis (&mut T) permitem modificação, mas apenas uma por vez.
fn main() {
let mut s = String::from("hello");
adicionar_mundo(&mut s); // pasa referência mutável
println!("{}", s); // "hello world"
}
fn adicionar_mundo(s: &mut String) {
s.push_str(" world");
}
A Regra de Empréstimo
Rust impõe uma regra estrita: você não pode ter referências mutáveis e imutáveis simultaneamente no mesmo escopo. Isso previne data races em tempo de compilação.
let mut s = String::from("hello");
let r1 = &s; // OK: referência imutável
let r2 = &s; // OK: outra referência imutável
let r3 = &mut s; // ERRO: não pode emprestar como mutável enquanto há imutáveis
println!("{}, {}, {}", r1, r2, r3);
Move Semantics e Cópia de Dados
Por que Strings se movem, mas números não?
Tipos que implementam o trait Copy (como i32, f64, bool) são copiados implicitamente durante atribuição. Tipos que não implementam Copy (como String, Vec) têm seus dados movidos. A razão é performance: copiar um inteiro é trivial, mas copiar uma string gigante na heap é caro.
fn main() {
let x = 5;
let y = x; // cópia implícita
println!("{}, {}", x, y); // OK: ambos válidos
let s1 = String::from("rust");
let s2 = s1; // MOVE: s1 inválido agora
println!("{}", s1); // ERRO
}
Clone: Cópia Explícita
Se você realmente precisa copiar dados de um tipo não-Copy, use .clone():
fn main() {
let s1 = String::from("rust");
let s2 = s1.clone(); // cópia profunda
println!("{}, {}", s1, s2); // OK: ambas válidas
}
Na Prática: Um Exemplo Real
Imagine uma função que processa um arquivo. Com ownership explícito, você entende exatamente quem é responsável pelo arquivo:
use std::fs::File;
use std::io::{self, Read};
fn main() -> io::Result<()> {
let mut arquivo = File::open("dados.txt")?;
let conteudo = ler_arquivo(arquivo)?;
println!("{}", conteudo);
Ok(())
}
fn ler_arquivo(mut arquivo: File) -> io::Result<String> {
let mut conteudo = String::new();
arquivo.read_to_string(&mut conteudo)?;
Ok(conteudo)
// arquivo é descartado aqui, fechando o arquivo automaticamente
}
Este código é seguro porque: (1) a função ler_arquivo recebe propriedade do arquivo, (2) sabe que é responsável por limpeza e (3) garante que o arquivo seja fechado. Não há vazamentos de memória, deadlocks ou use-after-free. O compilador verifica tudo.
Conclusão
Ownership em Rust resolve o problema clássico da programação: como gerenciar memória segura e eficientemente? Ao enforçar propriedade única e borrowing em tempo de compilação, Rust elimina categorias inteiras de bugs sem sacrificar performance. Os três pontos-chave que você deve internalizar são: (1) cada valor tem um proprietário, (2) você pode emprestar sem transferir propriedade através de referências, e (3) as regras de empréstimo previnem data races. Domine esses conceitos e você dominará Rust.