How to call a Rust function from Dart using FFI
By: Sacha Arbonel
Tags: Dart, Flutter, Rust

Flutter hits version 1.9 with Dart 2.5 🎉

What a moment to be alive guys ! More seriously … this a huge update, Flutter for Web is no longer in technical preview (no weird imports anymore), and Dart has now support for FFI. Relax, it is not a boring article where I’ll detail every aspects of the changelog. After all it is my first article ever, so I’ll try to explain to you like you were five what is FFI and why do you need to show it some love.

I didn’t want to make a dummy example neither a super complex one so to keep it simple, in this article / tutorial we will make a Dart Command Line Application that reads an audio file.

But wait, what is FFI again ?

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. When you need extra speed 🚀 or need to use a library of an other language, needless to say it becomes handy to have this kind of tool under our tool belt 👷‍.

Why do I need FFI anyway ?

Until today you were fine without it so why bother ? Because we are programmers and we want to optimize things. Right ?! No just kidding… FFI serves many purposes but the main strategy behind Dart language team focusing on this imho, is to smooth the path to Desktop development with Flutter, which is next stage for Flutter supremacy on the market (am I being dramatic here ?).

Well let’s say you are a lazy programmer like me, and you quickly need a library to play music/audio on your computer OR have THE music streaming Startup idea. Unfortunately no such thing exists in Flutter/ Dart because it is a relatively young ecosystem (the language is not that young actually). Without to say that Flutter for desktop still need some tooling to have the same experience that other supported platforms offer. Sooo, what are our options here?

Write a library from scratch using native libraries written in C to play audio files? Nope, I’ll pass (as I said I’m a lazy programmer). Unfortunately I don’t know C and I sleep well at night (you’ll know why later 🦀) Furthermore, if you expect to share your work with the rest of the community (Yeah the Flutter community is awesome btw) you’ll have to write code that compiles on Mac, Linux, and Windows. Which means you’ll have to find a way to develop on all those platforms. Yeaah life sucks I know.

The smart option is to choose a language that is known for its portability on other platforms and its rich ecosystem of libraries. If you know these little guys,

to Go or not to Go

I know what you are thinking , but not this time, it is a story for an other day. Why ? Mainly because Golang doesn’t have a lot of good repositories to read an audio file in a cross platform fashion. Which language then ?

Let me introduce you this little guy.

Getting started with Rust and Dart FFI 🏎

What I like the most about Rust is that it is relatively easy to learn coming from Golang, and there is no struggle at all to find free resources to learn at your own pace.

When you search for rust audio cross platform in your favorite search engine you'll most likely end up on this forum thread.

Looks like a match 💓: 339 ⭐ 116,656 dowloads. It has support for Macos, Linux and Windows + all audio formats. Niiice! Let the fun begin.

⚠️️ If you want to follow along make sure you have installed Rust on your computer and (obviously) Flutter

Let’s give a name to our pet project. I called mine “rusty_ffi”. First we will scaffold our dart project with stagehand. In your terminal type:

mkdir rusty_ffi

cd rusty_ffi

stagehand console-full

pub get

Let’s create your (first ?) Rust project. In your terminal type:

cargo new rusty_ffi

It will scaffold a project for you like this :

rusty_ffi

├── Cargo.toml

└── src

└── main.rs

Let’s move the content of the rusty_ffi folder into the root folder :

cp -r rusty_ffi/. .

rm -rf rusty_ffi

You should end up with a folder that looks like this one :

├── analysis_options.yaml

├── bin

│ └── main.dart

├── Cargo.toml

├── CHANGELOG.md

├── lib

│ └── rusty_ffi.dart

├── pubspec.lock

├── pubspec.yaml

├── README.md

├── src

│ └── main.rs

└── test

└── rusty_ffi_test.dart

You can run your “hello world” program shipped by default using cargo run . Yeah congrats if you made it to this point ! Now let's add our audio library to Cargo.toml :



name = “rusty_ffi”

version = “0.1.0”

authors = [“Yourname <

edition = “2018” [package]name = “rusty_ffi”version = “0.1.0”authors = [“Yourname < youremail@yourdomain.com >”]edition = “2018” [dependencies]

rodio = “0.9.0” #added rodio library

Now that we have our audio library added as a depedency to our project, let’s play some audio shall we. According to the docs of Rodio, playing an audio file called beep.wav is as simple as this:

use rodio;

use std::fs::File;

use std::io::BufReader;

use std::thread;

use std::time::Duration; fn play_once(file_name : &str){

let device = rodio::default_output_device().unwrap(); // instantiate rodio with the default speaker

let file = File::open(file_name).unwrap(); // open file named beep.wav

rodio::play_once(&device, BufReader::new(file)).unwrap(); // play audio

thread::sleep(Duration::from_millis(1500)); // wait 1.5 s until stop playing

}

fn main() {

play_once("beep.wav");

}

Don’t worry it’s not a pocket call just your program running. Yeaaah !

A little bit of refactoring

We need to transform our program into a library by changing main.rs into lib.rs .

# ...

# occulted

[lib]

name = "play_once"

crate-type = ["cdylib"] #dynamic library [dependencies]

rodio = "0.9.0"

Then export our C API :

// ...

// occulted

use std::ffi::CStr;

use std::os::raw::c_char; #[no_mangle]

pub extern "C" fn play_once(ptr: *const c_char) { //here is the trick

let cstr = unsafe { CStr::from_ptr(ptr) }; //just two lines changed

let device = rodio::default_output_device().unwrap();

let file = File::open(cstr.to_str().unwrap()).unwrap();

rodio::play_once(&device, BufReader::new(file)).unwrap();

thread::sleep(Duration::from_millis(1500));

}

Build your library with cargo build . Finally, in your bin/main.dart program add this :

import 'dart:ffi' as ffi;

import 'dart:ffi';

import 'package:ffi/ffi.dart'; typedef NativeRustPlayOnceFunction = ffi.Void Function(ffi.Pointer<Utf8>);

typedef NativePlayOnceFunction = void Function(); main() {

ffi.DynamicLibrary dl =

ffi.DynamicLibrary.open("target/debug/libplay_once.so");

var play_once =

dl.lookupFunction<NativeRustPlayOnceFunction, NativeRustPlayOnceFunction>(

"play_once");

final Pointer<Utf8> song = Utf8.toUtf8("data/beep.wav").cast();

play_once(song);

}

Conclusion 🏁

Run your dart cli app dart bin/main.dart , Profit ! 💸

In this article you:

learned low level programming, congrats mate.

learned how to call a Rust function from Dart

revolutionized the Music Software industry, yep true story.

I hope you enjoyed my first article, if so drop (pun intended 🦀) me some claps 👏. You can find the source code on my repository. Let me know in the comment section below if you have some difficulties or suggestions to help me improve the quality of my futures blog posts. Follow me and stay tuned for my next articles, I’m working on integrating Flutter Web + Rust via Web Assembly. Flutter for the win 🏆.