![Latest Version] ![docs] ![MIT]
đŚ â đ â Python, C#, C, ...
FFI bindings to your favorite language. Composable. Sane. Escape hatches included.
If you ...
extern "C"
API in Rust... then Interoptopus might be for you.
Assume you have written this Rust FFI code:
```rust use interoptopus::{ffifunction, ffitype};
pub struct Vec3f32 { pub x: f32, pub y: f32, pub z: f32, }
pub extern "C" fn mygamefunction(input: Option<&Vec3f32>) -> Vec3f32 { Vec3f32 { x: 2.0, y: 4.0, z: 6.0 } }
interoptopus::inventoryfunction!(ffiinventory, [], [mygamefunction], []); ```
You can now use one of these backends to generate interop code:
| Language | Crate | Comment | | --- | --- | --- | | C# (incl. Unity) | interoptopusbackendcsharp | Built-in. | | C | interoptopusbackendc | Built-in. | | Python CFFI | interoptopusbackendcpython_cffi | Built-in. | | Your language | Write your own backend! | See existing backends for what to do. |
This library naturally does "unsafe" things and any journey into FFI-land is a little adventure. That said, here are some assumptions and quality standards this project is based on:
Safe Rust calling safe Rust code must always be sound, with soundness boundaries
on the module level, although smaller scopes are preferred. For example, creating a FFISlice
from Rust and directly using it from Rust must never cause UB.
We must never willingly generate broken bindings. For low level types we must never
generate bindings which "cannot be used correctly" (e.g., map a u8
to a float
), for
patterns we must generate bindings that are "correct if used according to specification".
There are situations where the (Rust) soundness of a binding invocation depends on conditions outside
our control. In these cases we trust foreign code will invoke the generated functions
correctly. For example, if a function is called with an AsciiPointer
type we consider it safe and sound
to obtain a str
from this pointer as AsciiPointer
's contract specifies it must point to
ASCII data.
Related to the previous point we generally assume functions and types on both sides are used appropriately w.r.t.
Rust's FFI requirements and we trust you do that, e.g., you must declare types #[repr(C)]
(or similar)
and functions extern "C"
.
Any unsafe
code in any abstraction we provide should be "well contained", properly documented
and reasonably be auditable.
If unsound Rust types or bindings were ever needed (e.g., because of a lack of Rust specification,
like 'safely' mapping a trait's vtable) such bindings should be gated behind a feature flag
(e.g., unsound
) and only enabled via an explicit opt-in. Right now there are none, but this is
to set expectations around discussions.
Why do I get error[E0658]: macro attributes in #[derive] output are unstable
?
This happens when #[ffi_type]
appears after #derive[...]
. Just switch their order.
How do I support a new language?
1) create a new crate, like my_language
1) check which existing backend comes closest, copy that code
1) start from trait Interop::write_to
producing some output, fix errors as they appear
1) create a UI test against interoptopus_reference_project
to ensure your bindings are stable
How does it actually work?
As answered by Alex Hirsekorn:
Occasionally a GPO will get minor injuries from capturing the crab but, for the most part they are too careful and too skilled for that to be much of an issue.
After the GPO has rested, FFI bindings are produced.
PRs are welcome.
Bug fixes can be submitted directly. major changes should be filed as issues first.
Anything that would make previously working bindings change behavior or stop compiling is a major change; which doesn't mean we're opposed to breaking stuff before 1.0, just that we'd like to talk about it before it happens.
New features or patterns must be materialized in the reference project and accompanied by an interop test (i.e., a backend test running C# / Python against a DLL invoking that code) in at least one included backend.
This license only applies to code in this repository, not code generated by this repository. We do not claim copyright for code produced by backends included here; even if said code was based on a template in this repository.
For the avoidance of doubt, anything produced by Interop::write_to
or any item emitted by a proc macro is considered "generated by".