Pular para o conteúdo

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.

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: 5
Ainda é meu: hello
▶ Experimente você mesmo

O &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.)

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á!!!
▶ Experimente você mesmo

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.

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 immutable

A mensagem diz: “não é possível pegar text emprestada como mutável porque ela também está emprestada como imutável”.

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á, mundo
▶ Experimente você mesmo

A 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.

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.

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.

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 main
▶ Experimente você mesmo

A 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.)

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çãs
▶ Experimente você mesmo

add_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.

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 here
4 | let second = &mut message;
| ^^^^^^^^^^^^ second mutable borrow occurs here
5 | first.push_str(" 10");
| ----- first borrow later used here

A 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 pontos
▶ Experimente você mesmo

No 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 &mut para 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

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