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:

  1. 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.
  2. Só pode haver um único proprietário por vez: Dois ponteiros nunca podem ser donos simultâneos do mesmo buffer na Heap.
  3. 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

  1. 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 move
    

    Resoluçã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.

  2. Clonar sem Necessidade: Evite encher seu código de .clone() apenas para silenciar o compilador. Cada .clone() em uma String ou Vec realiza 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 */ }