Proper way to transmute handles for FFI

std::mem::transmute casts one type into another. Regarding this i have problem understanding the following:
<1> when the source type is Box<A> and the destination type is *const A then how does it correctly work ? I mean isn't Box a type very different to A ? So i basically understand this as reinterpret_cast of a memory layout of type Box to that of A. If that is correct then how does de-referencing the obtained *const A above work at all ?

<2> In my FFI interface I need to give a handle to one of my structures. I do it as follows:
let handle: *const libc::c_void = std::mem::transmute(Box::new(Arc::new(Mutex::new(MyStruct)))); and give this across the FFI boundary. When i receive it back again, I do:
let boxed_my_struct: Box<Arc<Mutex<MyStruct>>> = transmute(handle);
What is the correct way to now bring it out of the Box (because all the interfaces internally take Arc<Mutex<MyStruct>> so I cannot give them a Box) and make Box not destroy it at the end of my function execution (because the ownership lies with the caller on the other side of FFI boundary) ?

1 Like

Well, the big difference is that the *const won't release the memory, so you'll leak it. But their layout is the same, so the transmute "works," for some definition.

Arc already boxes its contents, so you have a double box... so that's unfortunate. I'm not quite sure what the best thing is to do here, but I would bet it involves taking an & to the Arc directly and transmuting that, rather than the box.

This is forget in std::mem - Rust

It's not clear to me what the role of Arc here is. If the ownership is shared, you'd clone the Arc but then it's okay for the Box to be destroyed. If the ownership isn't shared, Arc seems out of place.

Arc is very much required since lot of internal implementations are multithreaded. Yes cloning the Arc (Box::Deref and clone that) will make me not care about the Box anymore, so it can be one of the ways to do <2> in my question. The other is as mentioned by @steveklabnik, use of mem::forget.

However I am still not clear on the answers to point <1>. Let me explain my confusion there again. So coming from a C/C++ background, this is sort of a reinterpret_cast for me. So i cast and interpret the memory location as the destination type, which in this case is *const A. (Let's not go into memory leaks etc., for a moment). The source type however is Box. Now I can guess from what has been written that Box must be a simple wrapper without adding anything to the layout of the contained object. But that is implementation detail. For me Box is a different type than A. So what i would guess is I should be doing something like this (hypothetical functions):

let boxed_ptr = Box::new(A { ..... });
let raw_ptr = boxed_ptr.function_to_get_raw_ptr();
// mem::forget(boxed_ptr); // If one wants, but that's beside the point.
(*raw_ptr).some_function();

but in all examples i see equivalent of this being done:

let boxed_ptr = Box::new(A { ..... });
let raw_ptr: *const A = mem::transmute(boxed_ptr);
(*raw_ptr).some_function();

This for me is undefined behavior and it works by the sheer luck that Box is simply a wrapper and can be thought of as having the same layout as what it contains. In C++ it's like saying:

auto smart_ptr = std::make_unique<A>();
A* raw_ptr = reinterpret_cast<A*>(smart_ptr);
raw_ptr->some_function();

and i think the result of that is undefined because you are not allowed to assume the implementation details of unique_ptr.

This is my point of confusion.

Yeah technically, you should not transmute a Box<T> but use into_raw/from_raw. But at least until these are stabilized, it's guaranteed to have the same representation as a pointer (a &T or *const T), because it is a wrapper around *const T (but not T itself of course).

pub struct Box<T>(Unique<T>);

pub struct Unique<T: ?Sized> {
    pointer: NonZero<*const T>,
    _marker: PhantomData<T>,
}

pub struct NonZero<T: Zeroable>(T);

This is also an alternative:

let a = Box::new(A);
let a_ptr = &*a as *const A;

I do, unfortunately, not know of an equally nice way to do the reverse...

Actually I misread the Box docs. It doesn't say that a Box might become represented differently. So I dare say that a Box will always have the same representation as non-smart pointers (it's not that smart itself currently)

Just tried that. I don't think that would be possible because i got stuck at getting that back into an Arc. So i guess you meant something like this ?

let handle = *arc_var as *const libc::c_void;
mem::forget(arc_var);
// sent handle across FFI boundary

The problem is how do i get it back into an Arc once i receive it from the FFI boundary back:

let arc_var: Arc<Mutex<MyStruct>> = magically_wrap_this_in_an_Arc(handle);

This is not doable. For Box it is doable because, from the discussions so far, i gather that we make an exceptional exception in its case to assume its internals and layout.

This would get something to the contents of the arc, I mean

let handle = &arc_var as *const libc::c_void;

and then transmute it back after.

But again, I haven't actually done a ton of this, so maybe I should just be quiet, I might be missing something :smile:

Wouldn't that be a stack pointer? That's ok within the scope, I guess, but I wouldn't trust it if the ffi lib may keep the pointer for a longer time. I suspect that using this in a callback, for example, may cause some real trouble. It depends on what it will be used for, and how it will be used.

Ok .. Thanks all ! So to summarize the answer to the 2 points:

<1> We make assumptions on the internals of Box. I'm sure this is not an ideal idiom though - Box is not a language feature, it's a library feature and prone to change. But as stated in one of the answers, until into_raw/from_raw are stabilized, there is really no other way.

<2> Do something like this:

let boxed_var: Box<Arc<Mutex<MyStruct>>> = transmute(void_ptr_handle);
foo((*boxed_var).clone());
bar((*boxed_var).clone());
.....
mem::forget(boxed_var);
1 Like

Rustinomicon has this:

Transmuting between non-repr(C) types is UB