miniffi 0.1.0

A simple but opinionated FFI system
Documentation
  • Coverage
  • 50%
    15 out of 30 items documented6 out of 16 items with examples
  • Size
  • Source code size: 389.3 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.5 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 13s Average build duration of successful builds.
  • all releases: 13s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • evanw/miniffi
    381 7 1
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • evanw

miniffi

[!WARNING] This project is on its first release. I'm still trying out the idea. It has not been used in production.

This is a simple but opinionated FFI system for Rust. It allows you to call Rust code from other languages, and vice versa. Some design principles:

  • Intended for writing platform-agnostic Rust with platform-specific details in the host language
  • FFI support is split into data (structs passed by copy) and code (traits passed by reference)
  • Rather than have a separate schema language, the public API in your lib.rs is your schema
  • The generated binding code is compact, dependency-free, and straightforward to integrate with

Supported Features

  • Primitive types (bool, i32, f64, String, etc.)
  • Tuples
  • Structs
  • Enums
  • Top-level constants
  • Top-level functions
  • Traits (must be either Box<dyn T> or Rc<dyn T>)
  • Vec<T>
  • Option<T>
  • Box<T>

Available Targets

  • JavaScript/TypeScript (WASM)
  • Swift
  • C++

Example

After setting everything up, using miniffi looks something like this. The following example Rust code (in lib.rs) demonstrates passing code and data back and forth between Rust and the host language:

pub enum Level {
    Warning,
    Error,
}

pub trait Logger {
    fn log(&self, level: Level, text: &str);
}

pub trait Demo {
    fn run(&self, logger: Box<dyn Logger>);
}

pub fn get_demo() -> Box<dyn Demo> {
    struct DemoImpl;
    impl Demo for DemoImpl {
        fn run(&self, logger: Box<dyn Logger>) {
            logger.log(Level::Warning, "example");
        }
    }
    Box::new(DemoImpl)
}

// This includes the binding code generated by miniffi
include!(concat!(env!("OUT_DIR"), "/miniffi.rs"));

Unlike other FFI systems, there are no annotations required to expose something from lib.rs to the host language other than using pub to mark things public. The miniffi build script parses lib.rs, extracts the supported parts of the public API, and generates FFI binding code both for Rust (the miniffi.rs file referenced above) and for the host language. Calling that example Rust code might look like this:

In JavaScript/TypeScript:

import * as rust from "./miniffi.js"
import fs from "fs"

await rust.instantiate(fs.readFileSync(
    "./target/wasm32-unknown-unknown/debug/example.wasm"))

rust.get_demo().run({
    log(level, text) {
        if (level === rust.Level.Error)
            console.error("ERROR:", text)
        else if (level === rust.Level.Warning)
            console.warn("WARNING:", text)
    }
})

In Swift:

class LoggerImpl : Logger {
    func log(_ level: Level, _ text: String) {
        if level == .Error {
            print("ERROR:", text)
        } else if level == .Warning {
            print("WARNING:", text)
        }
    }
}

get_demo().run(LoggerImpl())

In C++:

#include <iostream>
#include "ffi.h"

struct LoggerImpl : rust::Logger {
    void log(rust::Level level, std::string text) {
        if (level == rust::Level::Error)
            std::cout << "ERROR: " << text << std::endl;
        else if (level == rust::Level::Warning)
            std::cout << "WARNING: " << text << std::endl;
    }
};

int main() {
    rust::get_demo()->run(std::make_unique<LoggerImpl>());
    return 0;
}

More information about how to use miniffi can be found in the documentation.

Similar Projects

These projects are much further along, and you may want to use them instead: