Reputation: 2041
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
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