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
- Primitive types (
bool
,i32
,f64
,String
, etc.) - Tuples
- Structs
- Enums
- Top-level constants
- Top-level functions
- Traits (must be either
Box<dyn T>
orRc<dyn T>
) Vec<T>
Option<T>
Box<T>
- JavaScript/TypeScript (WASM)
- Swift
- C++
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.
These projects are much further along, and you may want to use them instead: