Dart Meets Rust: a match made in heaven ✨
Tags: rust, dart, flutter, ffi
Published: 2020-06-06 13:38:25

### A small piece of Dart

![bird](https://dev-to-uploads.s3.amazonaws.com/i/uhuu6z9k9muxkn6pdu9u.png)
[Dart](https://dart.dev/) is a client-optimized language for fast apps on any platform, it make it easy to build the UI of your application and it is quite nice language to work with, it the language used by [Flutter](flutter.dev) Framework, Flutter is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

### Enter Rust
![rust](https://i.ibb.co/pP6BfMZ/corrosion.jpg)
[Rust](https://www.rust-lang.org/) is blazingly fast and memory-efficient, with no runtime or garbage collector, it can power performance-critical services, run on embedded devices, and easily integrate with other languages.

We are using both Rust and Dart (in Flutter) at [Sunshine](https://sunshine.foundation/) to enable open-source grant initiatives to easily operate in an on-chain ecosystem.

Almost all of our Code is written in Rust, that's why we needed to think about using the same code and the same logic in our client-side application, but How?

Well, let's see what options we have here

### Using Flutter Platform Channels

 [Flutter Platform channels](https://flutter.dev/docs/development/platform-integration/platform-channels) is a flexible system that allows you to call platform-specific APIs whether available in Kotlin or Java code on Android, or in Swift or Objective-C code on iOS.
this way, we will have to first bind our rust code to Java (for Android), Swift (for iOS), and WASM for the Web, but that would be an over complicated, and maybe that could result a performance issues in the future. Here is a simple graph to get an idea of how it looks like:

![Flutter Platform Channels](https://i.ibb.co/B3xSFNn/flutter-channels.jpg)
but as you could see, there is a lot of overhead involved here and data serialization/deserialization is very costly at runtime, so what else we could do?   

### FFI, break boundaries

as Wikipedia says: A foreign function interface (FFI) is a mechanism by which a program written in one programming language can call routines or make use of services written in another.

hmmm, interesting let's see what we could do, dose Dart support FFI?
Yes!, actually FFI introduced in [Dart 2.5](https://medium.com/dartlang/announcing-dart-2-5-super-charged-development-328822024970#:~:text=Today%20we're%20announcing%20the,C%20code%20directly%20from%20Dart.) quite recently at the end of last year, so it is still under active development, but quite stable.

After Playing around with [FFI Examples with Dart](https://github.com/dart-lang/samples/tree/master/ffi), I started to work on [flutterust](https://github.com/shekohex/flutterust) a simple template to show how to use Flutter/Dart with Rust through FFI.

The simple idea here is that we build our rust code for all supported targets then build a Flutter Package that uses these targets.

And Here is the benefits of using the FFI Method here

* No Swift/Kotlin wrappers
* No message passing
* No async/await on Dart
* Write once, use everywhere
* No garbage collection
* No need to export `aar` bundles or `.framework's`

So, it would be like this:

![Dart FFI](https://i.ibb.co/VW6yrFD/flutter-channels2.jpg)

that is so cool, here is a simple example

### Learning How to count!
we are going to use the same flutter hello world example, but instead of doing the logic (incrementing the counter) in the Dart side, we going to do it in the Rust side.

Our Project Sturcutre: 
```
.
├── android
├── ios
├── lib                     <- The Flutter App Code
├── native                  <- Containes all the Rust Code
│   ├── adder
│   └── adder-ffi
├── packages                <- Containes all the Dart Packages that bind to the Rust Code
│   └── adder_ffi
├── target                  <- The compiled rust code for every arch
│   ├── aarch64-apple-ios
│   ├── aarch64-linux-android
│   ├── armv7-linux-androideabi
│   ├── debug
│   ├── i686-linux-android
│   ├── universal
│   ├── x86_64-apple-ios
│   └── x86_64-linux-android
└── test

```

#### The Rust Side

Start by creating a Cargo Workspace, so we add a simple `Cargo.toml` to the root of our Flutter app
```toml
[workspace]
members = ["native/*"]

[profile.release]
lto = true
codegen-units = 1
debug = true # turn it off if you want.
```
Create our simple `adder` package

```
$ cargo new --lib native/adder
```
and let's write some code

```rs
pub fn add(a: i64, b: i64) -> i64 {
    a.wrapping_add(b)
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(super::add(2, 2), 4);
    }
}
```
boring, isn't it? 🥱

let's show the world our new `add` function :)

```
$ cargo new --lib native/adder-ffi
```
and don't forget to change it's type in the `native/adder-ffi/Cargo.toml`

```toml
[lib]
name = "adder_ffi"
crate-type = ["cdylib", "staticlib"]

[dependencies]
adder = { path = "../adder" }
```

```rs
// lib.rs

#[no_mangle]
pub extern "C" fn add(a: i64, b: i64) -> i64 {
    adder::add(a, b)
}
```

Nice, but how to compile our code for the mobile?
Well, it is a bit complicated. We could use cargo directly and it would of course work, but we need to configure a lot of other things, so we will relay on other tools that would do it for us like [`cargo-lipo`](https://github.com/TimNN/cargo-lipo) and [`cargo-ndk`](https://github.com/bbqsrc/cargo-ndk).

After Compiling our rust code to all of these platforms:
```
aarch64-apple-ios
aarch64-linux-android
armv7-linux-androideabi
i686-linux-android
x86_64-apple-ios
x86_64-linux-android
```
we are ready to go to next step, that we will copy our compiled code to specific locations 

start first by generating a flutter plugin named after our rust crate:

```
$ flutter create --template=plugin packages/adder
```

```
target/universal/debug/libadder_ffi.a -> packages/adder/ios/libadder_ffi.a
target/aarch64-linux-android/debug/libadder_ffi.so -> packages/adder/android/src/main/jniLibs/arm64-v8a/libadder_ffi.so
...
...other android libs
```
Are we ready yet? well, technicllay yes, but Xcode has another thing to do like writing a [C Header file](https://github.com/shekohex/flutterust/blob/master/native/adder-ffi/binding.h) for our FFI for iOS, if you developing on a macOS you should do these steps [here](https://github.com/shekohex/flutterust#in-ios) other than that you are ready to go to the next step, writing a Flutter Package to our rust lib.

#### The Dart Side

so back to Dart, in our generated flutter plugin, we will define how our rust function look like (the type definition) in dart code

```dart
import 'dart:ffi';

// For C/Rust
typedef add_func = Int64 Function(Int64 a, Int64 b);
// For Dart
typedef Add = int Function(int a, int b);

```

and we need a function that loads our rust lib depending on the platform like iOS/Android or Linux/macOS or whatever it is.

```dart
import 'dart:io' show Platform;


DynamicLibrary load({String basePath = ''}) {
  if (Platform.isAndroid || Platform.isLinux) {
    return DynamicLibrary.open('${basePath}libadder_ffi.so');
  } else if (Platform.isIOS) {
    // iOS is statically linked, so it is the same as the current process
    return DynamicLibrary.process();
  } else if (Platform.isMacOS) {
    return DynamicLibrary.open('${basePath}libadder_ffi.dylib');
  } else if (Platform.isWindows) {
    return DynamicLibrary.open('${basePath}libadder_ffi.dll');
  } else {
    throw NotSupportedPlatform('${Platform.operatingSystem} is not supported!');
  }
}

class NotSupportedPlatform implements Exception {
  NotSupportedPlatform(String s);
}
```

and finally create a simple Class that holds our ffi function

```dart
class Adder {
  static DynamicLibrary _lib;
  
  Adder() {
    if (_lib != null) return;
    // for debugging and tests
    if (Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
      _lib = load(basePath: '../../../target/debug/');
    } else {
      _lib = load();
    }
  }
}
```
and here is the `add` method
```dart
int add(int a, int b) {
    // get a function pointer to the symbol called `add`
    final addPointer = _lib.lookup<NativeFunction<add_func>>('add');
    // and use it as a function
    final sum = addPointer.asFunction<Add>();
    return sum(a, b);
}
```
so far so good, lets use it in our Flutter app

in the `pubspec.yaml` of the app, add our `adder` package under `dependencies`

```yaml
adder:
    path: packages/adder_ffi
```

and in `lib/main.dart` change the logic of the `_incrementCounter` method to use our rust logic


```dart
import 'package:adder/adder.dart';

// in the `MyHomePage` add 
final adder = Adder();

// and latter in `_MyHomePageState` replace
...
 void _incrementCounter() {
    setState(() {
      _counter = widget.adder.add(_counter, 1);
    });
  }
...
```

and fire up the Flutter App on Android Emulator or iOS Simulator and Test it 🔥.

phew ..

![phew](https://media.giphy.com/media/xT8qBqNisx9dkXWAX6/giphy.gif)

but we found it is so boring to do that, and especially when it comes to using other build systems like Xcode and Android NDK toolchain and linking everything together 🤦‍♂️. That's why we tried to automate everything, but we need something that is easy to use, cross platform, and CI friendly.


### Cargo-make to rescue 🚀

[cargo-make](https://github.com/sagiegurari/cargo-make) is a cross-platform task runner and build tool built in Rust, it is really an Amazing tool to use, it helps you write your workflow in a simple set of tasks, and it has a lot of other cool features like it is easy to add inline scripts in it and [a lot more](https://medium.com/@sagiegurari/automating-your-rust-workflows-with-cargo-make-part-1-of-5-introduction-and-basics-b19ced7e7057).
you could see how we using it at [sunshine-flutter](https://github.com/sunshine-protocol/sunshine-flutter/blob/master/Makefile.toml).

That's it, I hope it helped to understand how Dart FFI and Rust works together.

Next Up, _How to handle async Rust and Dart FFI_
I will leave this to a next blog post, pretty soon :)

For now, you could see that I start hacking on the [`scrap`](https://github.com/shekohex/flutterust/tree/master/packages/scrap_ffi) package that created to demonstrate how we could integrate async Rust with Dart.

##### Other Intersting Rust + Mobile FFI Development 

* https://dart.dev/guides/libraries/c-interop
* https://flutter.dev/docs/development/platform-integration/c-interop
* https://github.com/dart-lang/samples/blob/master/ffi/structs/structs.dart
* https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-06-rust-on-ios.html
* https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-21-rust-on-android.html