Storing a mutable reference to an "inner"

I'm committing some crimes with hashbrown's RawTable. In hashbrown the HashMap::drain_filter() method returns a DrainFilter, which contains a DrainFilterInner, which contains a mutable reference to the RawTable (because while iterating over the contents of the map/table it may need to modify it).

My bastardized map (MyMap) uses RawTable, but it stores an extra data structure alongside of the RawTable. Whenever the collection is updated both structures need to be updated. This means that the DrainFilterInner needs to store a mutable reference to MyMap, which would mean:

    pub fn drain_filter<F>(&mut self, f: F) -> DrainFilter<'_, K, V, F>
    where
        F: FnMut(&K, &mut V) -> bool,
    {
        DrainFilter {
            f,
            inner: DrainFilterInner {
                iter: unsafe { self.table.iter() },
                map: &mut self,
            },
        }
    }

This is clearly a no-no. So I created an MyMapInner, and stuffed the RawTable and my alternative data structure in that, so instead it's more in line with hashbrown's HashMap:

    pub fn drain_filter<F>(&mut self, f: F) -> DrainFilter<'_, K, V, F>
    where
        F: FnMut(&K, &mut V) -> bool,
    {
        DrainFilter {
            f,
            inner: DrainFilterInner {
                iter: unsafe { self.table.iter() },
                inner_map: &mut self.inner,
            },
        }
    }

This works, but I realize I'm not sure why. It's clear why if I have a &mut self I can't store the &mut self in another structure. However, it's not clear to me why I can store a mutable reference to an inner struct of self in a foreign struct.

My mental model was "I can't alias &mut self, including anything inside it", but that's clearly insufficient.

What am I missing?

You can borrow distinct fields and return them. They don't alias (overlap). But if you tried to return both self and &mut self.table, you could reach the latter from the former -- they do alias, and that would be instant undefined behavior.

3 Likes

You shouldn't be using unsafe for circumventing the borrow checker. Under ordinary circumstances, you should limit your usage of unsafe to FFI (and maybe a few other very rare and very special cases, such as creating custom DSTs). If you are fighting the lifetime system, that is almost always an indication that you are doing it wrong, and while adding unsafe will (sometimes) make the compiler shut up, the result is basically guaranteed to be incorrect (i.e. unsound and causing undefined behavior).

Accordingly, your second example doesn't need the unsafe. It compiles just fine without. (I filled in the missing details but the point stands.)

3 Likes

I originally tried to use hashbrown::HashMap, but some of my crimes involved things that led to borrow-checker complications. I was pointed to RawTable as a solution, and it indeed did as promised, but as the "Raw" implies, it has some rather sharp edges: The unsafe comes from hashbrown's RawTable::iter() being pub unsafe fn -- it's out of my control.