Capítulo 05: O Sistema de Ownership (Propriedade) e Move Semantics
Capítulo 05: O Sistema de Ownership (Propriedade) e Move Semantics
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual do Mecanismo de Ownership e Move Semantics
flowchart TD
subgraph STACK_MEMORY["Memória Stack (Metadados da Variável)"]
S1["s1: ptr (0x1000), len (5), cap (5)"]
S2["s2: ptr (0x1000), len (5), cap (5)"]
end
subgraph HEAP_MEMORY["Memória Heap (Buffer de Dados)"]
H1["['H', 'e', 'l', 'l', 'o'] em 0x1000"]
end
S1 -->|"let s1 = String::from('Hello')"| H1
S1 -.->|"let s2 = s1 (MOVE SEMANTICS)"| S2
S2 -->|"s2 assume a posse exclusiva da Heap"| H1
S1 -.-x|"s1 é INVALIDADA em compilação (Previne Double Free)"| S1_INV["s1 INVALIDADA"]
style STACK_MEMORY fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style HEAP_MEMORY fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style S2 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style S1_INV fill:#ffebee,stroke:#f44336,stroke-width:2px
🏛️ 1. O Coração do Rust: O Sistema de Ownership
O conceito mais distintivo e revolucionário da linguagem Rust é o seu Sistema de Ownership (Propriedade). É esse mecanismo que permite ao Rust garantir segurança absoluta de memória sem o overhead de um Garbage Collector e sem os riscos de ponteiros soltos de C/C++.
As Três Regras Invioláveis do Ownership:
- Cada valor em Rust tem um único proprietário (owner): Uma variável associada que detém a posse daquele pedaço de memória.
- Só pode haver um único proprietário por vez: Dois ponteiros nunca podem ser donos simultâneos do mesmo buffer na Heap.
- Quando o proprietário sai de escopo, o valor é imediatamente destruído: O compilador insere automaticamente a chamada ao destrutor
drop(), liberando a memória na Heap sem atrasos.
💾 2. Memória Stack vs Memória Heap
Para compreender o Ownership, é fundamental entender a separação física da memória:
| Característica | Memória Stack (Pilha) | Memória Heap (Monte) |
|---|---|---|
| Alocação | LIFO ultra-rápida (ajuste do ponteiro de pilha SP) | Busca por bloco livre pelo alocador do SO |
| Tamanho | Tamanho fixo e conhecido em tempo de compilação | Tamanho dinâmico que pode crescer em runtime |
| Acesso | Acesso direto e contíguo (alta afinidade com cache L1/L2) | Acesso indireto seguindo ponteiro |
| Tipos Exemplo | Inteiros, floats, bool, arrays [T; N], tuplas |
String, Vec<T>, Box<T>, HashMap |
🔄 3. Move Semantics vs Clonagem Profunda
Quando atribuímos uma variável proprietária de Heap a outra em linguagens tradicionais:
- Em C++: ou ocorre uma cópia rasa (shallow copy) perigosa que leva ao bug de Double Free, ou uma cópia profunda (deep copy) custosa.
- Em Java/Python: ambos os nomes apontam para o mesmo objeto gerenciado pelo Garbage Collector.
No Rust, ocorre uma operação chamada Move:
let s1 = String::from("SISTEMA_CRITICO");
let s2 = s1; // MOVE! O ownership foi transferido de s1 para s2
println!("{}", s2); // Perfeito: s2 é a nova proprietária
// println!("{}", s1); // ERRO DE COMPILAÇÃO! s1 foi invalidada!
3.1 Por que o Rust Invalida s1?
Se o Rust permitisse que s1 e s2 continuassem existindo, quando ambas saíssem de escopo, o compilador tentaria liberar o mesmo endereço de memória na Heap duas vezes consecutivas. Isso constitui a vulnerabilidade crítica conhecida como Double Free, uma das principais causas de exploits em C/C++. Ao invalidar s1 no momento da atribuição, o Rust elimina o problema na raiz.
3.2 Clonagem Profunda com .clone()
Se você realmente precisa duplicar os dados da Heap para manter ambas as variáveis ativas, declare isso explicitamente invocando .clone():
let s1 = String::from("DADOS_DUPLICADOS");
let s2 = s1.clone(); // Aloca um novo buffer separado na Heap
println!("s1: {}, s2: {}", s1, s2); // Ambas válidas!
⚡ 4. O Trait Copy para Tipos da Stack
Tipos primitivos cujos dados residem 100% na Stack (inteiros, floats, booleanos, chars e tuplas contendo apenas tipos Copy) implementam o marcador Copy. Para esses tipos, uma atribuição realiza uma cópia bit a bit instantânea na pilha sem transferir ownership:
let a = 42;
let b = a; // Cópia bit a bit na Stack
println!("a = {}, b = {}", a, b); // Ambas continuam 100% válidas!
🚀 5. Ownership em Chamadas de Funções
Passar uma variável proprietária para uma função como argumento transfere a posse (Move) para o parâmetro da função:
fn processar_string(texto: String) {
println!("Função assumiu a posse: {}", texto);
// 'texto' é liberado da Heap ao fechar esta chave '}'
}
fn main() {
let mensagem = String::from("ALERTA_NUCLEO");
processar_string(mensagem); // Ownership movido para a função!
// println!("{}", mensagem); // ERRO: borrow of moved value: `mensagem`
}
⚠️ 6. Armadilhas Comuns e Mensagens do Compilador
- Uso de Valor Movido (E0382):
error[E0382]: borrow of moved value: `s1` --> src/main.rs:4:20 | 2 | let s1 = String::from("hello"); 3 | let s2 = s1; | -- value moved here 4 | println!("{}", s1); | ^^ value borrowed here after moveResolução: Se precisar que a função original mantenha os dados, não passe o valor por posse; use Borrowing (
&s1), conforme estudaremos no Capítulo 06. - Clonar sem Necessidade:
Evite encher seu código de
.clone()apenas para silenciar o compilador. Cada.clone()em umaStringouVecrealiza uma alocação de memória na Heap, degradando a performance.
🛡️ 7. Boas Práticas e RAII (Resource Acquisition Is Initialization)
- Destruição Determinística: O Rust aplica RAII estrito através do trait
Drop. Você pode utilizar estruturas para gerenciar conexões com bancos, travas de arquivos e sockets de rede, com a certeza de que serão fechados imediatamente quando o proprietário sair de escopo.
📋 8. Cheat-Sheet: Resumo Executivo
// Heap: Move Semantics
let s1 = String::from("Texto");
let s2 = s1; // s1 invalidada, s2 é a dona
let s3 = s2.clone(); // Deep copy na heap, ambas válidas
// Stack: Copy Semantics
let x = 10;
let y = x; // x continua válida
// Função que assume posse
fn consome(s: String) { /* drop automático no fim */ }