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.
Parameter values can be passed by reference by prefixing the variable name with an & . In the example given below, we have a variable no, which is initially 5. A reference to the variable no is passed to the mutate_no_to_zero() function. The function operates on the original variable.
Keyword fnA function or function pointer. Functions are the primary way code is executed within Rust. Function blocks, usually just called functions, can be defined in a variety of different places and be assigned many different attributes and modifiers.
We define a function in Rust by entering fn followed by a function name and a set of parentheses. The curly brackets tell the compiler where the function body begins and ends. We can call any function we've defined by entering its name followed by a set of parentheses.
In Rust, a function pointer type, is either fn(Args...) -> Ret , extern "ABI" fn(Args...)
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::MaybeUninit
:
unsafe {
let mut v = std::mem::MaybeUninit::uninit();
takes_a_value_pointer(addr, v.as_mut_ptr());
v.assume_init()
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With