web-dev-qa-db-fra.com

Impossible d'emprunter en tant que mutable car il est également emprunté en tant qu'immuable

J'apprends Rust et je ne comprends pas très bien pourquoi cela ne fonctionne pas.

#[derive(Debug)]
struct Node {
    value: String,
}

#[derive(Debug)]
pub struct Graph {
    nodes: Vec<Box<Node>>,
}

fn mk_node(value: String) -> Node {
    Node { value }
}

pub fn mk_graph() -> Graph {
    Graph { nodes: vec![] }
}

impl Graph {
    fn add_node(&mut self, value: String) {
        if let None = self.nodes.iter().position(|node| node.value == value) {
            let node = Box::new(mk_node(value));
            self.nodes.Push(node);
        };
    }

    fn get_node_by_value(&self, value: &str) -> Option<&Node> {
        match self.nodes.iter().position(|node| node.value == *value) {
            None => None,
            Some(idx) => self.nodes.get(idx).map(|n| &**n),
        }
    }
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn some_test() {
        let mut graph = mk_graph();

        graph.add_node("source".to_string());
        graph.add_node("destination".to_string());

        let source = graph.get_node_by_value("source").unwrap();
        let dest = graph.get_node_by_value("destination").unwrap();

        graph.add_node("destination".to_string());
    }
}

( aire de jeux )

Cela a l'erreur

error[E0502]: cannot borrow `graph` as mutable because it is also borrowed as immutable
  --> src/main.rs:50:9
   |
47 |         let source = graph.get_node_by_value("source").unwrap();
   |                      ----- immutable borrow occurs here
...
50 |         graph.add_node("destination".to_string());
   |         ^^^^^ mutable borrow occurs here
51 |     }
   |     - immutable borrow ends here

Cet exemple de Programmation Rust est assez similaire à ce que j'ai, mais cela fonctionne:

pub struct Queue {
    older: Vec<char>,   // older elements, eldest last.
    younger: Vec<char>, // younger elements, youngest last.
}

impl Queue {
    /// Push a character onto the back of a queue.
    pub fn Push(&mut self, c: char) {
        self.younger.Push(c);
    }

    /// Pop a character off the front of a queue. Return `Some(c)` if there /// was a character to pop, or `None` if the queue was empty.
    pub fn pop(&mut self) -> Option<char> {
        if self.older.is_empty() {
            if self.younger.is_empty() {
                return None;
            }

            // Bring the elements in younger over to older, and put them in // the promised order.
            use std::mem::swap;
            swap(&mut self.older, &mut self.younger);
            self.older.reverse();
        }

        // Now older is guaranteed to have something. Vec's pop method // already returns an Option, so we're set.
        self.older.pop()
    }

    pub fn split(self) -> (Vec<char>, Vec<char>) {
        (self.older, self.younger)
    }
}

pub fn main() {
    let mut q = Queue {
        older: Vec::new(),
        younger: Vec::new(),
    };

    q.Push('P');
    q.Push('D');

    assert_eq!(q.pop(), Some('P'));
    q.Push('X');

    let (older, younger) = q.split(); // q is now uninitialized.
    assert_eq!(older, vec!['D']);
    assert_eq!(younger, vec!['X']);
}
9
Shulhi Sapli

Un MCVE de votre problème peut être réduit à ceci:

fn main() {
    let mut items = vec![1];
    let item = items.last();
    items.Push(2);
}
error[E0502]: cannot borrow `items` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let item = items.last();
  |                ----- immutable borrow occurs here
4 |     items.Push(2);
  |     ^^^^^ mutable borrow occurs here
5 | }
  | - immutable borrow ends here

Vous rencontrez le problème exact que Rust a été conçu pour empêcher. Vous avez une référence pointant dans le vecteur et êtes tentative d'insertion dans le vecteur. Cela pourrait nécessiter une réallocation de la mémoire du vecteur, invalidant toutes les références existantes. Si cela se produisait et que vous utilisiez la valeur dans item, vous accéderiez à la mémoire non initialisée, potentiellement provoquant un crash.

Dans ce cas ( particulier , vous n'utilisez pas réellement item (ou source, dans l'original) donc vous pourrait juste ... pas appeler cette ligne. Je suppose que vous l'avez fait pour une raison quelconque, vous pouvez donc envelopper les références dans un bloc afin qu'elles disparaissent avant d'essayer de muter à nouveau la valeur:

fn main() {
    let mut items = vec![1];
    {
        let item = items.last();
    }
    items.Push(2);
}

Cette astuce n'est plus nécessaire dans Rust 2018 car des durées de vie non lexicales ont été implémenté, mais la restriction sous-jacente demeure - vous ne pouvez pas avoir de référence mutable tant qu'il existe d'autres références à la même chose. C'est l'une des règles de références couvertes par Le langage de programmation Rust . Un exemple modifié qui ne fonctionne toujours pas avec NLL:

let mut items = vec![1];
let item = items.last();
items.Push(2);
println!("{:?}", item);

Dans d'autres cas, vous pouvez copier ou cloner la valeur dans le vecteur. L'article ne sera plus une référence et vous pourrez modifier le vecteur comme bon vous semble:

fn main() {
    let mut items = vec![1];
    let item = items.last().cloned();
    items.Push(2);
}

Si votre type n'est pas clonable, vous pouvez le transformer en une valeur comptée par référence (telle que Rc ou Arc ) qui peut ensuite être cloné. Vous devrez peut-être également utiliser mutabilité intérieure :

struct NonClone;

use std::rc::Rc;

fn main() {
    let mut items = vec![Rc::new(NonClone)];
    let item = items.last().cloned();
    items.Push(Rc::new(NonClone));
}

cet exemple de programmation Rust est assez similaire

Non, ce n'est pas le cas, car il n'utilise pas du tout de références.

Voir également

17
Shepmaster

Essayez de placer votre emprunt immuable dans un bloc {...}. Cela met fin à l'emprunt après le bloc.

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn some_test() {
        let mut graph = mk_graph();

        graph.add_node("source".to_string());
        graph.add_node("destination".to_string());

        {
            let source = graph.get_node_by_value("source").unwrap();
            let dest = graph.get_node_by_value("destination").unwrap();
        }

        graph.add_node("destination".to_string());
    }
}
2
Jan