0

I have a Vec of optional HashSets. If the slot is None, I want to allocate a HashSet and store it in the Vec. In both cases, I want to add to the HashSet.

I understand what the compiler error is complaining about but not what syntax will make the problem go away. The compiler's suggested change also does not compile, as well as any other syntax I try. How do I match the Vec cell, check if it is None, allocate a Some<HashSet> if it is None or just access the HashSet if it does exist and in both cases add in a new integer to the set?

THE CODE:

use std::collections::HashSet;
use std::collections::BTreeSet;
use std::iter::FromIterator;
use maplit;

pub struct Graph {
    outgoing_edges : Vec<Option<HashSet<usize>>>,
    incoming_edges : Vec<Option<HashSet<usize>>>
}

impl Graph {
    pub fn new(node_count : usize) -> Self {
        Graph {
            outgoing_edges : vec!(None; node_count),
            incoming_edges : vec!(None; node_count)
        }
    }

    /// Add a directional edge that starts at `from_node` and points to `to_node`.
    pub fn add_edge(&mut self, from_node : usize, to_node : usize) {
        match &self.outgoing_edges[from_node] {
            Some(mut set) => { set.insert(to_node); () },
            None => { self.outgoing_edges[from_node] = Some(hashset!{ to_node }); () }
        }
        match &self.incoming_edges[to_node] {
            Some(mut set) => { set.insert(from_node); () },
            None => { self.incoming_edges[to_node] = Some(hashset!{ from_node }); () }
        }
    }
}

THE ERROR:

(Line numbers are from my original file, not the short code snippet. The error message is from before I added the ampersand to borrow, as shown above, but that code change didn't work.)

error[E0507]: cannot move out of index of `std::vec::Vec<std::option::Option<std::collections::HashSet<usize>>>`
  --> src\graph\mod.rs:46:15
   |
46 |         match self.outgoing_edges[from_node] {
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider borrowing here: `&self.outgoing_edges[from_node]`
47 |             Some(mut set) => { set.insert(to_node); () },
   |                  -------
   |                  |
   |                  data moved here
   |                  move occurs because `set` has type `std::collections::HashSet<usize>`, which does not implement the `Copy` trait
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Paul Chernoch
  • 5,275
  • 3
  • 52
  • 73
  • It's hard to answer your question because it doesn't include a [MRE]. There are imports that could be removed or replaced (e.g. maplit isn't needed). It would make it easier for us to help you if you try to reproduce your error on the [Rust Playground](https://play.rust-lang.org) if possible, otherwise in a brand new Cargo project, then [edit] your question to include the additional info. There are [Rust-specific MRE tips](//stackoverflow.com/tags/rust/info) you can use to reduce your original code for posting here. Thanks! – Shepmaster Oct 07 '19 at 14:38
  • It looks like your question might be answered by the answers of [How to update-or-insert on a Vec?](https://stackoverflow.com/q/47395171/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Oct 07 '19 at 14:39
  • 2
    _"Line numbers are from my original file"_ — but also `match self.outgoing_edges[from_node]` (in the error) doesn't appear at all in the given code. – Peter Hall Oct 07 '19 at 14:39

2 Answers2

3

Instead of using pattern matching, you can use the method Option::get_or_insert_with() to create a new hash set if required, and return a reference to either the existing or the newly created hash set. The full code could look like this:

pub fn add_edge(&mut self, from_node: usize, to_node: usize) {
    self.outgoing_edges[from_node]
        .get_or_insert_with(HashSet::new)
        .insert(to_node);
    self.incoming_edges[to_node]
        .get_or_insert_with(HashSet::new)
        .insert(from_node);
}
Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
2

This is pretty straightforward, you want to bind by reference:

/// Add a directional edge that starts at `from_node` and points to `to_node`.
pub fn add_edge(&mut self, from_node : usize, to_node : usize) {
    match self.outgoing_edges[from_node] {
        Some(ref mut set) => { set.insert(to_node); },
        None => { self.outgoing_edges[from_node] = Some(hashset!{ to_node }); }
    }
    match self.incoming_edges[to_node] {
        Some(ref mut set) => { set.insert(from_node); },
        None => { self.incoming_edges[to_node] = Some(hashset!{ from_node }); }
    }
}

Or with pattern matching ergonomics feature called binding mode, you can also do the following:

/// Add a directional edge that starts at `from_node` and points to `to_node`.
pub fn add_edge(&mut self, from_node : usize, to_node : usize) {
    match &mut self.outgoing_edges[from_node] {
        Some(set) => { set.insert(to_node); },
        None => { self.outgoing_edges[from_node] = Some(hashset!{ to_node }); }
    }
    match &mut self.incoming_edges[to_node] {
        Some(set) => { set.insert(from_node); },
        None => { self.incoming_edges[to_node] = Some(hashset!{ from_node }); }
    }
}
edwardw
  • 12,652
  • 3
  • 40
  • 51
  • thanks! I tried the ref before, but only together with the ampersand that the compiler suggested, which didn't work. Too many orthogonal concepts and keywords to play with! Like I need an A* path finder just to find the right syntax... – Paul Chernoch Oct 07 '19 at 14:50
  • 1
    @PaulChernoch Rust reference is the ultimate guide for such syntax and semantics if you don't mind some BNFs. In this case, it is [identifier patterns](https://doc.rust-lang.org/reference/patterns.html#identifier-patterns). – edwardw Oct 07 '19 at 15:06