-1

Let v be Vec<HashSet<usize>>.

Is it possible to update v[i] while iterating v[i - 1]?

Normally, Rust's ownership rule doesn't allow this, but I believe some way should exist since v[i] and v[i - 1] are essentially independent.

unsafe is allowed because unsafe sometimes lets us bypass (in a sense) the ownership rule. (For example, swapping values of HashMap is normally impossible, but using unsafe makes it possible. ref: swapping two entries of a HashMap)

Please assume v.len() is very large because, if v.len() is small, you can give up using Vec container in the first place.

Very artificial but minimum working example is shown below (Rust Playground). This type of source code is often seen in doing dynamic programming.

use std::collections::HashSet;

fn main() {
    let n = 100000;
    let mut v = vec![HashSet::new(); n];
    v[0].insert(0);
    for i in 1..n {
        v[i] = v[i - 1].clone(); //This `clone()` is necessarily.
        let prev = v[i - 1].clone(); //I want to eliminate this `clone()`.
        prev.iter().for_each(|e| {
            v[i].insert(*e + 1);
        })
    }
    println!("{:?}", v); //=> [{0}, {0, 1}, {0, 1, 2}, {0, 1, 2, 3}, ...]
}
ynn
  • 3,386
  • 2
  • 19
  • 42

1 Answers1

1

When you modify a vector with v[i], you are using the IndexMut trait, which requires a mutable borrow to Self, ie. the whole vector. For this reason, Rust will never allow taking v[i] and v[i-1] at the same time, if at least one of them is a mutable borrow.

To solve this issue, you must work a little harder to make Rust understand v[i] and v[i-1] are not aliased (because, in the end, all the borrow checking stuff ends up in LLVM being able to tell if something is aliased, or not).

The "bad" news is that it's impossible to do so without relying on unsafe somewhere. The good news is that someone else already did that, and wrapped it in a safe interface, namely split_at_mut. This will break a single vector into two subslices, which are guaranteed to be disjoint (this is where unsafe kicks in).

So, for instance, in your case, you could do

use std::collections::HashSet;

fn main() {
    let n = 100000;
    let mut v = vec![HashSet::new(); n];
    v[0].insert(0);
    for i in 1..n {
        v[i] = v[i - 1].clone(); //This `clone()` is necessarily.
        let (left, right) = v.split_at_mut(i);
        left[i-1].iter().for_each(|e| {
            right[0].insert(*e + 1);
        })
    }
    println!("{:?}", v); //=> [{0}, {0, 1}, {0, 1, 2}, {0, 1, 2, 3}, ...]
}

See the playground.


Besides, maybe this is just because your example is simplified, but there is actually no point in creating 100000 HashMaps, if you just modify them right away. A simpler solution would be

use std::collections::HashSet;

fn main() {
    let n = 100000;
    let mut v = Vec::with_capacity(n);
    v.insert(HashSet::from([0]));
    for i in 0..n-1 {
        let mut new_set = v[i].clone();
        for e in v[i].iter().copied() {
            new_set.insert(e+1);
        }
        v.push(new_set);
    }
    println!("{:?}", v);
}

See the playground.

jthulhu
  • 7,223
  • 2
  • 16
  • 33