Che cosa significa "non può essere preso in prestito come immutabile perché è anche preso in prestito come mutabile" significa in un indice di array nidificato?


16

Cosa significa l'errore in questo 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

Ho trovato che l'indicizzazione è implementata tramite l' Indexe IndexMuttratti e che v[1]è zucchero sintattico per *v.index(1). Dotato di questa conoscenza, ho provato ad eseguire il seguente codice:

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;
}

Con mia sorpresa, questo funziona perfettamente! Perché il primo frammento non funziona, ma il secondo funziona? Per come comprendo la documentazione, dovrebbero essere equivalenti, ma ovviamente non è così.


2
Imparare la ruggine con l'avvento del codice? Benvenuto su StackOverflow e grazie per l'ottima domanda!
Sven Marnach,

Precisamente ; ) Questo è il mio terzo anno (2 volte prima di Haskell) ~> ho pensato di dare a Rust un vortice da quando ho iniziato a interessarmi di cose di basso livello
Lucas Boucke,

@LucasBoucke È divertente, di solito uso Rust per il mio progetto, ma scrivo questo AoC in Haskell. Entrambi sono grandi lingue nel loro dominio.
Boiethios,

Risposte:


16

La versione desugared è leggermente diversa da quella che hai. La linea

v[v[1]] = 999;

in realtà desugars a

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

Ciò provoca lo stesso messaggio di errore, ma le annotazioni danno un suggerimento su ciò che sta accadendo:

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 differenza importante con la tua versione desugared è l'ordine di valutazione. Gli argomenti di una chiamata di funzione vengono valutati da sinistra a destra nell'ordine elencato, prima di effettuare effettivamente la chiamata di funzione. In questo caso ciò significa che &mut vviene valutato per primo , mutuamente mutuato v. Successivamente, Index::index(&v, 1)dovrebbe essere valutato, ma questo non è possibile - vè già mutuamente preso in prestito. Infine, il compilatore mostra che il riferimento mutabile è ancora necessario per la chiamata della funzione index_mut(), quindi il riferimento mutabile è ancora attivo quando si tenta il riferimento condiviso.

La versione effettivamente compilata ha un ordine di valutazione leggermente diverso.

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

Innanzitutto, gli argomenti delle funzioni per le chiamate al metodo vengono valutati da sinistra a destra, ovvero *v.index(1)vengono valutati per primi. Ciò si traduce in un usizee il prestito condiviso temporaneo di vpuò essere rilasciato nuovamente. Quindi, index_mut()viene valutato il ricevitore di , ovvero vviene mutuamente mutuato. Funziona bene, poiché il prestito condiviso è già stato finalizzato e l'intera espressione passa il controllo del prestito.

Nota che la versione che compila lo fa solo dopo l'introduzione di "vite non lessicali". Nelle versioni precedenti di Rust, il prestito condiviso sarebbe durato fino alla fine dell'espressione e si sarebbe verificato un errore simile.

La soluzione più pulita secondo me è usare una variabile temporanea:

let i = v[1];
v[i] = 999;

Woah! C'è molto da fare qui! Grazie per il tempo dedicato a spiegare! (è interessante notare che questi tipi di "stranezze" rendono una lingua più interessante per me ...). Potresti forse anche dare un suggerimento sul perché il *v.index_mut(*v.index_mut(1)) = 999;fallimento con "non posso prendere in prestito v come mutevole più di una volta" ~> non dovrebbe essere il compilatore, in quanto in *v.index_mut(*v.index(1)) = 999;grado di capire che il prestito interno non è più necessario?
Lucas Boucke,

@LucasBoucke Rust ha alcune stranezze che a volte sono un po 'scomode, ma nella maggior parte dei casi la soluzione è piuttosto semplice, come in questo caso. Il codice è ancora abbastanza leggibile, solo un po ' diverso da quello che avevi originariamente, quindi in pratica non è un grosso problema.
Sven Marnach,

@LucasBoucke Siamo spiacenti, non ho visto la tua modifica fino ad ora. Il risultato di *v.index(1)è il valore memorizzato in quell'indice e quel valore non richiede di mantenere in vita il prestito v. Il risultato *v.index_mut(1), d'altra parte, è un'espressione mutevole del luogo che potrebbe teoricamente essere assegnata, quindi mantiene vivo il prestito. A prima vista, dovrebbe essere possibile insegnare al controllore del prestito che un'espressione di posto nel contesto di un'espressione di valore può essere trattata come un'espressione di valore, quindi è possibile che questo venga compilato in alcune versioni future di Rust.
Sven Marnach,

Che ne dite di un RFC per desugar questo a:{ let index = *Index::index(&v, 1); let value = 999; *IndexMut::index_mut(&mut v, index) = value; }
Boiethios

@FrenchBoiethios Non ho idea di come lo formalizzeresti, e sono sicuro che non volerà mai volare. Se vuoi affrontarlo, l'unico modo che vedo è attraverso miglioramenti del controllore dei prestiti, ad esempio facendo in modo che rilevi che il prestito mutabile può iniziare in un secondo momento, dal momento che non è davvero necessario così presto. (Questa idea particolare probabilmente non funziona neanche.)
Sven Marnach,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.