Search code examples
rustmutableunsafe

Mapping a struct to an underlying buffer


I am attempting to map a simple struct to an underlying buffer as follows, where modifying the struct also modifies the buffer:

#[repr(C, packed)]
pub struct User {
    id: u8,
    username: [u8; 20],
}

fn main() {
    let buf = [97u8; 21];
    let mut u: User = unsafe { std::ptr::read(buf.as_ptr() as *const _) };

    let buf_addr = &buf[0] as *const u8;
    let id_addr = &u.id as *const u8;
    println!("buf addr: {:p} id addr: {:p} id val: {}", buf_addr, id_addr, u.id);
    assert_eq!(buf_addr, id_addr); // TODO addresses not equal

    u.id = 10;
    println!("id val: {}", u.id);
    println!("{:?}", &buf);
    assert_eq!(buf[0], u.id); // TODO buffer not updated
}

However the starting address of the buffer is different to the address of the first member in the struct and modifying the struct does not modify the buffer. What is wrong with the above example?


Solution

  • The struct only contains owned values. That means that, in order to construct one, you have to copy data into it. And that is exactly what you are doing when you use ptr::read.

    But what you want to do (at least the code presented) is not possible. If you work around Rust's safety checks with unsafe code then you would have two mutable references to the same data, which is Undefined Behaviour.

    You can, however, create a safe API of mutable "views" onto the data, something like this:

    #[repr(C, packed)]
    pub struct User {
        id: u8,
        username: [u8; 20],
    }
    
    pub struct RawUser {
        buf: [u8; 21],
    }
    
    impl RawUser {
        pub fn as_bytes_mut(&mut self) -> &mut [u8; 21] {
            &mut self.buf
        }
        
        pub fn as_user_mut(&mut self) -> &mut User {
            unsafe { &mut *(self.buf.as_mut_ptr() as *mut _) }
        }
    }
    

    These accessors let you view the same data in different ways, while allowing the Rust borrow checker to enforce memory safety. Usage looks like this:

    fn main() {
        let buf = [97u8; 21];
        let mut u: RawUser = RawUser { buf };
        
        let user = u.as_user_mut();
        user.id = 10;
        println!("id val: {}", user.id); // id val: 10
    
        let bytes = u.as_bytes_mut();
        // it would be a compile error to try to access `user` here
        assert_eq!(bytes[0], 10);
    }