Capítulo 06: Borrowing, Referências Imutáveis e Mutáveis (&, &mut)

Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance


🗺️ Mapa Conceitual da Matriz de Decisão do Borrow Checker

flowchart TD
    Dono["Variável Proprietária do Dado (Owner na Heap / Stack)"] --> Escolha["Operação Desejada pelo Desenvolvedor"]

    subgraph BORROW_RULES["As Regras do Empréstimo (Aliasing XOR Mutability)"]
        RefImutavel["&T: Referência Compartilhada (Shared / Imutável)"]
        RefMutavel["&mut T: Referência Exclusiva (Unique / Mutável)"]
    end

    Escolha -->|"Apenas Leitura"| RefImutavel
    Escolha -->|"Escrita / Modificação"| RefMutavel

    RefImutavel -->|"Regra 1"| Multiplos["Permite N Leitores Simultâneos (&T, &T, &T...)"]
    RefMutavel -->|"Regra 2"| Exclusivo["Permite Exatamente 1 Escritor (&mut T). Zero Leitores Coexistindo!"]

    Multiplos & Exclusivo --> PrevineDataRace["Zero Data Races & Zero Iteration Invalidation Garantidos em Compilação"]

    style Dono fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style BORROW_RULES fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style Multiplos fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style Exclusivo fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style PrevineDataRace fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

🏛️ 1. O Conceito de Empréstimo (Borrowing)

Se cada chamada de função ou inspeção de dados exigisse transferir a posse (Move) ou fazer uma cópia completa da memória (.clone()), programar em Rust seria extremamente ineficiente e verboso.

Para solucionar isso, o Rust introduz o conceito de Borrowing (empréstimo de dados por referência):

  • Uma referência (&) é um ponteiro que permite acessar o valor de uma variável sem assumir a propriedade daquele valor.
  • Como a referência não é proprietária, quando ela sai de escopo, o valor ao qual ela aponta não é destruído.
  • A criação de uma referência é chamada de tomar emprestado (borrowing).

⚙️ 2. Referências Imutáveis (&T)

Uma referência imutável permite ler os dados sem modificá-los. Você pode ter infinitas referências imutáveis ativas simultaneamente:

fn calcular_tamanho(texto: &String) -> usize {
    // texto é uma referência imutável (&) para a String do chamador
    texto.len()
    // texto sai de escopo aqui, mas como não é proprietário, a String NÃO é dropada!
}

fn main() {
    let s = String::from("SISTEMA_OPERACIONAL");
    
    // Passamos &s (referência por empréstimo)
    let len = calcular_tamanho(&s);
    
    // 's' continua 100% válida e disponível para uso!
    println!("A string '{}' possui {} caracteres.", s, len);
}

✍️ 3. Referências Mutáveis (&mut T)

Quando uma rotina precisa alterar o valor de uma variável emprestada sem assumir sua posse, utiliza-se uma referência mutável &mut:

fn adicionar_sufixo_datacenter(nome_servidor: &mut String) {
    nome_servidor.push_str(".sa-east-1.compute.internal");
}

fn main() {
    let mut host = String::from("srv-db-primary");
    
    // Passagem explícita de referência mutável &mut
    adicionar_sufixo_datacenter(&mut host);
    
    println!("FQDN Atualizado: {}", host);
}

⚖️ 4. A Regra de Ouro do Borrow Checker: Aliasing XOR Mutability

A regra central que torna o Rust infalível contra concorrência corrompida e ponteiros inválidos é conhecida na literatura de linguagens como Aliasing XOR Mutability:

Em qualquer momento dado no código, você pode ter:

  • Ou qualquer quantidade de referências imutáveis (&T);
  • Ou exatamente UMA referência mutável (&mut T).
  • MAS NUNCA AMBAS COEXISTINDO AO MESMO TEMPO!

Por que essa regra elimina Data Races?

Um Data Race (condição de corrida de dados) ocorre quando três condições se manifestam simultaneamente:

  1. Dois ou mais ponteiros acessam o mesmo endereço de memória ao mesmo tempo.
  2. Pelo menos um dos acessos é de escrita.
  3. Não há mecanismos de sincronização coordenando os acessos.

Ao proibir que leitores e escritores coexistam para o mesmo dado, o compilador do Rust torna a ocorrência de data races matematicamente impossível em código seguro!


⏳ 5. Non-Lexical Lifetimes (NLL)

Nas primeiras versões do Rust (antes da edição 2018), uma referência ficava ativa até o fim da chave } do bloco onde foi criada. No Rust moderno (com NLL):

  • O compilador rastreia o último ponto exato em que a referência foi utilizada.
  • Assim que a última leitura acontece, o empréstimo é considerado encerrado, liberando a variável para novos empréstimos mutáveis:
let mut dados = String::from("Kernel");

let ref_leitor = &dados; // Empréstimo imutável inicia
println!("Lendo dado: {}", ref_leitor); // ÚLTIMO USO da ref_leitor! O empréstimo encerra aqui.

// Agora é 100% permitido criar uma referência mutável, sem precisar de chaves extras!
let ref_escritor = &mut dados;
ref_escritor.push_str(" v2.0");
println!("Atualizado: {}", ref_escritor);

⚠️ 6. Armadilhas Comuns e Mensagens do Compilador

  1. Tentativa de Empréstimo Mutável Concomitante (E0502):
    error[E0502]: cannot borrow `dados` as mutable because it is also borrowed as immutable
      --> src/main.rs:6:5
       |
     4 |     let ref1 = &dados;
       |                ------ immutable borrow occurs here
     5 |     let ref2 = &mut dados;
       |                ^^^^^^^^^^ mutable borrow occurs here
     6 |     println!("{}", ref1);
       |                    ---- immutable borrow later used here
    

    Correção: Garanta que todas as leituras de ref1 terminem antes de criar o empréstimo mutável ref2.

  2. Invalidating Slices Durante Mutação: Tentar fazer vetor.push(10) enquanto uma fatia &vetor[0] está sendo lida resultará em erro do compilador, impedindo que a realocação do vetor invalide o ponteiro da fatia.

🛡️ 7. Boas Práticas e Recomendações de Engenharia

  • Prefira Empréstimos a Mutações Globais: Passe referências granulares apenas aos campos necessários de uma struct em vez de passar a struct inteira como &mut.
  • Zero-Copy Architecture: Projete suas funções para receberem &str em vez de &String e &[T] em vez de &Vec<T>, permitindo aceitar tanto dados dinâmicos da Heap quanto arrays estáticos da Stack com zero overhead.

📋 8. Cheat-Sheet: Resumo Executivo

let mut x = 10;

// Empréstimo imutável (múltiplos leitores)
let r1 = &x;
let r2 = &x;
println!("{} e {}", r1, r2);

// Empréstimo mutável (escritor exclusivo)
let r3 = &mut x;
*r3 += 5; // Desreferenciação e mutação