dnaq
dnaq

Reputation: 2244

generic deserialisation (type-punning) of structs (fighting with the borrow checker)

I am using packed structs and I need to be able to go from raw bytes to structs and vice versa without any decoding/encoding overhead.

I wrote some code that seemed to work:

#[packed]
struct Test {
    data: u64
}
impl Test {
    fn from_byte_slice(bs: &[u8]) -> Option<Test> {
        if bs.len() != std::mem::size_of::<Test>() {
            None
        } else {
            let p: *const u8 = &bs[0];
            let p2: *const Test = p as *const Test;
            unsafe {
                Some(*p2)
            }
        }
    }
}

However I have a couple of different structs that need to be serialized/deserialed so I wanted to use a generic function to reduce code duplication.

The following code fails to compile with the error message: "error: cannot move out of dereference of *-pointer"

fn from_byte_slice<T>(bs: &[u8]) -> Option<T> {
    if bs.len() != std::mem::size_of::<T>() {
        None
    } else {
        let p: *const u8 = &bs[0];
        let p2: *const T = p as *const T;
        unsafe {
            Some(*p2)
        }
    }
}

What is weird is that if I instead of returning an Option return an Option<&T> then the code compiles:

fn from_byte_slice<'a, T>(bs: &'a [u8]) -> Option<&'a T> {
    if bs.len() != std::mem::size_of::<T>() {
        None
    } else {
        let p: *const u8 = &bs[0];
        let p2: *const T = p as *const T;
        unsafe {
            Some(&*p2)
        }
    }
}

Am I doing something wrong or have I run into a bug in the borrow checker?

Upvotes: 0

Views: 393

Answers (1)

Manishearth
Manishearth

Reputation: 16198

The argument bs: &[u8] is a slice, and is borrowed. This is a form of temporary ownership, you can't move the data out. *p2 does just that, it moves ownership of that data out.

You need to clone it:

fn from_byte_slice<T: Clone>(bs: &[u8]) -> Option<T> {
    if bs.len() != std::mem::size_of::<T>() {
        None
    } else {
        let p: *const u8 = &bs[0];
        let p2: *const T = p as *const T;
        unsafe {
            Some((*p2).clone())
        }
    }
}

Using transmute you probably can make this work with a Vec<u8> instead, if you don't mind moving an owned vector into the function.

The direct impl in the first case works because Test contains all Copy fields and is thus implicitly copied (instead of needing the explicit clone()).

This probably will change soon, Copy will have to be explicitly derived in the future.

Upvotes: 3

Related Questions