Dart and Rust: the async story 🔃
Tags: dart, rust, flutter, ffi
Published: 2020-06-22 00:04:28

In the last blog post: [**Dart Meets Rust: a match made in heaven**](https://dev.to/sunshine-chain/dart-meets-rust-a-match-made-in-heaven-9f5) we show how to connect both languages and build more secure, performant Flutter Applications.
In this blog post, we will talk about how to use Multithreaded async Rust code with async Dart and how we could build applications that have more than one runtime working together in a smooth way like how the [sōzu](https://en.wikipedia.org/wiki/Shishi-odoshi) works.

![A Shishi-odoshi breaks the quietness of a Japanese garden with the sound of a bamboo rocker arm hitting a rock.](https://upload.wikimedia.org/wikipedia/commons/4/41/Higashiyama_Botanical_Garden_Shishiodoshi_20170617.gif)


### The Problem 🎯

Running more than async loops is not always easy to get it right, you know that Dart VM has it is own async runtime and in Rust, we could plug-in any one of the popular runtimes, here is a few for reference [Tokio](https://tokio.rs/), [async-std](https://async.rs/), and recently [smol](https://github.com/stjepang/smol) which is now built-in the async-std as it is low-level runtime.

The issue is that Dart as a language is designed to be single-threaded, that's not a problem in itself, but we will see later how would that be an issue when using Rust.
well, you could run more than [Isolate](https://api.dart.dev/stable/dart-isolate/Isolate-class.html) which is like a small Dart VM instance they can work together and communicate by message passing, here is a [video](https://www.youtube.com/watch?v=vl_AaCgudcY) where Andrew from Flutter team talk about Dart Isolates and the Event Loop, I highly recommend watching it before continuing if you have no idea about Dart isolates (PS: it is less than 6 min 😅) 

### Let's Scrape the web 🌍

For making this post more exciting let's build a simple web scraping library in Rust and use it in Flutter Application.
using our experience from the last blog post on how to connect both languages, we will use the same idea here too.

Create the Rust crate first:
```bash
$ cargo new --lib native/scrap # yes, it is a scrap lol
```
cool, now we need a way to send async web requests, so let's use [reqwest](http://docs.rs/reqwest) 

```toml
[dependencies]
# we will use `rustls-tls` here since openssl is an issue when cross-compiling for Android/iOS
reqwest = { version = "0.10", default-features = false, features = ["rustls-tls"] }
```
Now let's write some rusty code 😀

```rust
// lib.rs
use std::{error, fmt, io};

/// A useless Error just for the Demo
#[derive(Copy, Clone, Debug)]
pub struct ScrapError;

impl fmt::Display for ScrapError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error While Scrapping this page.")
    }
}

impl error::Error for ScrapError {}

impl From<reqwest::Error> for ScrapError {
    fn from(_: reqwest::Error) -> Self {
        Self
    }
}

impl From<io::Error> for ScrapError {
    fn from(_: io::Error) -> Self {
        Self
    }
}

/// Load a page and return its HTML body as a `String`
pub async fn load_page(url: &str) -> Result<String, ScrapError> {
    Ok(reqwest::get(url).await?.text().await?)
}

```
so as you see it is some simple code with sh**ty error handling 😂

Next, Let's Write our FFI binding to the `scrap` crate:

```bash
$ cargo new --lib native/scrap-ffi
```
in our `Cargo.toml`
```toml
[lib]
name = "scrap_ffi"
crate-type = ["cdylib", "staticlib"]

[dependencies]
scrap = { path = "../scrap" }
```
Now, it gets dirty because we need to consider a few things here

#### Setting up the Runtime 🔌

We will choose [Tokio](https://tokio.rs/) here as our runtime, it does not matter which runtime we use, but in our case, since `reqwest` works well with Tokio we going to use it.
also [lazy_static](https://docs.rs/lazy_static) it will be handy in a second.

```toml
tokio = { version = "0.2", features = ["rt-threaded"] }
lazy_static = "1.4"
```
Write some code to set up the runtime

```rust
// lib.rs
use tokio::runtime::{Builder, Runtime};
use lazy_static::lazy_static;
use std::io;

lazy_static! {
    static ref RUNTIME: io::Result<Runtime> = Builder::new()
        .threaded_scheduler()
        .enable_all()
        .core_threads(4)
        .thread_name("flutterust")
        .build();
}

/// Simple Macro to help getting the value of the runtime.
macro_rules! runtime {
    () => {
        match RUNTIME.as_ref() {
            Ok(rt) => rt,
            Err(_) => {
                return 0;
            }
        }
    };
}
```
This setup a `Tokio` Runtime with 4 threads and naming them `flutterust` for each thread.

#### Error Handling 🧰

Handling Errors between FFI is hard, so we will use a helper crate that will make our life easier. 
add [ffi_helpers](https://docs.rs/ffi_helpers) to the dependencies

```toml
ffi_helpers = "0.2"
```

it provides useful functions and macros that could help us to handle errors, exposing `last_error_length` and `error_message_utf8` so in Dart's side we could get readable error messages.

```rust
macro_rules! error {
    ($result:expr) => {
        error!($result, 0);
    };
    ($result:expr, $error:expr) => {
        match $result {
            Ok(value) => value,
            Err(e) => {
                ffi_helpers::update_last_error(e);
                return $error;
            }
        }
    };
}

macro_rules! cstr {
    ($ptr:expr) => {
        cstr!($ptr, 0);
    };
    ($ptr:expr, $error:expr) => {{
        null_pointer_check!($ptr);
        error!(unsafe { CStr::from_ptr($ptr).to_str() }, $error)
    }};
}


#[no_mangle]
pub unsafe extern "C" fn last_error_length() -> i32 {
    ffi_helpers::error_handling::last_error_length()
}

#[no_mangle]
pub unsafe extern "C" fn error_message_utf8(buf: *mut raw::c_char, length: i32) -> i32 {
    ffi_helpers::error_handling::error_message_utf8(buf, length)
}
```

#### Exposing `load_page` function 🔑

Now comes the issue, how we would expose `async` function? well C/Dart has no idea how Rust's `async` works.
![thinking](https://dev-to-uploads.s3.amazonaws.com/i/yzk0pnri83lplfudnvs6.png)
One way could be writing a normal non-`async` function as a wrapper around the `async` function and call [block_on](https://docs.rs/tokio/0.2.21/tokio/runtime/struct.Runtime.html#method.block_on) to get the result out from the `async fn`, as you see the function name says it all, we will block that thread only to run that `async` task, so what the benefit in the first place?

There is another way, what about using [`callbacks`](https://en.wikipedia.org/wiki/Callback_(computer_programming))?!
Good, that would be an option and use javascript `Promise` style to handle the async tasks by passing two callbacks one called on the `success` and the other one called on `err`.

Theoretically, that would work, but

![Dammit](https://dev-to-uploads.s3.amazonaws.com/i/ogscxfndipxj3j1rlst2.jpg)

As I said before, Dart is designed to be single-threaded so you can't just call the callback from other threads it basically will break Dart semantics (you could read more [here](https://github.com/dart-lang/sdk/issues/37022#issuecomment-533462476)).

So what would be the solution then?
What if I told you that we could communicate between Dart and Rust using an `Isolate` 😦.


Every new Isolate get a [SendPort](https://api.dart.dev/stable/dart-isolate/SendPort-class.html) and [ReceivePort](https://api.dart.dev/stable/dart-isolate/ReceivePort-class.html) these are used to communicate with the `Isolate` and you only need `SendPort` to send messages to the `Isolate`, also every `SendPort` have what is called a [NativePort](https://api.dart.dev/stable/dart-ffi/NativePort.html) this the underlying number of that port.

> Just a small note, the term of `Port` here is not related at all to a `Networking` Port or something like that, it is just numbers used as Keys for a Map implemented internally in Dart VM, think of them as a `Handle` to that `Isolate` 

What if we used these numbers to send our `async fn` result on them from any thread we want, is that possible? **YES**

### Enter: Allo Isolate 📞

[allo-isolate](https://github.com/sunshine-protocol/allo-isolate) is a crate we built at [Sunshine](https://sunshine.foundation/) to help us Running Multithreaded Rust along with Dart VM (in isolate) 🌀
It only needs a `Port` of that isolate and you could send any message on it from your Rust code.

Under the hood, it uses the Dart API `Dart_PostCObject` function and it can convert most (almost [all](https://docs.rs/allo-isolate/0.1.0/allo_isolate/trait.IntoDart.html)?) of Rust types to Dart types, see the [docs](https://docs.rs/allo-isolate) for more information.

> Allo (pronounced Hello, without the H) usually used in communication over the phone in some languages like Arabic :).

Back to our example, we now know how to expose the `load_page` function lets use `allo-isolate`

```toml
allo-isolate = "0.1"
```
use it
```rust
use allo_isolate::Isolate;

...

#[no_mangle]
pub extern "C" fn load_page(port: i64, url: *const raw::c_char) -> i32 {
    // get a ref to the runtime
    let rt = runtime!();
    let url = cstr!(url);
    rt.spawn(async move {
        // load the page and get the result back
        let result = scrap::load_page(url).await;
        // make a ref to an isolate using it's port
        let isolate = Isolate::new(port);
        // and sent it the `Rust's` result
        // no need to convert anything :)
        isolate.post(result);
    });
    1
}
```

Great, we are ready to go, after generating our `binding.h` and building everything for `iOS` and `Android` we need also to write the Dart side FFI... ugh not again 🤦‍♂️

![Matrix](https://dev-to-uploads.s3.amazonaws.com/i/vhhcbalclde1vnbo0ey3.jpg)

What if I told you that you could now generate your Dart FFI binding from a C Header File?


### dart-bindgen: a new way to write Dart FFI

[dart-bindgen](https://github.com/sunshine-protocol/dart-bindgen) is a tool for generating Dart FFI bindings to C Header file, it comes as a [CLI](https://github.com/sunshine-protocol/dart-bindgen/releases) and a [Library](https://docs.rs/dart-bindgen).

and we going to use it to help us generate the FFI, just add it to your `build-dependencies`

```toml
[build-dependencies]
cbindgen = "0.14.2" # Rust -> C header file
dart-bindgen = "0.1" # C header file -> Dart FFI
```

and in our `build.rs`

```rust
use dart_bindgen::{config::*, Codegen};

fn main() {
    ...
    let config = DynamicLibraryConfig {
        ios: DynamicLibraryCreationMode::Executable.into(),
        android: DynamicLibraryCreationMode::open("libscrap_ffi.so").into(),
        ..Default::default()
    };
    // load the c header file, with config and lib name
    let codegen = Codegen::builder()
        .with_src_header("binding.h")
        .with_lib_name("libscrap")
        .with_config(config)
        .build()
        .unwrap();
    // generate the dart code and get the bindings back
    let bindings = codegen.generate().unwrap();
    // write the bindings to your dart package
    // and start using it to write your own high level abstraction.
    bindings
        .write_to_file("../../packages/scrap_ffi/lib/ffi.dart")
        .unwrap();
}
```

So far so good, let's write our higher-level abstraction over the `ffi.dart` file.

```dart
// packages/scrap_ffi/lib/scrap.dart

import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// isolate package help us creating isolate and getting the port back easily.
import 'package:isolate/ports.dart';

import 'ffi.dart' as native;

class Scrap {
  // this only should be called once at the start up.
  static setup() {
    // give rust `allo-isolate` package a ref to the `NativeApi.postCObject` function.
    native.store_dart_post_cobject(NativeApi.postCObject);
    print("Scrap Setup Done");
  }

  Future<String> loadPage(String url) {
    var urlPointer = Utf8.toUtf8(url);
    final completer = Completer<String>();
    // Create a SendPort that accepts only one message.
    final sendPort = singleCompletePort(completer);
    final res = native.load_page(
      sendPort.nativePort,
      urlPointer,
    );
    if (res != 1) {
      _throwError();
    }
    return completer.future;
  }

  void _throwError() {
    final length = native.last_error_length();
    final Pointer<Utf8> message = allocate(count: length);
    native.error_message_utf8(message, length);
    final error = Utf8.fromUtf8(message);
    print(error);
    throw error;
  }
}
```

in our flutter application, we could use that package as so:

```dart
...
class _MyHomePageState extends State<MyHomePage> {

  ...
  Scrap scrap;
  @override
  void initState() {
    super.initState();
    scrap = Scrap();
    Scrap.setup();
  }  
    
  // somewhere in your app
  final html = await scrap.loadPage('https://www.rust-lang.org/');
```

That's it, Fire up Android Emulator or iOS Simulator and Run:

```bash
$ cargo make # build all rust packages for iOS and Android
```
Then,
```bash
$ flutter run # 🔥
```

All the Code is available in [flutterust](https://github.com/shekohex/flutterust) which is only clone and Hack 😀.

Stay tuned for other Hacks in Dart and Rust, you could follow me on [twitter](https://twitter.com/ShekoHex) and [github](https://github.com/shekohex)

Thank you 💚.