Convenient deep mutability

⚓ rust    📅 2025-06-04    👤 surdeus    👁️ 3      

surdeus

Info

This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Convenient deep mutability

Hi all, I'm building a compiler, and in various passes over the code, I need to (immutably) access a global namespace, while holding mutable references to something inside that namespace.

There's various ways I've found to get around this thus far, but I'm looking for a better solution.

A first little trick I used was to create an "IndexExcept iterator". I saw that within a single Module, Type or Constant, Instructions never refer to themselves. And so I could iterate mutably over the instructions quite while accessing the others immutably. Accessing the instruction currently in mutation panics.

pub struct IndexExcept<'buffer, ID: Copy + Eq, T: 'buffer, Buffer: Index<ID, Output = T>> {
    buf_ptr: *const Buffer,
    except: ID,
    _ph: PhantomData<&'buffer ()>,
}

impl<'buffer, ID: Copy + Eq, T: 'buffer, Buffer: Index<ID, Output = T>> Index<ID>
    for IndexExcept<'buffer, ID, T, Buffer>
{
    type Output = T;

    fn index(&self, index: ID) -> &T {
        assert!(index != self.except);
        unsafe { &(*self.buf_ptr)[index] }
    }
}

pub struct ConvenientMutableIterator<
    'buffer,
    ID: Copy + Eq,
    T: 'buffer,
    Buffer: Index<ID, Output = T>,
> where
    for<'b> &'b mut Buffer: IntoIterator<Item = (ID, &'b mut T)>,
{
    buf_ptr: *const Buffer,
    buf_iter: <&'buffer mut Buffer as IntoIterator>::IntoIter,
}

impl<'buffer, ID: Copy + Eq, T: 'buffer, Buffer: Index<ID, Output = T>> Iterator
    for ConvenientMutableIterator<'buffer, ID, T, Buffer>
where
    for<'b> &'b mut Buffer: IntoIterator<Item = (ID, &'b mut T)>,
{
    type Item = (&'buffer mut T, IndexExcept<'buffer, ID, T, Buffer>);

    fn next(&mut self) -> Option<Self::Item> {
        self.buf_iter.next().map(|(idx, v)| {
            (
                v,
                IndexExcept {
                    buf_ptr: self.buf_ptr,
                    except: idx,
                    _ph: PhantomData,
                },
            )
        })
    }
}

// Use:
for (instr, instructions) in instructions.iter_mut_convenient() {
    match instr {
        // and for instr's dependencies, we can simply use &instructions[dependency_id]
    }
}

Now, I've been very careful in this unsafe code, and I find it works well though if you find an issue with it I'd love to hear.

But my problem is one level up. When I'm initializing the type of a part of an instruction representing the port of another module, then I'd need to have mutable access to the instructions of other modules.

This is quite a conundrum. I would like to simply write and use the below:

    pub unsafe fn get_link_info_mut_unsafe(
        &mut self,
        global: GlobalUUID,
    ) -> (&mut LinkInfo, &Self) {
        let self_ptr = self as *const Linker;
        let global_link_info = match global {
            GlobalUUID::Module(md_id) => &mut self.modules[md_id].link_info,
            GlobalUUID::Type(typ_id) => &mut self.types[typ_id].link_info,
            GlobalUUID::Constant(cst_id) => &mut self.constants[cst_id].link_info,
        };
        unsafe { (global_link_info, &*self_ptr) }
    }

And given that any access I do to my types only reads from typ_expr fields, (and typ fields within the LinkInfo), and only writes to typ fields, it should be safe. But of course, I can't trust that the compiler won't blow up my code because it realizes &Self contains something that aliases with &mut LinkInfo

Thing is, I really don't want to reach for RefCell, there's lots of stuff after this in the pipeline, heavily using the typ and related fields, for whom the LinkInfo references will always be shared. And of course, I don't want to allow that code to mutate the LinkInfo in any way.

Basically, what my question comes down to is: Will the compiler blow up in my face if I do this, even though I'm never actually writing to a &mut that aliases with a & that I use? Or, should I just bite the bullet and accept RefCells everywhere?

1 post - 1 participant

Read full topic

🏷️ rust_feed