I'm having a hard time wrapping my head around declaring mutable (or pointer) variables and interacting with C code through FFI. I've been playing with this for most of the day and have found conflicting examples due to how quickly Rust is developing. :)
The situation is like this: I have a C function which takes in a pointer to a struct
, this struct has fields that are int
s and char *
s. My understanding is that I need to declare a similar struct in Rust to pass to the extern C
function.
Here are my example files I've written while trying to figure this out:
main.rs
extern crate libc;
struct testStruct {
an_int: libc::c_int,
a_string: *mut libc::c_char
}
extern {
fn start_test(test: *mut testStruct) -> libc::c_int;
}
fn main() {
// println!("Hello, world!");
let test_struct = testStruct { an_int: 1, a_string: "hello" };
start_test(&mut test_struct);
}
--
test_file.c
#include <stdio.h>
#include "test_file.h"
struct test_struct {
int an_int;
char *a_string;
};
int start_client(struct test_struct *test) {
printf("Test function!\n");
return 0;
}
Obviously the actual code is more complex, I'm just trying to get a basic example working to understand how mutability/pointers work in Rust with FFI.
What is the correct way to declare a structure, or just a variable, in Rust that can be passed to C code expecting a pointer?
The memory layout of a struct is undefined (the compiler is allowed to reorder fields, for instance) unless you add the #[repr(C)]
attribute to the struct. This attribute gives the struct a layout compatible with C.
#[repr(C)]
struct TestStruct {
an_int: libc::c_int,
a_string: *mut libc::c_char
}
Using a raw pointer in the struct works fine, but we can do better. There are two other important types in Rust that are only composed of a pointer: borrowed pointers (&'a T
or &'a mut T
) and Box<T>
. You can use these types instead *const T
or *mut T
to make it clear that the pointer borrows an existing value (and enables the compiler to validate that the pointer doesn't outlive its referent) or points to an object on the heap that should be dropped when the pointer (or the struct containing it) goes out of scope. However, be careful with Box<T>
, since you could accidentally free a value while the C code still has a pointer to the value.
#[repr(C)]
struct TestStruct<'a> {
an_int: libc::c_int,
a_string: &'a mut libc::c_char
}
Another thing to watch out for is the use of fat pointers. In Rust, some pointer types are fat, i.e. they carry additional data along with the pointer. For example, slices (*const [T]
, *mut [T]
, &'a [T]
, &'a mut [T]
) can be thought of as a struct or tuple containing a pointer to the first item and the number of items in the slice (a usize
); trait objects (*const T
, *mut T
, &'a T
, &'a mut T
where T
is the name of a trait) are composed of a pointer to the object and a pointer to the virtual method table for the trait implementation. You should avoid using these types when defining a Rust struct matching a C struct.
You can find more information on using Rust's FFI in the FFI section of the Rust book.