Capítulo 06: Borrowing, Referências Imutáveis e Mutáveis (&, &mut)
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:
- Dois ou mais ponteiros acessam o mesmo endereço de memória ao mesmo tempo.
- Pelo menos um dos acessos é de escrita.
- 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
- 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 hereCorreção: Garanta que todas as leituras de
ref1terminem antes de criar o empréstimo mutávelref2. - 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
&strem vez de&Stringe&[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