Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Referências e Empréstimos

O problema com o código com a tupla da Listagem 4-5 é que precisamos retornar a String para a função chamadora para que ainda possamos usá-la depois da chamada para calculate_length, porque a String foi movida para dentro de calculate_length. Em vez disso, podemos fornecer uma referência ao valor String. Uma referência é parecida com um ponteiro no sentido de que é um endereço que podemos seguir para acessar os dados armazenados naquele local; esses dados pertencem a alguma outra variável. Diferentemente de um ponteiro, uma referência tem a garantia de apontar para um valor válido de um tipo específico durante toda a vida dessa referência.

Veja como você definiria e usaria uma função calculate_length que recebe como parâmetro uma referência a um objeto, em vez de assumir o ownership do valor:

Filename: src/main.rs
fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Primeiro, repare que todo o código envolvendo a tupla na declaração da variável e no valor de retorno da função desapareceu. Segundo, observe que passamos &s1 para calculate_length e que, na definição da função, recebemos &String em vez de String. Esses e comerciais (&) representam referências, e elas permitem que você se refira a algum valor sem assumir seu ownership. A Figura 4-6 ilustra esse conceito.

Three tables: the table for s contains only a pointer to the table
for s1. The table for s1 contains the stack data for s1 and points to the
string data on the heap.

Figura 4-6: Um diagrama de &String s apontando para String s1

Observação: o oposto de referenciar usando & é desreferenciar, o que é feito com o operador de desreferência, *. Veremos alguns usos do operador de desreferência no Capítulo 8 e discutiremos os detalhes de desreferenciação no Capítulo 15.

Vamos observar mais de perto a chamada de função aqui:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

A sintaxe &s1 nos permite criar uma referência que se refere ao valor de s1, mas não é dona dele. Como a referência não possui ownership, o valor para o qual ela aponta não será desalocado quando a referência deixar de ser usada.

Da mesma forma, a assinatura da função usa & para indicar que o tipo do parâmetro s é uma referência. Vamos adicionar algumas anotações explicativas:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because s does not have ownership of what
  // it refers to, the String is not dropped.

O escopo em que a variável s é válida é igual ao escopo de qualquer parâmetro de função, mas o valor apontado pela referência não é desalocado quando s deixa de ser usado, porque s não tem ownership. Quando funções recebem referências como parâmetros em vez dos próprios valores, não precisamos devolver esses valores para transferir o ownership de volta, porque nunca o tivemos.

Chamamos o ato de criar uma referência de borrowing ou empréstimo. Como na vida real: se uma pessoa é dona de algo, você pode pegar emprestado dela. Quando terminar, precisa devolver. Você não é o dono.

Então, o que acontece se tentarmos modificar algo que estamos pegando emprestado? Experimente o código da Listagem 4-6. Aviso de antemão: não funciona!

Filename: src/main.rs
fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}
Listing 4-6: Tentando modificar um valor emprestado

Este é o erro:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn change(some_string: &mut String) {
  |                         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Assim como variáveis são imutáveis por padrão, referências também são. Não temos permissão para modificar algo para o qual temos apenas uma referência.

Referências Mutáveis

Podemos corrigir o código da Listagem 4-6 para permitir modificar um valor emprestado com apenas alguns pequenos ajustes, usando, em vez disso, uma referência mutável:

Filename: src/main.rs
fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

Primeiro, mudamos s para mut. Depois, criamos uma referência mutável com &mut s no ponto em que chamamos a função change e atualizamos a assinatura da função para aceitar uma referência mutável com some_string: &mut String. Isso deixa muito claro que a função change vai mutar o valor que tomou emprestado.

Referências mutáveis têm uma grande restrição: se você tem uma referência mutável para um valor, não pode ter nenhuma outra referência para esse mesmo valor. Este código, que tenta criar duas referências mutáveis para s, falha:

Filename: src/main.rs
fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{r1}, {r2}");
}

Este é o erro:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{r1}, {r2}");
  |                -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Esse erro diz que o código é inválido porque não podemos pegar s emprestada como mutável mais de uma vez ao mesmo tempo. O primeiro empréstimo mutável está em r1 e precisa durar até ser usado no println!, mas, entre a criação dessa referência mutável e seu uso, tentamos criar outra referência mutável em r2 que empresta os mesmos dados de r1.

A restrição que impede múltiplas referências mutáveis aos mesmos dados ao mesmo tempo permite mutação, mas de uma forma muito controlada. É algo com que novos rustaceanos costumam ter dificuldade, porque a maioria das linguagens permite mutar quando você quiser. A vantagem dessa restrição é que o Rust consegue evitar data races em tempo de compilação. Uma data race é parecida com uma condição de corrida e acontece quando estes três comportamentos ocorrem:

  • Dois ou mais ponteiros acessam os mesmos dados ao mesmo tempo.
  • Pelo menos um desses ponteiros está sendo usado para escrever nos dados.
  • Não há nenhum mecanismo de sincronização sendo usado para coordenar o acesso.

Data races causam comportamento indefinido e podem ser difíceis de diagnosticar e corrigir quando você está tentando rastreá-las em tempo de execução; o Rust evita esse problema recusando-se a compilar código com data races.

Como sempre, podemos usar chaves para criar um novo escopo, permitindo múltiplas referências mutáveis, desde que não sejam simultâneas:

fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.

    let r2 = &mut s;
}

O Rust impõe uma regra semelhante para combinar referências mutáveis e imutáveis. Este código gera um erro:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

    println!("{r1}, {r2}, and {r3}");
}

Este é o erro:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no problem
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no problem
6 |     let r3 = &mut s; // BIG PROBLEM
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{r1}, {r2}, and {r3}");
  |                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Também não podemos ter uma referência mutável enquanto temos uma referência imutável ao mesmo valor.

Quem está usando uma referência imutável não espera que o valor mude de repente enquanto a referência ainda está em uso. No entanto, múltiplas referências imutáveis são permitidas, porque ninguém que esteja apenas lendo os dados consegue afetar a leitura dos demais.

Observe que o escopo de uma referência começa no ponto em que ela é introduzida e continua até a última vez em que essa referência é usada. Por exemplo, este código compila porque o último uso das referências imutáveis está no println!, antes da introdução da referência mutável:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{r1} and {r2}");
    // Variables r1 and r2 will not be used after this point.

    let r3 = &mut s; // no problem
    println!("{r3}");
}

Os escopos das referências imutáveis r1 e r2 terminam depois do println!, onde são usadas pela última vez, o que acontece antes da criação da referência mutável r3. Esses escopos não se sobrepõem, então esse código é permitido: o compilador consegue perceber que a referência já não está mais sendo usada em um ponto anterior ao fim do escopo.

Mesmo que erros de borrowing possam ser frustrantes às vezes, lembre-se de que é o compilador do Rust apontando um possível bug cedo, em tempo de compilação em vez de em tempo de execução, e mostrando exatamente onde está o problema. Assim, você não precisa sair procurando por que seus dados não são aquilo que você achava que fossem.

Referências Pendentes

Em linguagens com ponteiros, é fácil criar por engano um dangling pointer ou ponteiro pendente, isto é, um ponteiro que faz referência a um local da memória que talvez já tenha sido entregue a outra pessoa, ao liberar alguma memória enquanto ainda se preserva um ponteiro para ela. Em Rust, ao contrário, o compilador garante que referências jamais serão pendentes: se você tem uma referência para algum dado, o compilador garante que o dado não sairá de escopo antes da referência.

Vamos tentar criar uma referência pendente para ver como o Rust evita isso com um erro de compilação:

Filename: src/main.rs
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

Este é o erro:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
5 | fn dangle() -> &'static String {
  |                 +++++++
help: instead, you are more likely to want to return an owned value
  |
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
  |

For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Essa mensagem de erro faz referência a um recurso que ainda não cobrimos: lifetimes. Discutiremos lifetimes em detalhe no Capítulo 10. Mas, se você ignorar as partes sobre lifetimes, a mensagem contém a chave para entender por que esse código é um problema:

this function's return type contains a borrowed value, but there is no value
for it to be borrowed from

Vamos observar com mais cuidado o que está acontecendo em cada etapa do código de dangle:

Filename: src/main.rs
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // dangle returns a reference to a String

    let s = String::from("hello"); // s is a new String

    &s // we return a reference to the String, s
} // Here, s goes out of scope and is dropped, so its memory goes away.
  // Danger!

Como s é criada dentro de dangle, quando o código de dangle termina, s é desalocada. Mas tentamos retornar uma referência a ela. Isso significa que a referência apontaria para uma String inválida. Nada bom! O Rust não nos deixa fazer isso.

A solução aqui é retornar a String diretamente:

fn main() {
    let string = no_dangle();
}

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

Isso funciona sem problemas. O ownership é movido para fora, e nada é desalocado.

As Regras das Referências

Vamos recapitular o que discutimos sobre referências:

  • Em qualquer momento, você pode ter ou uma referência mutável ou qualquer quantidade de referências imutáveis.
  • Referências sempre precisam ser válidas.

A seguir, veremos um tipo diferente de referência: fatias.