Membrane

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.61+

Membrane diagram

Development Environment

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

Usage

View the example directory for a runnable example.

In your crate's lib.rs 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: ``` rust use membrane::runtime::{App, Interface, JoinHandle};

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

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

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

static RUNTIME: App = App::new(|| { Runtime( tokio::runtime::Builder::newmultithread() .workerthreads(2) .threadname("libexample") .build() .unwrap() ) }); ```

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>>:

``` rust use membrane::asyncdart; use tokiostream::Stream;

use crate::data;

[async_dart(namespace = "accounts")]

pub fn contacts() -> impl Stream> { futures::stream::iter(vec![Ok(Default::default())]) }

[async_dart(namespace = "accounts")]

pub async fn contact(id: String) -> Result { Ok(data::Contact { id: id.parse().unwrap(), ..Default::default() }) } ```

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

``` rust fn main() { // if nothing else in this generator.rs references lib.rs then // at least call a dummy function so lib.rs doesn't get optimized away example::load();

let mut project = membrane::Membrane::new(); project // name the output pub directory .packagedestinationdir("../dartexample") // the pub package name, if different than the directory .packagename("example") // give the basename of the .so or .dylib that your Rust program provides .usinglib("libexample") // use Dart enums instead of class enums .withcstyleenums(true) .createpubpackage() .writeapi() .writecheaders() .writebindings(); } ```

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

bash 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)

``` dart import 'package:dart_example/accounts.dart';

void main(List arguments) async { var accounts = AccountsApi(); print(await accounts.contact(id: "1")); } ```

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