Moving a reference between containers


#1

Hi,

For some background, I’m playing around with Rust on an embedded ARM chip - which means no_std and lots of unsafe code.

I have a buffer that is declared as a mutable static. The hardware writes data to this buffer and after completion signals the program via an interrupt.
I want to interpret this data without copying it, so I cast the pointer to the buffer to a reference to a struct.
Since the lifetime of the reference is unbound, I wrap it in another struct.
I want to be able to pass the reference from one wrapper struct to another, while keeping all the safety guarantees.
I made a simple example that demonstrates this:

static mut TMP : [u32; 20] = [0; 20];
#[derive(Debug, Copy, Clone)]
struct Static {
    f1: u32,
    f2: u32,
}

#[derive(Debug)]
struct Container<'a> {
    s: &'a mut Static,
}

impl<'a> Container<'a> {
    fn new() -> Self {
        let tmp = unsafe { &TMP };
        let ptr = &tmp[0] as *const u32;
        Container {
            s: unsafe { &mut *(ptr as *mut Static) },
        }
    }

    fn get_s(&mut self) -> &mut Static {
        self.s
    }
}

struct NewContainer<'b> {
    s1: &'b mut Static,
}

impl<'b> NewContainer<'b> {
    fn from_container(c: Container) -> Self {
        let ptr = c.s as *mut Static;
        NewContainer {
            s1: unsafe { &mut *ptr },
        }
    }
}

fn main() {
    let mut c = Container::new();
    let cs = c.get_s();
    cs.f1 = 2;
    let mut nc = NewContainer::from_container(c);
    cs.f1 = 3;
}

This works just like I want it to - the compiler correctly throws an error when trying to create NewContainer.
My two questions are:

  • Are the lifetimes on Static and Container correct?
  • Is there a better way to move the reference c.s in from_container than via unsafe casts? The way it’s done now seems kinda hacky to me.

Thanks,
Dawid


#2

It looks like you’re trying to make Container and NewContainer own exclusive access to the memory. The actual lifetime for the underlying memory is &'static mut, so just use that lifetime in your struct instead of adding a lifetime parameter. You can then pass ownership of that &'static mut reference from one struct to another without casting.

Container::new() needs to be marked unsafe for the same reason that static mut variables require unsafe – you can use it to obtain multiple &mut references that alias the same memory. You must manually uphold the invariant that it can only be called once. As long as you only create one &'static mut Static reference to the global variable, it provides exclusive access to the struct and can be moved around, as if were a Box<Static> that owned the memory .

Static::get_s is where lifetime parameters actually come in. With lifetime elision, that signature is equivalent to fn get_s<'a>(&'a mut self) -> &'a mut Static, so the lifetime of the returned reference is limited to the lifetime of the mutable reference to the Container.

The Static struct also needs #[repr(C)] or #[repr(packed)] if you care about their layout, because the compiler is free to reorder fields in structs by default. There may be alignment considerations to take into account when casting memory between types as well.