Crates.io | membrane |
lib.rs | membrane |
version | 0.11.0 |
source | src |
created_at | 2021-09-22 00:48:12.175296 |
updated_at | 2023-09-15 18:00:02.496362 |
description | Membrane 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. |
homepage | |
repository | https://github.com/jerel/membrane |
max_upload_size | |
id | 454670 |
size | 104,932 |
apt-get install libclang-dev
brew install llvm
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
.
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:
use membrane::runtime::{App, Interface, AbortHandle};
pub struct Runtime(tokio::runtime::Runtime);
impl Interface for Runtime {
fn spawn<F>(&self, future: F) -> AbortHandle
where
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
where
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(|| {
Runtime(
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.thread_name("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>>
:
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>> {
futures::stream::iter(vec![Ok(Default::default())])
}
#[async_dart(namespace = "accounts")]
pub async fn contact(id: String) -> Result<data::Contact, data::Error> {
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):
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
.package_destination_dir("../dart_example")
// the pub package name, if different than the directory
.package_name("example")
// give the basename of the .so or .dylib that your Rust program provides
.using_lib("libexample")
// use Dart enums instead of class enums
.with_c_style_enums(true)
.create_pub_package()
.write_api()
.write_c_headers()
.write_bindings();
}
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 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
.