Passing a Rust variable to a C function that expec

2019-04-11 15:08发布

I'm writing a safe Rust layer with which I can call functions from a C library in Rust. I've generated the unsafe bindings using rust-bindgen, but I'm getting a little confused on the differences between how Rust and C work with regards to passing pointers.

The C function looks like this:

bool imeGet(unsigned char address, int *value);

It reads an I2C sensor at address, stores the result in value, and returns TRUE on success.

Bindgen has the Rust function looking like this:

pub fn imeGet(address: ::std::os::raw::c_uchar,
              value: *mut ::std::os::raw::c_int) -> bool;

And my safe caller looks like this currently:

pub fn ime_get(addr: u8) -> i32 {
    let v: &mut i32 = 0;
    unsafe {
        imeGet(addr, v);
        *v
    }
}

This code doesn't compile because of the = 0. When I didn't have that, the compiler complained about v possibly not having been initialized. My intent is to handle the success within this function, and just return the i32 value.

How do I handle the behavior of the *mut c_int argument? I tried to declare v as a reference and return its dereferenced value (above), but that doesn't work. I also tried to just return v, but I don't really want the return value to stay mutable.

I'm pretty new to Rust, but I do have a decent background in C, which may be my source of confusion.

标签: c rust ffi
1条回答
老娘就宠你
2楼-- · 2019-04-11 15:52

but I do have a decent background in C

The moral equivalent of your Rust code is:

int *v = NULL;
imeGet(addr, v);
*v

This will have an error because the C code is likely going to dereference that v to store the value in, except you've passed in a NULL, so it's more likely to go boom.

You need to create storage for the value, then provide a reference to that storage to the function:

fn ime_get(addr: u8) -> i32 {
    let mut v = 0;
    unsafe { imeGet(addr, &mut v) };
    v
}

The solution for any pointer type uses ptr::null_mut:

unsafe { 
    let mut v = std::ptr::null_mut();
    takes_a_pointer_pointer(addr, &mut v);
    v
}

The general solution for any type uses mem::uninitialized:

unsafe { 
    let mut v = std::mem::uninitialized();
    takes_a_value_pointer(addr, &mut v);
    v
}

For completeness, you should be checking the return value:

fn ime_get(addr: u8) -> Option<i32> {
    let mut v = 0;
    let success = unsafe { imeGet(addr, &mut v) };

    if success {
        Some(v)
    } else {
        None
    }
}

the differences between how Rust and C work with regards to passing pointers.

There really aren't any, at this level.

查看更多
登录 后发表回答