Borrowing em Rust
Na página de ownership você viu um problema: passar uma String para uma função move essa string, e aí você não consegue mais usá-la. Ficar movendo (ou clonando) valores o tempo todo seria um sofrimento. O borrowing (empréstimo) é a solução do Rust para isso.
Pegar emprestado significa deixar um trecho de código usar um valor sem virar dono dele. É como emprestar um livro para um amigo: ele lê, devolve, e o livro continua sendo seu.
Referências: emprestando com &
Seção intitulada “Referências: emprestando com &”Você cria uma referência colocando & na frente de um valor. Com uma referência, a função consegue olhar os dados sem ser dona deles:
fn print_length(s: &String) { // pega s emprestada, não é dona dela println!("Tamanho: {}", s.len());}
fn main() { let text = String::from("hello"); print_length(&text); // empresta text para a função println!("Ainda é meu: {text}"); // ✅ text continua válida aqui}Saída:
Tamanho: 5Ainda é meu: helloO &text quer dizer “deixe esta função pegar text emprestada”. Como a função só pegou emprestado, o ownership continua com main, e você pode seguir usando text depois. Compare com a página de ownership, em que o mesmo código sem o & dava erro.
(Deixamos "hello" em inglês de propósito: em UTF-8, letras acentuadas como o “á” de “olá” ocupam 2 bytes, e .len() conta bytes. Com “olá”, o tamanho mostrado seria 4, e não 3.)
Referências mutáveis: emprestando para alterar
Seção intitulada “Referências mutáveis: emprestando para alterar”Uma referência & comum é somente leitura: dá para olhar, mas não para mexer. Se a função precisa alterar o valor emprestado, use uma referência mutável com &mut:
fn add_excitement(s: &mut String) { s.push_str("!!!");}
fn main() { let mut text = String::from("olá"); add_excitement(&mut text); // empresta de forma mutável println!("{text}"); // olá!!!}Saída:
olá!!!Repare que três coisas precisaram combinar: text foi declarada com mut, a função pede um &mut String e a chamada passa &mut text. As três são obrigatórias para alterar um valor emprestado.
As regras do borrowing
Seção intitulada “As regras do borrowing”O Rust impõe duas regras sobre referências. Elas existem para evitar data races (condições de corrida nos dados): toda uma família de bugs em que dois trechos de código mexem nos mesmos dados ao mesmo tempo e acabam corrompendo tudo.
A qualquer momento, você pode ter ou:
- quantas referências somente leitura quiser (
&), ou - exatamente uma referência mutável (
&mut),
mas nunca as duas coisas ao mesmo tempo.
Em outras palavras: vários leitores ou um único escritor, nunca os dois juntos. Este código quebra a regra:
fn main() { let mut text = String::from("olá");
let r1 = &text; // um empréstimo somente leitura let r2 = &mut text; // ❌ não dá para emprestar como mutável enquanto r1 existe
println!("{r1} {r2}");}O compilador não deixa:
error[E0502]: cannot borrow `text` as mutable because it is also borrowed as immutableA mensagem diz: “não é possível pegar text emprestada como mutável porque ela também está emprestada como imutável”.
Quando um borrow termina?
Seção intitulada “Quando um borrow termina?”Talvez você espere que um empréstimo dure até o } que fecha o seu escopo. Não é assim. Um empréstimo dura só até o último lugar em que a referência é usada. Depois disso, o valor está livre para ser emprestado de novo, até de forma mutável.
Isso significa que o exemplo quebrado acima pode ser corrigido só terminando de usar r1 antes de criar r2:
fn main() { let mut text = String::from("olá");
let r1 = &text; println!("Lendo: {r1}"); // último uso de r1, então o empréstimo termina aqui
let r2 = &mut text; // ✅ tudo certo: ninguém está mais lendo r2.push_str(", mundo");
println!("{text}");}Saída:
Lendo: oláolá, mundoA regra continua sendo “vários leitores ou um único escritor”. Ela só é verificada no trecho de código em que cada referência é realmente usada. O leitor r1 já terminou quando o escritor r2 aparece, e r2 já terminou antes que o último println! leia text de novo. Nada se sobrepõe, então o Rust fica satisfeito.
Esse é o jeito mais comum de corrigir um erro de borrowing: mudar a ordem. Faça toda a leitura de que precisa e depois a escrita (ou o contrário), em vez de misturar as duas.
Por que isso importa
Seção intitulada “Por que isso importa”Essas regras são verificadas inteiramente em tempo de compilação, então não custam nada quando o programa está rodando. É assim que o Rust consegue prometer “sem data races” e, mesmo assim, ser tão rápido quanto C: a segurança é comprovada antes mesmo de o programa começar.
Prática
Seção intitulada “Prática”Tente resolver cada exercício sozinho primeiro, no Rust Playground ou em um projeto local. Se o compilador reclamar, leia o erro: ele normalmente mostra exatamente onde os empréstimos se sobrepõem.
1. Contar sem tomar para si
Seção intitulada “1. Contar sem tomar para si”Escreva uma função total_letters que pegue emprestado um Vec<String> e retorne quantas letras todas as palavras têm juntas. Depois da chamada, o main ainda precisa conseguir imprimir o vetor.
Dica: o tipo do parâmetro é &Vec<String>, e você chama a função com &words. Percorra o vetor emprestado com for word in list e some os valores de word.len().
Ver solução
fn total_letters(list: &Vec<String>) -> usize { let mut total = 0; for word in list { total += word.len(); } total}
fn main() { let words = vec![String::from("borrow"), String::from("check")]; let count = total_letters(&words); // empresta o vetor println!("{count} letras"); println!("{:?} continua pertencendo ao main", words);}Saída:
11 letras["borrow", "check"] continua pertencendo ao mainA função só precisa olhar as palavras, então um empréstimo & somente leitura basta. Se você escrevesse list: Vec<String>, o vetor seria movido para dentro da função e o último println! não compilaria. (As palavras ficaram em inglês de propósito: len() conta bytes, e letras acentuadas ocupam 2 bytes em UTF-8, o que mudaria o total.)
2. Uma lista de compras
Seção intitulada “2. Uma lista de compras”Escreva duas funções: add_item, que acrescenta um item a uma lista de compras, e print_list, que imprime todos os itens. Acrescente três itens e depois imprima a lista.
Dica: uma função altera a lista e a outra só lê. Qual delas precisa de &mut Vec<String> e qual se contenta com &Vec<String>? Para transformar um &str em uma String, use String::from(item).
Ver solução
fn add_item(list: &mut Vec<String>, item: &str) { list.push(String::from(item));}
fn print_list(list: &Vec<String>) { for item in list { println!("- {item}"); }}
fn main() { let mut list = Vec::new(); add_item(&mut list, "leite"); add_item(&mut list, "pão"); add_item(&mut list, "maçãs"); print_list(&list);}Saída:
- leite- pão- maçãsadd_item altera a lista, então recebe um empréstimo &mut, e list no main precisa ser mut. print_list só lê, então um & comum basta. Cada empréstimo &mut list termina quando a chamada retorna, e é por isso que a próxima chamada pode pegá-la emprestada de novo.
3. Dois escritores ao mesmo tempo
Seção intitulada “3. Dois escritores ao mesmo tempo”Este programa não compila. Corrija-o para que ele imprima Pontuação: 10 pontos, mantendo tanto first quanto second.
fn main() { let mut message = String::from("Pontuação:"); let first = &mut message; let second = &mut message; first.push_str(" 10"); second.push_str(" pontos"); println!("{message}");}error[E0499]: cannot borrow `message` as mutable more than once at a time --> src/main.rs:4:18 |3 | let first = &mut message; | ------------ first mutable borrow occurs here4 | let second = &mut message; | ^^^^^^^^^^^^ second mutable borrow occurs here5 | first.push_str(" 10"); | ----- first borrow later used hereA mensagem diz: “não é possível pegar message emprestada como mutável mais de uma vez ao mesmo tempo”.
Dica: só um escritor por vez. Releia Quando um borrow termina? acima: dá para terminar de usar first antes de second ser criada?
Ver solução
fn main() { let mut message = String::from("Pontuação:");
let first = &mut message; first.push_str(" 10"); // último uso de first: o empréstimo termina aqui
let second = &mut message; second.push_str(" pontos");
println!("{message}");}Saída:
Pontuação: 10 pontosNo original, first ainda ia ser usada (na linha 5) quando second foi criada, na linha 4. São dois escritores ao mesmo tempo. Subir o first.push_str faz first terminar antes de second existir, então nunca há mais de um &mut de cada vez.
- O borrowing (
&) deixa o código usar um valor sem virar dono dele. Você recebe o valor de volta. - Uma referência
&comum é somente leitura. Use&mutpara alterar o valor emprestado. - Para alterar algo através de uma referência, a variável, o parâmetro e a chamada precisam de
mut/&mut. - A regra: vários leitores ou um único escritor, nunca os dois ao mesmo tempo.
- Um empréstimo termina no último uso da referência, então reordenar o código muitas vezes resolve um erro de borrowing.
- Tudo isso é verificado na compilação, então não custa nada em tempo de execução.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que acontece quando você coloca & na frente de um valor ao passá-lo para uma função?
2. Segundo as regras do borrowing, qual combinação é permitida ao mesmo tempo?
3. Do que você precisa para alterar um valor através de uma referência?
4. Por que o Rust consegue aplicar as regras do borrowing sem nenhum custo de desempenho em tempo de execução?
Pontuação: 0 / 4
Para saber mais
Seção intitulada “Para saber mais”- References and Borrowing — The Rust Book (em inglês): o capítulo oficial, incluindo referências pendentes (dangling references) e por que o Rust se recusa a criá-las.
- Borrowing — Rust by Example (em inglês): exemplos de empréstimos com
&que você pode rodar, com continuações sobre empréstimos mutáveis e aliasing. - Non-lexical lifetimes — The Edition Guide (em inglês): a mudança que fez os empréstimos terminarem no último uso, e não na chave de fechamento.
- The Slice Type — The Rust Book (em inglês): pegar emprestada só uma parte de uma string ou array, o próximo passo natural depois das referências.
reference— documentação da biblioteca padrão (em inglês): o que são&Te&mut Te quais traits eles implementam.