Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 6, 2020 01:38 pm GMT

Dart Meets Rust: a match made in heaven

A small piece of Dart

bird
Dart 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 Framework, Flutter is Googles UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase.

Enter Rust

rust
Rust 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 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 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
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 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, I started to work on 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

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

[workspace]members = ["native/*"][profile.release]lto = truecodegen-units = 1debug = true # turn it off if you want.

Create our simple adder package

$ cargo new --lib native/adder

and let's write some code

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

[lib]name = "adder_ffi"crate-type = ["cdylib", "staticlib"][dependencies]adder = { path = "../adder" }
// 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 and cargo-ndk.

After Compiling our rust code to all of these platforms:

aarch64-apple-iosaarch64-linux-androidarmv7-linux-androideabii686-linux-androidx86_64-apple-iosx86_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.atarget/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 for our FFI for iOS, if you developing on a macOS you should do these steps here 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

import 'dart:ffi';// For C/Rusttypedef add_func = Int64 Function(Int64 a, Int64 b);// For Darttypedef 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.

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

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

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

adder:    path: packages/adder_ffi

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

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

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 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.
you could see how we using it at sunshine-flutter.

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 package that created to demonstrate how we could integrate async Rust with Dart.

Other Intersting Rust + Mobile FFI Development

Original Link: https://dev.to/sunshine-chain/dart-meets-rust-a-match-made-in-heaven-9f5

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To