O que “não pode pedir emprestado como imutável porque também é emprestado como mutável” significa em um índice de matriz aninhada?

16

O que o erro significa neste caso:

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    v[v[1]] = 999;
}
error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:3:7
  |
3 |     v[v[1]] = 999;
  |     --^----
  |     | |
  |     | immutable borrow occurs here
  |     mutable borrow occurs here
  |     mutable borrow later used here

Eu descobri que a indexação é implementada via IndexeIndexMut características e que v[1]é açúcar sintático para *v.index(1). Equipado com esse conhecimento, tentei executar o seguinte código:

use std::ops::{Index, IndexMut};

fn main() {
    let mut v: Vec<usize> = vec![1, 2, 3, 4, 5];
    *v.index_mut(*v.index(1)) = 999;
}

Para minha surpresa, isso funciona perfeitamente! Por que o primeiro trecho não funciona, mas o segundo funciona? Do jeito que eu entendo a documentação, eles devem ser equivalentes, mas isso obviamente não é o caso.

Lucas Boucke
fonte
2
Aprendendo Ferrugem com o Advento do Código? Bem-vindo ao StackOverflow, e obrigado pela ótima pergunta!
Sven Marnach 02/12/19
Precisamente; ) Este é o 3º ano de fazê-lo (2x Haskell antes disso) ~> pensamento para dar Rust um turbilhão desde que eu comecei a ser mais interessados em baixo material nível
Lucas Boucke
@LucasBoucke Isso é engraçado, eu geralmente uso Rust no meu projeto, mas escrevo esse AoC em Haskell. Ambos são ótimos idiomas em seu domínio.
Boiethios

Respostas:

16

A versão sugerida é um pouco diferente do que você tem. A linha

v[v[1]] = 999;

realmente desugars para

*IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;

Isso resulta na mesma mensagem de erro, mas as anotações dão uma dica do que está acontecendo:

error[E0502]: cannot borrow `v` as immutable because it is also borrowed as mutable
 --> src/main.rs:7:48
  |
7 |     *IndexMut::index_mut(&mut v, *Index::index(&v, 1)) = 999;
  |      ------------------- ------                ^^ immutable borrow occurs here
  |      |                   |
  |      |                   mutable borrow occurs here
  |      mutable borrow later used by call

A diferença importante para sua versão desejada é a ordem de avaliação. Os argumentos de uma chamada de função são avaliados da esquerda para a direita na ordem listada, antes de realmente fazer a chamada de função. Nesse caso, isso significa que primeiro &mut vé avaliado, mutuamente emprestado v. Em seguida, Index::index(&v, 1)deve ser avaliado, mas isso não é possível -v já é mutuamente emprestado. Finalmente, o compilador mostra que a referência mutável ainda é necessária para a chamada de função index_mut(), portanto, a referência mutável ainda está ativa quando a referência compartilhada é tentada.

A versão que realmente compila tem uma ordem de avaliação ligeiramente diferente.

*v.index_mut(*v.index(1)) = 999;

Primeiro, os argumentos da função para as chamadas do método são avaliados da esquerda para a direita, ou seja, *v.index(1)são avaliados primeiro. Isso resulta em usizeae o empréstimo compartilhado temporário de vpode ser liberado novamente. Então, o receptor de index_mut()é avaliado, ou seja,v é mutuamente emprestado. Isso funciona bem, pois o empréstimo compartilhado já foi finalizado e toda a expressão passa no verificador de empréstimo.

Observe que a versão que compila apenas o faz desde a introdução de "vidas úteis não-lexicais". Nas versões anteriores do Rust, o empréstimo compartilhado permaneceria até o final da expressão e resultaria em um erro semelhante.

A solução mais limpa na minha opinião é usar uma variável temporária:

let i = v[1];
v[i] = 999;
Sven Marnach
fonte
Woah! Há muita coisa acontecendo aqui! Obrigado por reservar um tempo para explicar! (curiosamente, esses tipos de "peculiaridades" tornam uma linguagem mais interessante para mim ...). Você poderia também dar uma dica de por que *v.index_mut(*v.index_mut(1)) = 999;falha com "não pode pedir emprestado v tão mutável mais de uma vez" ~> o compilador não deveria ser, pois é *v.index_mut(*v.index(1)) = 999;capaz de descobrir que o empréstimo interno não é mais necessário?
Lucas Boucke
O @LucasBoucke Rust tem algumas peculiaridades que às vezes são um pouco inconvenientes, mas na maioria dos casos a solução é bastante simples, como neste caso. O código ainda é bastante legível, um pouquinho diferente do que você originalmente tinha, então, na prática, não é grande coisa.
Sven Marnach 02/12/19
@LucasBoucke Desculpe, eu não vi sua edição até agora. O resultado de *v.index(1)é o valor armazenado nesse índice e esse valor não requer para manter vvivo o empréstimo . O resultado de *v.index_mut(1), por outro lado, é uma expressão mutável de lugar que, teoricamente, poderia ser atribuída, mantendo assim o empréstimo vivo. Na superfície, deve ser possível ensinar ao verificador de empréstimo que uma expressão de lugar no contexto de expressão de valor pode ser tratada como uma expressão de valor; portanto, é possível que isso seja compilado em alguma versão futura do Rust.
Sven Marnach 02/12/19
Que tal um RFC para desugar isso para:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios
@FrenchBoiethios Eu não tenho idéia de como você formalizaria isso, e tenho certeza que nunca vai voar. Se você quiser resolver isso, a única maneira que vejo é através de melhorias no verificador de empréstimos, por exemplo, fazendo com que ele detecte que o empréstimo mutável pode começar mais tarde, uma vez que não é realmente necessário tão cedo. (Esta idéia particular provavelmente não quer trabalhar.)
Sven Marnach