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?