¿Qué significa "no se puede pedir prestado como inmutable porque también se toma prestado como mutable" en un índice de matriz anidada?

16

¿Qué significa el error en este 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

He encontrado que la indexación se realiza a través del Indexe IndexMutrasgos y que v[1]es el azúcar sintáctica para *v.index(1). Equipado con este conocimiento, intenté ejecutar el siguiente 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 mi sorpresa, ¡esto funciona perfectamente! ¿Por qué el primer fragmento no funciona, pero el segundo sí? Según entiendo la documentación, deberían ser equivalentes, pero obviamente este no es el caso.

Lucas Boucke
fuente
2
Aprendiendo óxido con el advenimiento del código? Bienvenido a StackOverflow, y gracias por la excelente pregunta.
Sven Marnach
Precisamente ) Este es mi tercer año de hacerlo (2 veces Haskell antes de eso) ~> pensé darle un giro a Rust desde que empecé a estar más interesado en cosas de bajo nivel
Lucas Boucke,
@LucasBoucke Eso es gracioso, generalmente uso Rust para mi proyecto, pero escribo este AoC en Haskell. Ambos son grandes idiomas en su dominio.
Boiethios

Respuestas:

16

La versión desugared es ligeramente diferente de la que tienes. La línea

v[v[1]] = 999;

en realidad desugar a

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

Esto da como resultado el mismo mensaje de error, pero las anotaciones dan una pista de lo que está sucediendo:

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

La diferencia importante con su versión desugared es el orden de evaluación. Los argumentos de una llamada a la función se evalúan de izquierda a derecha en el orden indicado, antes de realizar la llamada a la función. En este caso, esto significa que primero &mut vse evalúa, pidiendo prestado de forma mutable v. A continuación, Index::index(&v, 1)debe evaluarse, pero esto no es posible: vya está prestado de manera mutable. Finalmente, el compilador muestra que la referencia mutable todavía es necesaria para la llamada a la función index_mut(), por lo que la referencia mutable todavía está viva cuando se intenta la referencia compartida.

La versión que realmente compila tiene un orden de evaluación ligeramente diferente.

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

Primero, los argumentos de la función para las llamadas al método se evalúan de izquierda a derecha, *v.index(1)es decir, se evalúa primero. Esto da como resultado a usize, y el préstamo compartido temporal de vse puede liberar nuevamente. Luego, index_mut()se evalúa el receptor de , ves decir, se toma prestado de forma mutable. Esto funciona bien, ya que el préstamo compartido ya se ha finalizado, y toda la expresión pasa el corrector de préstamo.

Tenga en cuenta que la versión que compila solo lo hace desde la introducción de "vidas no léxicas". En versiones anteriores de Rust, el préstamo compartido viviría hasta el final de la expresión y daría lugar a un error similar.

La solución más limpia en mi opinión es usar una variable temporal:

let i = v[1];
v[i] = 999;
Sven Marnach
fuente
Woah! ¡Están pasando muchas cosas aquí! ¡Gracias por tomarte el tiempo para explicarme! (Curiosamente, ese tipo de "peculiaridades" hacen que un lenguaje sea más interesante para mí ...). ¿Podría también dar una pista de por qué *v.index_mut(*v.index_mut(1)) = 999;falla con "no se puede pedir prestado v como mutable más de una vez" ~> no debería ser el compilador, como *v.index_mut(*v.index(1)) = 999;capaz de darse cuenta de que el préstamo interno ya no es necesario?
Lucas Boucke
@LucasBoucke Rust tiene algunas peculiaridades que a veces son un poco inconvenientes, pero en la mayoría de los casos la solución es bastante simple, como en este caso. El código sigue siendo bastante legible, solo un poco diferente al que tenía originalmente, por lo que en la práctica no es gran cosa.
Sven Marnach
@LucasBoucke Lo siento, no vi tu edición hasta ahora. El resultado de *v.index(1)es el valor almacenado en ese índice, y ese valor no requiere mantener vvivo el préstamo . El resultado de *v.index_mut(1), por otro lado, es una expresión de lugar mutable que teóricamente podría asignarse, por lo que mantiene vivo el préstamo. En la superficie, debería ser posible enseñarle al verificador de préstamos que una expresión de lugar en un contexto de expresión de valor puede tratarse como una expresión de valor, por lo que es posible que esto se compile en alguna versión futura de Rust.
Sven Marnach
¿Qué tal un RFC para desugar esto a:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios
@FrenchBoiethios No tengo idea de cómo formalizarías eso, y estoy seguro de que nunca es bueno volar. Si desea abordar esto, la única forma en que veo es mediante mejoras en el verificador de préstamos, por ejemplo, haciendo que detecte que el préstamo mutable puede comenzar más tarde, ya que realmente no es necesario tan temprano. (Esta idea en particular probablemente tampoco funcione).
Sven Marnach