Allow C to fill struct created in rust

Hello,

I'm trying to implement a plugin lib that interfaces with C code.

The C API provides a function that takes a pointer + length combo, fills the array and returns:

int   ReadSomething(char* buff, unsigned int buff_size);

I have a #[repr(C)] struct (generated by bindgen) definition.
I'm trying to write a function a function that:

  1. Creates an empty struct of type T (on stack or heap).
  2. sends a pointer to the c executable.
  3. Returns the struct to whoever called it.

This is what I've come up with:

pub fn read_mem<T: Default>(addr: u32) -> Result<T, i32> {
    let mut buff = T::default();
    let ptr = &mut buff as *mut T;

    unsafe {
        match API.pfReadMem {
            Some(f) => match f(addr, ptr as *mut c_char, std::mem::size_of::<T>() as c_uint) {
                OK => Ok(buff),
                e => Err(e),
            },
            None => Err(ERR),
        }
    }
}

also similar code for WriteMem:

pub unsafe fn write_mem<T>(addr: u32, data: &T) -> Result<(), i32> {
    {
        match API.pfWriteMem {
            None => Err(ERR),
            Some(f) => match f(
                addr,
                data as *const T as *const c_char,
                std::mem::size_of::<T>() as c_uint,
            ) {
                OK => Ok(()),
                e => Err(e),
            },
        }
    }
}

Note: API is a static struct that holds pointers to c functions, it's provided by the C code earlier in execution.

Do you guys see any immediate problems?
Might there be an early deallocation problem created by the borrow that creates ptr?

Can I enforce that T is a struct that's #[repr(C)]?

Thanks a lot.

Your implementation seems fine (assuming pfReadMem won't perform a partial write). But read_mem should be unsafe because it can be used to create an invalid value (such as a dangling reference).

read_mem can be improved by using MaybeUninit. It removes Default requirement:

pub unsafe fn read_mem<T>(addr: u32) -> Result<T, i32> {
    let mut value = MaybeUninit::uninit();

    match API.pfReadMem {
        Some(f) => match f(addr, value.as_mut_ptr() as *mut c_char, std::mem::size_of::<T>() as c_uint) {
            OK => Ok(value.assume_init()),
            e => Err(e),
        },
        None => Err(ERR),
    }
}

Might there be an early deallocation problem created by the borrow that creates ptr ?

No, a local variable won't be dropped until the execution exits from the block containing the variable declaration.

Can I enforce that T is a struct that's #[repr(C)] ?

No. However, zerocopy crate defines FromBytes trait to provide a type-safe way to distinguish such types you can use with read_mem. For more information, see the documentation of the crate.

1 Like

Thanks a lot!