Partilhar Carros com os Amigos em Rust

Comecei do zero, tenho vindo a acompanhar a linguagem desde os seus inícios, mas nunca tinha tido contacto com ela. Comecei por fazer algumas experiências até que me envolvi na criação de um emulador para a GameBoy Color, apenas como um desafio e para diversão. Um dos conceitos mais complicado de se perceber são as referências e o borrowing (empréstimo).

O conceito de posse no Rust é como ter um carro. Podes ter todos os teus amigos a vir ver o carro e todos os seus detalhes, mas contudo eles não podem conduzir-lo. Isto é o immutable borrow no mundo Rust.

Mas digamos que tu queres deixar o teu amigo dar uma volta no teu carro. Tu podes empresta-lo em um momento e dizer-lhe que ele pode andar nele, isso é o mutably borrow no mundo Rust. Ele tem que dar o carro de volta para que tu ou outro amigo passa andar nele, e assim por diante. Se tentares deixar outro amigo conduzir ao mesmo tempo vai dar conflito, porque os dois querem conduzir.

Existem duas regras no sistema de empréstimo do Rust:

  1. Se é mutably borrowed, esse é apenas o único empréstimo que pode existir.
  2. Caso contrário, todos os amigos podem ver ao mesmo tempo.

Em ambos os casos, se o emprestador quiser conduzir o carro terá que em primeiro o obter de volta. Como é que alguém pode conduzir um carro se não o tem nas mãos?! 😄

Descobri que faz as coisas falhar e ler as mensagens de erro do compilador, ajuda-nos a perceber melhor este mecanismo de empréstimos. Steve Klabnik, o autor do “The Book”, escreveu uma introdução fantástica para o sistema de empréstimos do Rust, então vamos mexer num dos seus exemplos, quebra-lo e aprender!

Vamos começar começar por alguma coisa simples e que compile:

fn main() {
  let mut x = 5;
  let mut y = x;

  y += 1;

  println!("x is: {}", x);
  println!("y is: {}", y);
}

Aqui fica o link para o código e a mensagem do compilador é:

warning: variable does not need to be mutable, #[warn(unused_mut)] on by default
 --> <anon>:2:9
2 |>     let mut x = 5;
  |>         ^^^^^
x is: 5
y is: 6

O compilador avisa amavelmente que x não precisa de ser mutável. Mas por quê? É porque y acaba sendo uma copia separada de 5, e x não é modificado no código. Tu podes dizer que x e y são duas coisas diferentes na memória, porque os valores impressos são diferentes.

E se não quisermos que x seja copiado para y e querermos que as mudanças em y sejam aplicadas em x?

fn main() {
  let mut x = 5;
  let mut y = &x;

  *y += 1;

  println!("y is: {}", y);
}

Link para o código e a mensagem do compilador é:

error: cannot assign to immutable borrowed content `*y`
 --> <anon>:5:5
5 |>     *y += 1;
  |>     ^^^^^^^

error: aborting due to previous error

O que se passou? Desta vez y não é uma copia de x. Em vez disso, y é uma referência para x e portanto, teve que ser “desreferenciada” antes de 1 ser adicionado a y. Uma coisa importante a notar é que y é uma referência imutável, embora que o próprio y é mutável. É por isso que o compilador falha. y não tem permissão para alterar o valor.

Agora que sabemos o que se está a passar, vamos corrigir o nosso código e fazer o compilador novamente feliz! 😅

fn main() {
  let mut x = 5;
  let mut y = &mut x;

  *y += 1;

  println!("y is: {}", y);
}

Link para o código, e o output é:

y is: 6

y obtém uma referência mutável de x, podendo alterar o valor de x. Sendo assim, se imprimimos y ele imprime o valor 6 com sucesso.

Mas o que acontece se imprimimos x em vez? Isto leva-nos a mais um exemplo do livro.

fn main() {
  let mut x = 5;
  let mut y = &mut x;

  *y += 1;

  println!("x is: {}", x);
}

Link para o código e mensagem do compilador é:

error: cannot borrow `x` as immutable because it is also borrowed as mutable [--explain E0502]
   --> <anon>:7:26
3   |>     let mut y = &mut x;
    |>                      - mutable borrow occurs here
...
7   |>     println!("x is: {}", x);
    |>                          ^ immutable borrow occurs here
8   |> }
    |> - mutable borrow ends here
<std macros>:2:27: 2:58: note: in this expansion of format_args!
<std macros>:3:1: 3:54: note: in this expansion of print! (defined in <std macros>)
<anon>:7:5: 7:29: note: in this expansion of println! (defined in <std macros>)

error: aborting due to previous error

O compilador falha! O compilador dá uma explicação muito boa do porquê isto estar a ocorrer. Ele diz que println! não pode ter x imutavelmente emprestado, porque x já se encontra mutavelmente emprestado a y. Isso viola a primeira regra de empréstimos do Rust. Quando algo já se encontra mutavelmente emprestado não pode haver outros a pedir emprestado.

O livro mostra uma forma de corrigir o código usando chavetas, introduzindo o conceitos de âmbitos aninhados (ou nested scope).

fn main() {
  let mut x = 5;
  {
    let y = &mut x;
    *y += 1;
  }

  println!("x is: {}", x);
}

Link para o código e o output é:

x is: 6

Para uma leitura mais detalhada sobre scopes recomendo ler a secção do livro dedicada a esse tema.

O compilador do Rust faz um ótimo trabalho a apontar os possíveis erros que podem existir no código. Os erros não são assustadores, em vez disso indicam de forma esclarecedora a localização do erro e porque é que ele está a ocorrer. Uma boa forma de aprender mais sobre Rust e os seus mecanismos é introduzindo novos erros no código e ler a documentação sugerida pelo compilador. Cada vez que encontro um erro diferente aprendo novos conceitos e escrevo cada vez melhor código.

Agora é tempo que ires quebrar algum código! Obrigado pela leitura! 😉

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão / Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão / Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão / Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão / Alterar )

Connecting to %s