created_at2021-09-22 00:48:12.175296
updated_at2023-09-15 18:00:02.496362
descriptionMembrane is an opinionated crate that generates a Dart package from a Rust library. Extremely fast performance with strict typing and zero copy returns over the FFI boundary via bincode.
Jerel Unruh (jerel)




Membrane is an opinionated crate that generates a Dart package from your Rust library. It provides extremely fast performance with strict typing, automatic memory management, and zero copy returns over the FFI boundary via bincode.

Tests Lints Valgrind Memory Check Rust 1.62+

Membrane diagram

Development Environment

On Linux ffigen looks for libclang at /usr/lib/llvm-11/lib/ so you may need to symlink to the version specific library: ln -s /usr/lib/llvm-11/lib/ /usr/lib/llvm-11/lib/


View the example directory for a runnable example.

In your crate's add a RUNTIME static that will survive for the lifetime of the program. RUNTIME must hold an instance of membrane::App<Runtime> where Runtime has the membrane::Interface trait implemented for whichever async framework you wish to use. In our examples we use tokio to provide the runtime behavior, you are welcome to copy it:

use membrane::runtime::{App, Interface, AbortHandle};

pub struct Runtime(tokio::runtime::Runtime);

impl Interface for Runtime {
  fn spawn<F>(&self, future: F) -> AbortHandle
    F: std::future::Future + Send + 'static,
    F::Output: Send + 'static,
    let handle = self.0.spawn(future);
    AbortHandle {
      abort: Box::new(move || handle.abort()),

  fn spawn_blocking<F, R>(&self, future: F) -> AbortHandle
    F: FnOnce() -> R + Send + 'static,
    R: Send + 'static,
    let handle = self.0.spawn_blocking(future);
    AbortHandle {
      abort: Box::new(move || handle.abort()),

static RUNTIME: App<Runtime> = App::new(|| {

Then write some code that is annotated with the #[async_dart] macro. No need to use C types here, just use Rust String, i64, f64, bool, structs, or enums as usual (or with Option). The functions can be anywhere in your program and may return either an async Result<T, E> or an impl Stream<Item = Result<T, E>>:

use membrane::async_dart;
use tokio_stream::Stream;

use crate::data;

#[async_dart(namespace = "accounts")]
pub fn contacts() -> impl Stream<Item = Result<data::Contact, data::Error>> {

#[async_dart(namespace = "accounts")]
pub async fn contact(id: String) -> Result<data::Contact, data::Error> {
  Ok(data::Contact {
    id: id.parse().unwrap(),

And now you are ready to generate the Dart package. Note that this code goes in a bin/ or similar to be ran with cargo run or a build task rather than in (which only runs before compilation):

fn main() {
  // if nothing else in this references then
  // at least call a dummy function so doesn't get optimized away

  let mut project = membrane::Membrane::new();
    // name the output pub directory
    // the pub package name, if different than the directory
    // give the basename of the .so or .dylib that your Rust program provides
    // use Dart enums instead of class enums

If everything went as planned you can now call Rust from Dart with:

cd example
cargo run
cargo build
cd ../dart_example
cp ../example/target/debug/libexample.dylib .
dart --enable-asserts run

(--enable-asserts enables a pretty print toString() in the generated classes)

import 'package:dart_example/accounts.dart';

void main(List<String> arguments) async {
  var accounts = AccountsApi();
  print(await "1"));

If you get an error on Linux about not being able to load then add the pub package's path to LD_LIBRARY_PATH.

Commit count: 353

cargo fmt