Due to niche optimization, Rust will guarentee that the following types will all have the same layout in memory. However, each option comes with different guarentees and should be selected accordingly.
&T
&mut T
NonNull<T>
*mut T
, but non-zero and covariant
Box<T>
- Same aliasing rules as
&mut T
- Same aliasing rules as
*const T
*mut T
Option<&T>
Option<&mut T>
Option<NonNull<T>>
Option<Box<T>>
When using a wrapper struct, annotate it with #[repr(transparent)]
to guarentee that niche optimizations can stil be applied to the wrapped type.
#[repr(transparent)]
pub struct Foo<'a> (&'a i32);
pub struct Bar<'a> (&'a i32);
// Always true
assert_eq!(size_of::<*const Foo>(), size_of::<Option<Foo>>());
// Probably true, but no guarentees are given
assert_eq!(size_of::<*const Foo>(), size_of::<Option<Bar>>());
Lets say we need to implement the functions exported by this header.
struct Foo;
struct Foo *foo_alloc(void);
void foo_free(struct Foo *);
int foo_get_x(struct Foo *);
void foo_set_x(struct Foo *, int x);
In this case, we know that C code will never attempt to read/write/move Foo
so we can let Rust manage the memory. This can all be mirrored in safe Rust using the following approach. One big benefit of this is thta it does not require any unsafe and will also work when called from Rust with no added complications.
use std::ffi::c_int;
pub struct Foo {
x: i32,
}
impl Foo {
#[export_name = "foo_alloc"]
pub extern "C" fn new_boxed() -> Box<Self> {
Box::new(Foo {
x: 5,
})
}
// Use an option here because many libraries support passing null. We don't have to implement
// anything because by taking ownership of the box, it will be freed at the end of the function.
#[export_name = "foo_free"]
pub extern "C" fn _drop_boxed(_: Option<Box<Self>>) {}
#[export_name = "foo_get_x"]
pub extern "C" fn get_x(&self) -> c_int {
// Since we use i32 internally, we need to convert to c_int incase the types do not match
self.x as c_int
}
#[export_name = "foo_set_x"]
pub extern "C" fn set_x(&mut self, x: c_int) {
self.x = x as i32;
}
}