Ownership e Borrowing: O Coração do Rust
Entendendo as regras que tornam o Garbage Collector obsoleto.
Em linguagens como Python/JS, o Garbage Collector pausa seu programa para limpar o lixo.
Em C/C++, você limpa manualmente (e erra, causando crashes).
Em Rust, o compilador insere os free() para você nos lugares certos. Como? Ownership.
As 3 Leis de Ownership
- Cada valor em Rust tem um Dono (variável).
- Só pode haver um dono por vez.
- Quando o dono sai de escopo, o valor é descartado (Drop).
Move Semantics
let s1 = String::from("Ola");
let s2 = s1;
// println!("{}", s1); // ERRO!
Em C++, s2 = s1 faria uma cópia rasa (shallow copy), e ambos apontariam para a mesma memória. Se ambos derem free, ocorre um double-free.
Em Rust, s1 foi MOVIDO para s2. s1 agora é inválido. O problema do double-free desaparece.
Borrowing: Emprestando sem dar
Se tivesse que mover tudo o tempo todo, seria chato. Podemos emprestar (& Reference).
Shared References (&T)
Podem haver VÁRIAS referências de leitura. Ninguém pode alterar o dado enquanto ele está sendo lido.
let s = String::from("Texto");
let r1 = &s; // Ok
let r2 = &s; // Ok, leitores múltiplos.
Mutable References (&mut T)
Só pode haver UMA referência mutável. E nenhuma outra (nem leitura, nem escrita) ao mesmo tempo.
let mut s = String::from("Texto");
let r1 = &mut s;
// let r2 = &mut s; // ERRO: Data Race evitado na compilação!
// let r3 = &s; // ERRO: Não pode ler enquanto alguém edita.
Isso elimina Data Races matematicamente. Se compila, é thread-safe (na maioria dos casos).
Lifetimes (O Pesadelo dos Iniciantes)
O compilador precisa garantir que uma referência não viva mais que o dado original (Dangling Pointer).
fn main() {
let r;
{
let x = 5;
r = &x;
} // x morre aqui.
// println!("{}", r); // ERRO: r aponta para cadáver de x.
}
Normalmente o compilador infere isso. Quando não consegue, você precisa anotar 'a.