You don’t want rust in your android, but you might want Rust in your Android.
I like Kotlin, and I’m very impressed with the content being written in Rust. I knew it should be possible to call Rust from my Android app.
Because I love fighting with the compiler I wanted to see if I could get it working for fun. (I got it working!) I wrote this blog post so others could try it out, and so I could refer back when I try to do something again in the future.
The star of the show is Mozilla’s UniFFI library that does a lot of the hard work. A high level view is that it generates Rust and Kotlin1 that are made for each other. That way your Kotlin code can invoke the Rust methods without worrying about Foreign Function Interface (FFI) for talking cross-language.
The rest of this post will walk through
- configuring your development environment
- creating a basic Rust library with UniFFI-generated scaffolding
- generating Kotlin using UniFFI
- integrating the Rust and Kotlin in an Android app
I’ll assume you have a basic Rust (via cargo) and Android (via Android Studio) environment installed.
Step 1 - Configure your Rust + NDK environment
This was (I believe) the most annoying part to get right. You can either manually configure the Android Native Development Kit (NDK) or you can use
cross that downloads a Docker image that’s ready to go. I’d recommend setting up the NDK locally (builds faster2), but falling back on
cross (easier default setup) if you get stuck.
Option A - Use Docker-based
- Install Docker Desktop, OrbStack, Rancher Desktop, or your favorite tool. If you can run
docker run --rm hello-world, then you’re good.
- Install cross.
- If you’re happy with the
cross(seen here), you’re done. Otherwise, you’ll need to build new Docker images with the desired Android version (instructions here)
- That’s it! Go to “Step 2 - Make a Rust library”.
Option B - Configure Android NDK locally
Open Android Studio, and navigate to SDK Manager > SDK Tools > NDK (Side by Side) as laid out on the Android Developer site.
Locate which NDK version you have…
… and set it to your
NDK_PATH environment variable.
<⚠️> Android replaced
libuwind in NDK 23 which breaks the compilation step. Fortunately there’s a workaround3 that I’ll summarize. If you’re using NDK 23.x or higher, you’ll either need to use a
nightly version of Rust or run the following from your terminal.
You’ll be able to see the C libraries for each of the architecture-Android version combinations. I’ve modified the output to be more readable.
I’m going to build for an Android
minSdkVersion of 24, so these are the four libraries I’ll use.
Open (or create) your
$HOME/.cargo/config file. Add each of the target linkers. Please note:
- The path has to be absolute.
armv7a’s target name and clang name are different and it is “androideabi” as opposed to “android”.
Finally, add the targets to your Rust environment.
Step 2 - Make a Rust library
For our example, we’re going to make a simple library that has two methods: reverse a string (“hello” -> “olleh”) and reverse an integer (123 -> 321).
Let’s start by making the library using
Inside the generated
src/lib.rs file, I throw in some (ChatGPT-assisted) Rust code to reverse a string and integer as well as some tests.
reverse-rs/ folder, run
cargo test and make sure everything looks good.
Step 3 - Prepare the Rust for Android
Here’s where the UniFFI magic comes in! We’re going to define our reverse string and integer methods in UniFFI’s special language which we’ll then use to generate both the Rust and Kotlin code.
Cargo.toml file to look like this.
This snippet does three key things.
- Make the library a cdylib crate. I dropped the
-rsfrom the name because hyphens aren’t allowed.
uniffias a dependency.
uniffias a build dependency.
Write the UDL file
UniFFI uses it’s own special UniFFI Definition Language (UDL) for describing interfaces. I made
Write the Rust generator
Create a build file in the the top level folder (i.e.
reverse-rs/build.rs) and have it point to the UDL file.
uniffi::include_scaffolding macro on the top of the
lib.rs file, to generate the Rust scaffolding.
Step 4 - Compile the Rust library
If on step 1 you setup
cross use that, or if you went through all the NDK-related steps, use
cargo build ....
The end result will be a
.so file in your corresponding
To get these ready for the Android app you’ll need to:
- move everything to the appropriate Android ABI directory in a
Here’s a command that will do all of it for you.
Here’s where you’ll be at the end.
Step 5 - Generate the Kotlin methods
Add the following to the bottom of your
Then generate the Kotlin code!
This creates a new file
reverse-rs/src/uniffi/reverse/reverse.kt with a ton of boilerplate but also our methods!
Step 6 - Create the Android app
For demonstration purposes, I’m going to make a new project via Android Studio > File > New Project… and use the “Empty Activity” template, but I’m assuming you’re familiar with Android development and can make your own choices.
Add the JNA dependency
The UniFFI library depends on Java Native Access (JNA), so add the
Make sure to sync your Gradle files.
Copy over generated files
- Move the
- Move the
Use the generate Kotlin library
Your IDE will now autocomplete, and you’ll have access to
uniffi.reverse.reverseInteger. Here’s what my class looks like.
Run it and 🤞🏼 that you don’t have any errors!
Congratulations! You’re running Rust in Android!
Bonus - Suggestions and Resources
There are a few tweaks that you can do and other things I came across that you might find interesting/helpful.
cross build or
cargo build, adding the
--release flag really cuts down on size (but it ~doubles the build time).
uniffi-bindgen to its own crate
If you want to iterate faster on your Rust + Kotlin, you’ll need to have the
uniffi-bindgen logic in it’s own crate. Otherwise, you’ll hit this error.
Helpful Docker guide
Guillaume Endignoux’s very thorough blog post, Compiling Rust libraries for Android apps: a deep dive, was super helpful for me. It is much more comprehensive that my post.
More than just UniFFI
There is a neat alternative to UniFFI called Diplomat for which Mark Hammond(from Mozilla) wrote a nice comparison, Comparing UniFFI with Diplomat.
I’m personally excited for uniffi-kotlin-multiplatform-bindings which is still new-ish but could really move the Kotlin ecosystem forward.
Lammert Westerhoff helpfully pointed out that if you run
cargo build with the
--lib flag (in step 4), the subsequent
bin additions to the
Cargo.toml (in step 5) won’t break future attempts at
cargo building. I’ve updated the code block in step 4 to include the
heinrich5991 also mentioned something similar earlier, but I did not apply their feedback to my blog post. 🤦
Thank you to my friends
Special thanks to my friends who helped me with this post.
- Richard Moot - workshopping the title and hook
- Gary Guo - correcting my poor grammar and helping with the flow
- Ray Ryan - trying the recipe out, finding quite a few issues, and letting me know about them before I embarrassed myself
Let me know what you think!
Please feel free to reach out on email, the Fediverse @[email protected], or Twitter @SalTesta14.