This is similar to Can Box::leak
be complemented with Box::from_raw
, but not quite the same. In that topic, leak
is used to create the pointer.
The topic Passing mutable objects across ffi and back is slightly more in line with my goal, but there the object lives within Rust and is then passed as a reference to foreign code.
In my use case, I'm dealing with a generic struct opaque to the caller (not strings like the first topic), created by Rust on the foreign side (Rust is the foreign call, Java in my case is the host).
The following code should help make it clearer (playground):
// (obviating https://doc.rust-lang.org/nomicon/ffi.html#representing-opaque-structs for simplicity)
use std::ffi::c_void;
#[derive(Debug)]
struct Foo {
data: i32,
}
#[no_mangle]
pub extern "C" fn foo_create() -> *mut c_void {
Box::into_raw(Box::new(Foo { data: 0 })) as *mut c_void
}
#[no_mangle]
pub extern "C" fn foo_increment(pointer: *mut c_void) {
let foo = Box::leak(unsafe { Box::from_raw(pointer as *mut Foo) });
foo.data += 1;
}
#[no_mangle]
pub extern "C" fn foo_print(pointer: *mut c_void) {
let foo = Box::leak(unsafe { Box::from_raw(pointer as *mut Foo) });
dbg!(foo);
}
#[no_mangle]
pub extern "C" fn foo_free(pointer: *mut c_void) {
let _ = unsafe { Box::from_raw(pointer as *mut Foo) };
}
// pretend this main() function is Java code calling into the Rust code
// (in the real code the functions return i64 and not a pointer type
// but that should not relevant to the question)
fn main() {
let ptr = foo_create();
foo_increment(ptr);
foo_print(ptr);
foo_free(ptr);
}
In essence, some opaque object Foo
is put into the heap with Box::new
in foo_create
. It is then converted into a pointer with Box::into_raw
.
Now, here's the question: both foo_increment
and foo_print
reconstruct the Box
with Box::from_raw
, but then immediately leak it with Box::leak
to prevent it from being freed (essentially behaving like ManuallyDrop
), since the Java code (main()
in the example) is still holding the ownership of the object.
I ran the program with Miri in the playground, and it did not seem to complain. Is leaking the Box
like this to prevent it from being freed safe to do? Or should the functions be implemented with something more akin to:
#[no_mangle]
pub extern "C" fn foo_increment(pointer: *mut c_void) {
let mut foo = unsafe { Box::from_raw(pointer as *mut Foo) };
foo.data += 1;
let _ = Box::into_raw(foo);
}
(i.e. re-converting the Box
into a raw pointer). This doesn't feel right to me for two reasons:
- If
foo_increment
were to panic beforeBox::into_raw
was called, unwinding would free the memory which Java could then incorrectly attempt to use. - This is "creating a new pointer" which is then discarded, under the assumption the pointer will be the same as the old one, so that the old one can remain being used.
Both the leak
and the re-into_raw
approach compile and run fine in playground under Miri.
Perhaps there is a third way I'm unaware of. I might be overthinking this, and maybe the solution is to not touch Box
at all until I need to free the memory like so:
#[no_mangle]
pub extern "C" fn foo_increment(pointer: *mut c_void) {
let foo = unsafe { &mut *(pointer as *mut Foo) };
foo.data += 1;
}