Skip to content

evanw/miniffi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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:

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published