3

I have a data structure like this:

struct R {
    hmhs: HashMap<i64, HashSet<i64>>,
}

impl R {
    fn hs_for_hmhs(&mut self) -> &mut HashSet<i64> {
        if let None = self.hmhs.get(&0) {
            self.hmhs.insert(0, HashSet::new());
        }

        self.hmhs.get_mut(&0).unwrap()
    }

    fn iter_for_hmhs<'a>(&'a mut self) -> impl Iterator<Item = &'a i64> {
        self.hs_for_hmhs().iter()
    }

    fn insert_for_hmhs(&mut self, i: i64) -> bool {
        self.hs_for_hmhs().insert(i)
    }
}

This seems to work, but all the methods require a mutable reference to self which is unfortunate. I tried to give interior mutability a go:

struct S {
    hmhs: RefCell<HashMap<i64, HashSet<i64>>>,
}

impl S {
    fn hs_for_hmhs(&self) -> &HashSet<i64> {
        if let None = self.hmhs.borrow().get(&0) {
            self.hmhs.borrow_mut().insert(0, HashSet::new());
        }

        self.hmhs.borrow_mut().get_mut(&0).unwrap()
    }

    fn iter_for_hmhs(&mut self) -> impl Iterator<Item = &i64> {
        self.hs_for_hmhs().iter()
    }

    fn insert_for_hmhs(&mut self, i: i64) -> bool {
        self.hs_for_hmhs().insert(i)
    }
}

However, I constantly seem to hit problems. Mostly some variety of How do I return a reference to something inside a RefCell without breaking encapsulation?

I have tried lots of variants here, but I am missing something fundamental in my understanding. Is there a way of achieving what I want?

Complete Code:

use std::cell::RefCell;
use std::collections::{HashMap, HashSet};

struct R {
    hmhs: HashMap<i64, HashSet<i64>>,
}

impl R {
    fn hs_for_hmhs(&mut self) -> &mut HashSet<i64> {
        if let None = self.hmhs.get(&0) {
            self.hmhs.insert(0, HashSet::new());
        }

        self.hmhs.get_mut(&0).unwrap()
    }

    fn iter_for_hmhs<'a>(&'a mut self) -> impl Iterator<Item = &'a i64> {
        self.hs_for_hmhs().iter()
    }

    fn insert_for_hmhs(&mut self, i: i64) -> bool {
        self.hs_for_hmhs().insert(i)
    }
}

struct S {
    hmhs: RefCell<HashMap<i64, HashSet<i64>>>,
}

impl S {
    fn hs_for_hmhs(&self) -> &mut HashSet<i64> {
        if let None = self.hmhs.borrow().get(&0) {
            self.hmhs.borrow_mut().insert(0, HashSet::new());
        }

        self.hmhs.borrow_mut().get_mut(&0).unwrap()
    }

    fn iter_for_hmhs(&self) -> impl Iterator<Item = &i64> {
        self.hs_for_hmhs().iter()
    }

    fn insert_for_hmhs(&self, i: i64) -> bool {
        self.hs_for_hmhs().insert(i)
    }
}

fn main() {}

Compiler Message:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:36:9
   |
36 |         self.hmhs.borrow_mut().get_mut(&0).unwrap()
   |         ^^^^^^^^^^^^^^^^^^^^^^ temporary value does not live long enough
37 |     }
   |     - temporary value only lives until here
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the method body at 31:5...
  --> src/main.rs:31:5
   |
31 | /     fn hs_for_hmhs(&self) -> &mut HashSet<i64> {
32 | |         if let None = self.hmhs.borrow().get(&0) {
33 | |             self.hmhs.borrow_mut().insert(0, HashSet::new());
34 | |         }
35 | |
36 | |         self.hmhs.borrow_mut().get_mut(&0).unwrap()
37 | |     }
   | |_____^
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Phil Lord
  • 2,917
  • 1
  • 20
  • 31
  • Why do you need to borrow it mutably? All of the methods return immutable references to its contents? – Peter Hall Sep 05 '18 at 14:55
  • Which? The `HashMap` and the second `borrow_mut` call. I don't know. It complains more if I don't. – Phil Lord Sep 05 '18 at 15:03
  • Ah, I didn't notice that `insert_for_hmhs` calls `insert`. – Peter Hall Sep 05 '18 at 15:05
  • Oh, the `HashSet` -- yes, this part will fulfil the public interface of the struct. The HashMap is an implementation detail really. – Phil Lord Sep 05 '18 at 15:08
  • This is a tricky problem, and I wonder if you should try a different approach. For example, [this](https://play.rust-lang.org/?gist=208ec1166fe2519d06b44194620b8912&version=stable&mode=debug&edition=2015) will get you half way - an abstracted smart pointer to the `HashSet`. But it's difficult to return an iterator to that. – Peter Hall Sep 05 '18 at 15:13
  • Probably you'll need to create an iterator over a cloned `HashSet` – Peter Hall Sep 05 '18 at 15:24
  • It's frustrating. The implementation is poking through. I could pre-instantiate the HashMap (actually possible in this case) I guess. But that's also unfortunate. It really should be lazy. – Phil Lord Sep 05 '18 at 20:47
  • Is the key (0) in `hs_for_hmhs` fixed, or is this a simplification of the question and it may vary? – Matthieu M. Sep 06 '18 at 11:30
  • It's a simplification. Actually, the key is not a int, but values defined at compile time, so it's bounded. I could instantiate everything first. But there are 50-100 keys and in many cases, the HashSet values will be empty for many of these 50. So I'd rather not if I can avoid it. – Phil Lord Sep 06 '18 at 16:09

1 Answers1

2

I found a solution -- extract the HashMap as a raw pointer. This in turn means that I can get to the HashSet without shenanigans including returning a iterator.

I'm happy enough with this as a solution. The unsafe code is small and contained and if I understand the reason why the compiler is complaining without unsafe, it cannot occur in this code, since neither the HashMap nor the HashSet are ever removed or replaced after construction.

That was a lot of effort.

use std::cell::RefCell;
use std::collections::{HashMap, HashSet};

struct R {
    hmhs: HashMap<i64, HashSet<i64>>,
}

impl R {
    fn hs_for_hmhs(&mut self) -> &mut HashSet<i64> {
        if let None = self.hmhs.get(&0) {
            self.hmhs.insert(0, HashSet::new());
        }

        self.hmhs.get_mut(&0).unwrap()
    }

    fn iter_for_hmhs<'a>(&'a mut self) -> impl Iterator<Item = &'a i64> {
        self.hs_for_hmhs().iter()
    }

    fn insert_for_hmhs(&mut self, i: i64) -> bool {
        self.hs_for_hmhs().insert(i)
    }
}

struct S {
    hmhs: RefCell<HashMap<i64, HashSet<i64>>>,
}

impl S {
    fn hs_as_ptr(&self) -> *mut HashMap<i64, HashSet<i64>> {
        self.hmhs.borrow_mut().entry(0).or_insert(HashSet::new());
        self.hmhs.as_ptr()
    }

    fn mut_hs_for_hmhs(&mut self) -> &mut HashSet<i64> {
        unsafe { (*self.hs_as_ptr()).get_mut(&0).unwrap() }
    }
    fn hs_for_hmhs(&self) -> &HashSet<i64> {
        unsafe { (*self.hs_as_ptr()).get(&0).unwrap() }
    }

    fn iter_for_hmhs<'a>(&'a self) -> impl Iterator<Item = &'a i64> + 'a {
        self.hs_for_hmhs().iter()
    }

    fn insert_for_hmhs(&mut self, i: i64) -> bool {
        self.mut_hs_for_hmhs().insert(i)
    }
}

fn main() {
    let mut r = R {
        hmhs: HashMap::new(),
    };
    let mut s = S {
        hmhs: RefCell::new(HashMap::new()),
    };

    r.insert_for_hmhs(10);
    s.insert_for_hmhs(20);

    println!("r next: {:?}", r.iter_for_hmhs().next());
    println!("s next: {:?}", s.iter_for_hmhs().next());
}

https://play.rust-lang.org/?gist=3ed1977bdd5f9f82d144fe128f618979&version=stable&mode=debug&edition=2015

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Phil Lord
  • 2,917
  • 1
  • 20
  • 31