O Verificador de Empréstimos
O verificador de empréstimos rastreia quais ligações possuem dados e quais referências os emprestam. Ele rejeita programas que poderiam causar uso após liberação (use-after-free), liberação dupla (double-free) ou corridas de dados (data races).
Receita
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let s1 = String::from("longer");
let len = {
let s2 = String::from("x");
longest(&s1, &s2)
};
println!("{len}");
}Quando usar isso: Para entender erros de compilação sobre "valor emprestado não vive o tempo suficiente" ou "não é possível emprestar como mutável".
Exemplo de Trabalho
struct Cache { data: Vec<String> }
impl Cache {
fn get_or_insert(&mut self, key: &str) -> &String {
for s in &self.data {
if s == key { return s; }
}
self.data.push(key.to_string());
self.data.last().unwrap()
}
}
fn main() {
let mut c = Cache { data: vec![] };
let v = c.get_or_insert("a");
println!("{v}");
}O que isso demonstra:
- Retornar
&Stringde&mut selfestende o empréstimo deself - O chamador mantém um empréstimo compartilhado enquanto usa o valor de retorno
- Não é possível mutar
cenquantovempresta dele - Padrão comum em caches e internadores (interners)
Análise Profunda
Como Funciona
- Cada referência tem uma região de tempo de vida (lifetime) onde deve permanecer válida
- Empréstimos começam na criação do empréstimo, terminam no último uso (NLL - Non-Lexical Lifetimes)
- Empréstimos mutáveis excluem todos os outros empréstimos para os mesmos dados
- O verificador é intra-procedural mais restrições de tempo de vida em funções
Padrões de Erro Comuns
| Mensagem de erro | Causa Típica |
|---|---|
| valor emprestado após mover (value borrowed after move) | Uso de ligação após transferência |
| não é possível emprestar como mutável (cannot borrow as mutable) | Empréstimo compartilhado ativo existe |
| não vive o tempo suficiente (does not live long enough) | Referência retornada sobrevive ao proprietário |
&mut entre chamadas de método | Empréstimos sobrepostos em métodos de struct |
Estratégia de Correção
- Leia ambas as extensões de erro (início do empréstimo e uso inválido)
- Encurte o escopo do empréstimo com um bloco
{ } - Clone os dados se a divisão de propriedade for impossível
- Reestruture para retornar valores possuídos em vez de referências
Armadilhas
- Lutar com
unsafeoutransmute- Esconde um problema real de design. Correção: Refatoração segura primeiro. - Clonagem como correção padrão - Mascara problemas da API de empréstimos. Correção: Escope os empréstimos ou altere o tipo de retorno.
- Armazenar referências em structs de longa duração - Parâmetros de tempo de vida se proliferam. Correção: Use
String/Arcpossuídos ou índices. - Iterador mantendo
&mutenquanto muta a coleção - Segundo empréstimo falha. Correção: Separe passagens ou use índices. - Ignorar NLL e adicionar escopos extras desnecessariamente - Às vezes, o último uso já encerra o empréstimo. Correção: Confie nas dicas de linha do compilador.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
Retornar T possuído | Chamadores precisam de dados independentes | Compartilhamento de zero cópia é necessário |
| Índices em arena | Handles estáveis sem referências | Árvore simples com referências pai |
Rc<RefCell<T>> | Grafo compartilhado em um thread | Caminho crítico de alta performance |
| Alocador de arena | Muitos refs de curta duração ligados | Objetos não relacionados de longa duração |
FAQs
O verificador de empréstimos é uma verificação em tempo de execução?
Não. Ele roda apenas em tempo de compilação. Custo zero em tempo de execução para as regras de empréstimo.
Por que meu erro é tão grande?
Rust imprime cadeias de causalidade completas. Foque no primeiro erro e nas extensões destacadas.
O verificador pode ser muito rigoroso?
Às vezes, as APIs parecem difíceis de expressar. Padrões como split_at_mut e tempos de vida existem para modelar o uso seguro.
Ele pega bugs de lógica?
Não. Apenas segurança de memória e segurança de threads (com Send/Sync). Use testes para lógica.
O que é empréstimo em duas fases (two-phase borrow)?
O empréstimo mutável de um campo de struct pode ser dividido entre etapas de auto-referência em chamadas de método. Erros podem ser sutis.
Verificador de empréstimos Polonius?
Análise experimental melhorando alguns casos de erro. O rustc estável usa o verificador baseado em NLL.
Posso desativá-lo?
Apenas com unsafe Rust dentro de um bloco unsafe para ponteiros brutos - não para referências normais.
Por que emprestar entre funções?
Parâmetros de tempo de vida conectam empréstimos de entrada e saída para que os chamadores não possam usar indevidamente as refs retornadas.
O `Cell` contorna o verificador?
Cell move valores Copy para dentro/fora em tempo de execução. RefCell impõe regras de empréstimo dinamicamente com pânicos.
Melhor ferramenta de depuração?
rustc --explain E0499 (e códigos relacionados), dicas rust-analyzer e Miri para unsafe.
Relacionados
- Lutando contra o Verificador de Empréstimos - padrões de refatoração
- Anotações de Tempo de Vida - escrevendo
'a - Elisão de Tempo de Vida - tempos de vida omitidos
- Cenários de Erro do Verificador de Empréstimos - exemplos práticos
Versões da Pilha: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.