Different way to call into Rust from other languages

Hello! I recently did some exploration on "seamlessly" calling into Rust code from C and other languages. In theory, it would let new languages build on Rust, and it could also help smooth codebases' transition from C++ to Rust by enabling a new way to interop between them.

TL;DR: When a Rust function wants to give a (non-repr(C)'d) struct to C, our generated function first transmutes it into an equivalently-sized struct containing a MaybeUninit<[u8; SIZE_HERE]> and shares that with C. When C wants to send it back, it'll transmute it back into its original type. Our little tool generates these structs and functions for all explicitly requested methods and types.

Zozbot234 mentioned that I should post it here to get your thoughts on whether it would work safely or (in my words) burst into flames in glorious fashion.

I'm also curious if you know a better way to analyze the interrelationships between all the structs/functions/crates/modules/impls/enums/imports/aliases/etc. So far I've been consuming the JSON output of rustdoc, which has worked well, but I'm kind of reconsidering in favor of HIR, MIR, etc. Or maybe there's a better way!

Your thoughts would be greatly appreciated! And huge thanks for all you do =) Cheers!

I think you may need to be more careful about alignment, especially on the C side.

I think transmute by value itself is fine, and it should be forgiving about alignment, but if you return something to C by value, then C will have no way of knowing what is the right alignment for the type. Rust types can have a custom alignment greater than 8. When a struct stored on the C side is then returned to the Rust side by reference, you may get an unaligned pointer.


std::mem::drop is not Drop::drop. Calling std::mem::drop(&x) is always a no-op, because std::mem::drop is literally this:

fn drop<T>(meh: T) {}

and it drops by moving the value inside the function, where it becomes unused. But & prevents moves, so nothing is moved, and nothing is dropped.

You can't, and don't need to to call Drop::drop directly. It will always be called when an owned value goes out of scope, even if you create such value via transmute. So C needs to be dropping "by value".

If you'd rather pass pointers around, you'd need to use Box::new() + Box::into_raw() to create a pointer, and Box::from_raw() to drop it. Cast to & or &mut to use without dropping.


As long as the same Rust measures and casts all these structs it should be fine, but the ABI is still technically unspecified. In nightly Rust there's a chaos monkey for this:

RUSTFLAGS="-Z randomize-layout"
8 Likes

Thanks for catching those issues!

I've made the fixes and updated the article (and also added in a thank you). We now correctly move an owned value into the std::mem::drop, which I leave in there just for readability.

Also, if anyone has any better ideas than using the rustdoc JSON output to analyze the relationships between functions/structs/etc, let me know!