web-dev-qa-db-fra.com

Comment obtenir des références mutables à deux éléments de tableau en même temps?

fn change(a: &mut i32, b: &mut i32) {
    let c = *a;
    *a = *b;
    *b = c;
}

fn main() {
    let mut v = vec![1, 2, 3];
    change(&mut v[0], &mut v[1]);
}

Lorsque je compile le code ci-dessus, il présente l'erreur suivante:

error[E0499]: cannot borrow `v` as mutable more than once at a time
 --> src/main.rs:9:32
  |
9 |         change(&mut v[0], &mut v[1]);
  |                     -          ^   - first borrow ends here
  |                     |          |
  |                     |          second mutable borrow occurs here
  |                     first mutable borrow occurs here

Pourquoi le compilateur l'interdit-il? v[0] et v[1] occupent des positions de mémoire différentes, il n'est donc pas dangereux de les utiliser ensemble. Et que dois-je faire si je rencontre ce problème?

26
user2925565

Vous pouvez résoudre ceci avec split_at_mut() :

let mut v = vec![1, 2, 3];
let (a, b) = v.split_at_mut(1);   // Returns (&mut [1], &mut [2, 3])
change(&mut a[0], &mut b[0]); 

Il y a un nombre incalculable de choses sûres à faire que le compilateur ne reconnaît malheureusement pas encore. split_at_mut() est juste comme ça, une abstraction sûre implémentée avec un bloc unsafe en interne.

Nous pouvons le faire aussi, pour ce problème. Ce qui suit est quelque chose que j’utilise dans le code pour lequel j’ai quand même besoin de séparer les trois cas (I: index hors limites, II: indices égaux, III: indices distincts).

enum Pair<T> {
    Both(T, T),
    One(T),
    None,
}

fn index_twice<T>(slc: &mut [T], a: usize, b: usize) -> Pair<&mut T> {
    if a == b {
        slc.get_mut(a).map_or(Pair::None, Pair::One)
    } else {
        if a >= slc.len() || b >= slc.len() {
            Pair::None
        } else {
            // safe because a, b are in bounds and distinct
            unsafe {
                let ar = &mut *(slc.get_unchecked_mut(a) as *mut _);
                let br = &mut *(slc.get_unchecked_mut(b) as *mut _);
                Pair::Both(ar, br)
            }
        }
    }
}
25
bluss

Sur le canal nocturne, la correspondance de motif peut être faite avec des tranches. Vous pouvez l'utiliser tant que vous n'avez pas d'énormes index et que vos index sont connus au moment de la compilation.

#![feature(slice_patterns)]

fn change(a: &mut i32, b: &mut i32) {
    let c = *a;
    *a = *b;
    *b = c;
}

fn main() {
    let mut arr = [5, 6, 7, 8];
    {
        let &mut [ref mut a, _, ref mut b, _..] = &mut arr;
        change(a, b);
    }
    assert_eq!(arr, [7, 6, 5, 8]);
}

Notez que vous devez activer la fonctionnalité slice_patterns.

8
oli_obk

Les règles d’emprunt de Rust doivent être vérifiées au moment de la compilation. C’est pourquoi l’emprunt mutuel d’une partie de Vec est un problème très difficile à résoudre (si ce n’est impossible) et pourquoi ce n’est pas possible avec Rust.

Ainsi, lorsque vous faites quelque chose comme &mut v[i], il emprunte mutuellement le vecteur entier.

Imaginez que j'ai fait quelque chose comme

let guard = something(&mut v[i]);
do_something_else(&mut v[j]);
guard.do_job();

Ici, je crée un objet guard qui stocke en interne une référence mutable à v[i], et fera quelque chose avec cela lorsque j'appellerai do_job().

En attendant, j'ai fait quelque chose qui a changé v[j]. guard contient une référence mutable censée garantir que rien d'autre ne peut modifier v[i]. Dans ce cas, tout va bien, tant que i est différent de j; si les deux valeurs sont égales, il s'agit d'une violation énorme des règles d'emprunt.

Le compilateur ne pouvant garantir ce i != j, il est donc interdit.

Ceci était un exemple simple, mais des cas similaires sont légions et expliquent pourquoi un tel accès emprunte mutuellement le conteneur entier. De plus, le compilateur n'en sait pas assez sur les éléments internes de Vec pour garantir la sécurité de cette opération, même si i != j.


Dans votre cas précis, vous pouvez consulter la méthode swap(..) disponible sur Vec qui effectue l'échange que vous implémentez manuellement.

Sur un cas plus générique, vous aurez probablement besoin d'un autre conteneur. Les possibilités englobent toutes les valeurs de votre Vec dans un type à mutabilité intérieure, tel que Cell ou RefCell , ou même en utilisant un conteneur complètement différent, comme @llogiq l'a suggéré dans sa réponse avec par-vec .

8
Levans

La méthode [T]::iter_mut() renvoie un itérateur pouvant générer une référence modifiable pour chaque élément de la tranche. D'autres collections ont aussi une méthode iter_mut. Ces méthodes encapsulent souvent du code non sécurisé, mais leur interface est totalement sécurisée.

Voici un trait d'extension à usage général qui ajoute une méthode sur les tranches qui renvoie des références mutables à deux éléments distincts par index:

pub trait SliceExt {
    type Item;

    fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item);
}

impl<T> SliceExt for [T] {
    type Item = T;

    fn get_two_mut(&mut self, index0: usize, index1: usize) -> (&mut Self::Item, &mut Self::Item) {
        match index0.cmp(&index1) {
            Ordering::Less => {
                let mut iter = self.iter_mut();
                let item0 = iter.nth(index0).unwrap();
                let item1 = iter.nth(index1 - index0 - 1).unwrap();
                (item0, item1)
            }
            Ordering::Equal => panic!("[T]::get_two_mut(): received same index twice ({})", index0),
            Ordering::Greater => {
                let mut iter = self.iter_mut();
                let item1 = iter.nth(index1).unwrap();
                let item0 = iter.nth(index0 - index1 - 1).unwrap();
                (item0, item1)
            }
        }
    }
}
3
Francis Gagné

Vous ne pouvez pas faire deux références mutables aux mêmes données. Ceci est quelque chose explicitement interdit par le vérificateur d'emprunt, pour empêcher les modifications simultanées. Cependant, vous pouvez contourner le vérificateur d'emprunt en utilisant des blocs unsafe.

Alors que dans votre cas, v[0] et v[1] sont clairement des morceaux séparés, cela ne mérite pas un examen minutieux. Et si v est une sorte de carte appelée NullMap qui mappe tous les éléments dans un seul champ? Comment le compilateur saura-t-il que Vec operationsv[0];v[1]; est sécurisé mais que NullMap ne l’est pas?


Si vous essayez d’échanger deux éléments d’un tableau, pourquoi ne pas choisir slice::swap ?

fn main() {
    let mut v = vec![1, 2, 3];
    v.swap(0,1);
    println!("{:?}",v);
}

De plus, v doit être mut, car vous changez de vecteur. Une version immuable serait clonée et échangée.

2
Daniel Fath

Le problème est que &mut v[…] emprunte mutuellement mutuellement v et donne ensuite la référence modifiable à l'élément de la fonction de changement.

This reddit comment a une solution à votre problème.

Edit: Merci pour le heads-up, Shepmaster. par-vec est une bibliothèque qui permet d'emprunter mutuellement des partitions disjointes d'un vec.

0
llogiq