user422005
user422005

Reputation: 2041

Take ownership to mutable reference in unsafe block

I am wrapping an existing C library in Rust. The C library has a public structure like this:

struct {
   int flag;
   float * buffer;
} data_type;

and it has an API like:

void init_struct(struct data_type * data, int flag, float * buffer);
void run(struct data_type * data, const float * input_data, float * output_data); 

i.e. a typical C usage of this would be something like:

int main {
    int flag = 32;
    float * buffer = malloc( 1000*sizeof * float);
    {
        struct data_type data;
        init_struct(&data, flag, buffer);
        float input_data[64];
        float ouput_data[64];
        ... 
        ...
        run(&data, input_data, output_data);
        ...
    }
    free(buffer);
}

In rust I want to create a (tuple) struct wrapping the C struct data_type. The following roughly works:

#[repr(C)]
pub struct data_type {
    flag : u32,
    buffer : *mut f32,
}

extern "C" {
    pub fn init_struct(
        S: *mut data_struct,
        input_data: i32,
        buffer: *mut f32);

    pub fn run(
        S: *const data_struct,
        input_data: *const f32,
        output_data: *mut f32);
}

pub struct Data(data_type);

impl Data {
    pub fn new(flag: u32, buffer: &mut [f32]) -> Self {
        let mut data = MaybeUninit::<data_type>::uninit();
        unsafe {
            init_struct(data.as_mut_ptr(), flag, buffer.as_mut_ptr());
            FloatFIR(data.assume_init())
        }
    }

    pub fn run(&self, input: &[f32], output: &mut [f32]) {
        // ....
    }

}

However, as you can see from the code, I pass a mutable reference/pointer down to the C api, i.e. I would like the Rust borrow checker to ensure that the buffer can only be used in one Data instance at a time; but I guess that since the buffer is tucked away to C inside an unsafe section the borrow checker does not see that the Data instance holds on the buffer, and after Data::new() has returned it is happy to pass out new mutable refernces to the buffer?

Can I in some way explicitly say in the Data::new(), that for it's entire lifetime the Data instance will hold on to the supplied mutable buffer argument?

The code as posted is a simplified version and not Copy & Paste, i.e. it will probably not compile. However I hope it illustrates the problem/question?

Upvotes: 0

Views: 133

Answers (1)

cafce25
cafce25

Reputation: 27550

You have to tie the lifetime of the borrow to the Data struct somehow, one possible solution is to just store the mutable slice alongside the C ffi struct:

pub struct Data<'a>(data_type, &'a mut [f32]);

impl<'a> Data<'a> {
    pub fn new(flag: u32, buffer: &'a mut [f32]) -> Self {
        let mut data = MaybeUninit::<data_type>::uninit();
        Data(
            unsafe {
                init_struct(data.as_mut_ptr(), flag, buffer.as_mut_ptr());
                data.assume_init()
            },
            buffer,
        )
    }
}

If you need to avoid the overhead of that pointer + length you can use std::marker::PhantomData instead:

use std::marker::PhantomData;
pub struct Data<'a>(data_type, PhantomData<&'a mut [f32]>);

impl<'a> Data<'a> {
    pub fn new(flag: u32, buffer: &'a mut [f32]) -> Self {
        let mut data = MaybeUninit::<data_type>::uninit();
        Data(
            unsafe {
                init_struct(data.as_mut_ptr(), flag, buffer.as_mut_ptr());
                data.assume_init()
            },
            PhantomData,
        )
    }
}

Upvotes: 2

Related Questions