Interoptopus Framework

repository·master·Indexed 19 days ago

https://github.com/ralfbiedert/interoptopus

A high-performance, robust interop framework for Rust that enables seamless bidirectional communication between Rust and other languages. It provides backends for generating bindings for C (interoptopus_c), CPython (interoptopus_cpython), and C# (interoptopus_csharp), utilizing attributes like #[ffi_type] and #[ffi_function] and an InventoryBuilder to define exported symbols.

Tokens
24.9K
Snippets
87
Records
120
Agent score
66%

What's inside Interoptopus

  1. Overview of Interoptopus

    master

    Interoptopus is a high-performance interop library for Rust designed to facilitate bidirectional communication between Rust and other languages. It allows you to export Rust services and types to be consumed by foreign code (like C#) or load foreign code into a Rust application.

    Key capabilities include:

    • High Performance: Near-zero overhead (1-10ns) for plain calls, structs, enums, and services.
    • Rich Type Support: Supports structs, data-enums, callbacks, services, async, and idiomatic error handling.
    • Bidirectional Interop: Export Rust libraries or load foreign code into Rust.
    • Polyglot Core: First-class support for C#, with backend crates available for C and Python.
  2. Understand the Interoptopus reference project structure

    master

    The reference_project serves as a comprehensive inventory of all constructs supported by Interoptopus. It is organized into specific modules that demonstrate how to export different Rust elements via FFI:

    • lib.rs: The main entry point containing the full inventory of items intended for FFI export.
    • constants.rs: Demonstrates how to export constants for use via FFI.
    • functions/: Contains examples of functions, including various supported parameter types and return values.
    • types/: Showcases type constructs, including the use of generics and lifetimes.
    • patterns/: Demonstrates advanced "convenience patterns" for more complex FFI scenarios.
    • services/: Explains how to export "classes" (stateful objects/services) to high-level languages like C# and Python.
  3. Performance expectations for C# and Rust FFI

    master

    Interoptopus aims for zero-cost low-level bindings compared to hand-crafted ones. However, users should expect target-specific overhead at the FFI boundary due to marshalling, pinning, and safety checks.

    • C# overhead: Typically in the nanosecond range.
    • Python overhead: Typically in the microsecond range.
    • Wire-based (JSON) transfers: Performance scales linearly with the payload size.
    • Memory overhead: Loading the .NET runtime via Interoptopus adds approximately 20 MB RSS to the process footprint.
  4. Use the interoptopus_c backend to generate C headers

    master

    The interoptopus_c backend is used to generate C header files (.h) from a Rust FFI library. The resulting headers include type definitions, function declarations, and constants that correspond to the library's exported API, allowing C code to interface with the Rust library.

    Note: This backend is currently suspended and is not actively maintained.

  5. Explore Interoptopus convenience patterns

    master

    Interoptopus provides several advanced convenience patterns to simplify FFI (Foreign Function Interface) development, particularly when working from C# or Python. These patterns address common FFI challenges such as error handling, memory management, and type safety.

    Key patterns include:

    • guard.rs: Ensures your bindings match your .DLL version/identity.
    • callback.rs: Simplifies the use of callbacks and delegates.
    • option.rs: Provides an FFI-safe ffi::Option.
    • primitive.rs: Handles special primitives like ffi::Bool.
    • result.rs: Uses ffi::Result to propagate errors and trigger exceptions in the host language (e.g., C# or Python).
    • slice.rs: Facilitates receiving slices over FFI.
    • string.rs: Manages passing strings over FFI.
    • surrogate.rs: Allows exporting types over FFI that you do not directly control.
    • vec.rs: Enables passing high-performance, Rust-owned data structures.
  6. Use the interoptopus_cpython backend for Python bindings

    master

    The interoptopus_cpython backend allows you to generate Python bindings for Interoptopus-compatible Rust libraries. It works by generating a Rust FFI library and then using ctypes to create a Python module. This module provides Pythonic wrappers for all exported functions, types, and constants from the underlying Rust library.

    Note: This backend is currently suspended and is not actively maintained.

  7. What is a Service in Interoptopus

    master

    In Interoptopus, a Service is a construct defined in Rust and exposed over an FFI boundary. It consists of an (opaque) type and a set of methods operating on that type.

    Key characteristics:

    • Lifecycle: Services have explicitly defined constructors and automatic destructors.
    • Mapping: A Rust service typically becomes a class with methods in languages like C#.
    • Restrictions: Services have specific usage constraints; for example, they cannot be placed in fields.

    To define a service, use the #[ffi(service)] attribute on a struct and implement methods using #[ffi].

    #[ffi(service)]
    pub struct ServiceBasic {}
    
    #[ffi]
    impl ServiceBasic {
        pub fn create() -> ffi::Result<Self, Error> {
            ffi::Ok(Self {})
        }
    }
  8. What is an Extension in Interoptopus

    master

    An Extension is a feature of specific codegen backends (such as backend_csharp) that allows you to modify or inspect the emitted code. You can register extensions with a codegen pipeline to hook into different stages of the generation process.

    To create an extension, implement the RustCodegenExtension trait and register it using the RustLibrary::builder.

    impl RustCodegenExtension for MyExtension {
        fn init(&mut self, _: &mut RustInventory) {}
        fn post_model_cycle(&mut self, _: &RustInventory, _: PostModelPass) -> ModelResult {}
        fn post_model_all(&mut self, _: &RustInventory, _: PostModelPass) -> Result<(), Error> {}
        fn post_output(&mut self, _: &mut Multibuf, _: PostOutputPass) -> OutputResult {}
    }
    
    RustLibrary::builder(inventory)
      .with_extension(MyExtension::new())
      .build()
      .process()?;
  9. What is a Plugin in Interoptopus

    master

    A Plugin enables 'reverse interop', allowing a Rust application to define APIs that are fulfilled by other languages (e.g., C#).

    Workflow:

    1. Define: Use the plugin! macro in the core interoptopus crate to define the API.
    2. Emit: Use a backend crate (like interoptopus_csharp) to generate a plugin stub for the target language (e.g., interoptopus_csharp::DotnetLibrary).
    3. Implement: Implement the stub in the target language and compile it (e.g., via dotnet build).
    4. Load: Load the compiled plugin using the backend's provided plugin runtime (e.g., interoptopus_csharp::rt::dynamic).
    plugin!(MyPlugin {
        fn foo(vec: Vec3f32) -> Vec3f32;
        fn bar(x: u32);
    });
  10. What is the Interoptopus Inventory?

    master

    The Inventory is a language-neutral data model that describes the complete FFI surface, including types, functions, and high-level idiomatic patterns that can be lowered to and restored from a C ABI.

    Key characteristics:

    • Every item is keyed by a deterministic Id.
    • Items include info structs that can be inspected or modified by codegen plugins (e.g., to rename types, add attributes, or suppress items before code generation).
  11. Use the `plugin!` macro for Reverse Interop

    master

    The plugin! macro is used for Reverse Interop, which involves loading foreign code (such as C# DLLs) into Rust as plugins. The macro declares an interface that the foreign plugin must implement. It automatically generates a plugin struct containing the necessary FFI glue, symbol loading, API-guard verification, and instrumentation.

    When you load a plugin, the symbols defined in the macro are resolved from the foreign DLL at runtime.

    interoptopus::plugin!(MyPlugin {
        fn some_function(x: u32) -> u32;
    
        impl SomeService {
            fn create() -> Self;
            fn call(&self, x: u32) -> u32;
        }
    });