Search code examples
rustthread-local

How do I get a simple mutable thread-local struct in Rust?


I'm building an interpreter with a garbage collector. I want a thread-local nursery region, and a shared older region. I am having trouble setting up the nursery. I have:

const NurserySize : usize = 25000;
#[thread_local]
static mut NurseryMemory : [usize;NurserySize] = [0;NurserySize];
thread_local! {
    static Nursery: AllocableRegion = AllocableRegion::makeLocal(unsafe{&mut NurseryMemory});
}
#[cfg(test)]
mod testMemory {
    use super::*;
    #[test]
    fn test1() {
        Nursery.with(|n| n.allocObject(10));
    }
}

First question is why do I need the unsafe - NurseryMemory is thread local, so access can't be unsafe?

Second question is how can I actually use this? The code is at playground, but it doesn't compile and attempts I've made to fix it seem to make it worse.


Solution

  • 1. Why is unsafe required to get a reference to a mutable ThreadLocal?

    The same reason that you need unsafe for a normal mutable static, you would be able to create aliasing mut pointers in safe code. The following incorrectly creates two mutable references to the mutable thread local.

    #![feature(thread_local)]
    
    #[thread_local]
    static mut SomeValue: Result<&str, usize> = Ok("hello world");
    
    pub fn main() {
    
    let first = unsafe {&mut SomeValue};
    let second = unsafe {&mut SomeValue};
    
      if let Ok(string) = first {
        *second = Err(0); // should invalidate the string reference, but it doesn't 
        println!("{}", string) // as first and second are considered to be disjunct
      } 
      
    }
    

    first wouldn't even need to be a mutable reference for this to be a problem.

    2. How to fix the code?

    You could use a RefCell around the AllocatableRegion to dynamically enforce the borrowing of the inner value.

    const NurserySize : usize = 25000;
    #[thread_local]
    static mut NurseryMemory : [usize;NurserySize] = [0;NurserySize];
    
    thread_local! {
        static Nursery: RefCell<AllocableRegion> = RefCell::new(AllocableRegion::makeLocal(unsafe{&mut NurseryMemory}));
    }
    
    #[cfg(test)]
    mod testMemory {
        use super::*;
        #[test]
        fn test1() {
            Nursery.with(|n| n.borrow_mut().allocObject(10));
        }
    }