Chris Downie and Sam Landfried
Editor’s note: This is the first post in our series on building an iOS app in Rust.
The vast majority of apps that get developed for iOS and Android are written in the native languages provided by the platform: Swift or Objective-C on iOS, and Java on Android. I don’t expect that to change any time soon; however, sometimes there’s a need for something more.
If you’re developing an app on multiple platforms more or less independently, you’ll face certain challenges. Functionality will be duplicated (obviously), which means you have two different codebases that need to be maintained. Bugs can crop up on one platform or the other or both, and new features have to be added to each. An alternative approach, which Dropbox talked about at last year’s UIKonf and CppCon (video 1, video 2), is to develop a library that can be shared by both platforms.
Developing a cross-platform library is challenging for a number of reasons, not the least of which that the choice of language is pretty limited. There are some tools, like Google’s J2ObjC, which allow you to write in one platform’s language (Java, in this case) and have it automatically translated into another platform’s language (Objective-C). A more traditional approach is to develop in C or C++, languages that are portable to both platforms and that can be called by both platforms.
I’m not going to try and sell you on the merits of going down this road—there are big pros and big cons. I suspect that this approach is probably the wrong one for most applications, but it’s still a very interesting area to explore. C++ is the reigning king of the hill for portable, native library development, but there’s a new challenger with an exciting amount of development behind it.
Rust describes itself as “a systems programming language that runs blazingly fast, prevents almost all crashes and eliminates data races.” It’s been in development for quite a while (about eight years, at the time of this writing), and the Rust team released version 1.0 on May 15 of this year.
Rust is often compared with Go (probably because they entered the public eye around the same time and both described themselves as systems programming languages), but the comparison isn’t really fair to either: they have very different aims in mind. Rust’s goal is to be a safer alternative to C++ without giving up the control and performance that C++ provides.
This post is the first in a (long) series. We’re going to end up with a simple but nontrivial app that can ask Flickr for its recent photos, display
thumbnails in a UICollectionView and show the full image when a thumbnail
is tapped:

The trick is that we’re going to put all the smarts in the Rust layer. We’ll roughly follow an MVVM (Model—View—View Model) architecture where the Model and View Model layers are implemented in Rust, and the iOS side is just the View layer. (This app is a variant of one that you’ll build while going through the next edition of our iOS Programming Guide, to be published in the second half of 2015.)
While the app is simple, we’ll touch on a lot of advanced topics getting Rust and iOS to play nicely together. Here’s the plan for this blog series:
I’ll cover some basic Rust syntax as we go through the post, but if this is your first experience with the language, consider reading through the Rust book. I’ll be glossing over some fairly advanced things in the later posts out of necessity.
This section assumes you’re running Mac OS X and have not installed Rust. If either of those isn’t true, you’ll need to tweak these instructions.
There are three different versions of the Rust compiler available at any given time: stable, beta and nightly. Every six weeks, the current nightly becomes the new beta and the current beta becomes the new stable; this is called the six-week train model.
A slick tool for managing multiple Rust installations is multirust. We’ll use it to manage a version of the Rust compiler for targeting iOS.
Go ahead and install multirust and set the nightly as your default Rust compiler. (I’ve omitted copying the instructions from the multirust repository in case they change in the future, but at the moment, there’s a one-liner you can run to set everything up.) You should be able to replicate the following, although your build dates and version hashes will be different:
$ multirust show-default
multirust: default toolchain: nightly
multirust: default location: /Users/john/.multirust/toolchains/nightly
rustc 1.1.0-nightly (c42c1e7a6 2015-05-02) (built 2015-05-01)
cargo 0.2.0-nightly (efb482d 2015-04-30) (built 2015-05-01)
$ rustc -V
rustc 1.1.0-nightly (c42c1e7a6 2015-05-02) (built 2015-05-01)
Try a “Hello, World” program:
$ cat > hello-world.rs
fn main() {
println!("Hello, world!");
}
<Ctrl-D>
$ rustc hello-world.rs
$ ./hello-world
Hello, world!
This part is not for the faint of time: this will take at least an hour, maybe a few. We need to build a Rust toolchain that can create executables for all five iOS platforms: armv7, armv7s, arm64 and the 32- and 64-bit simulators. We’re going to build off of the master branch, the same as the nightly releases.
First, clone the Rust compiler’s repository and get its submodules (this assumes you have SSH set up with Github; feel free to clone however you normally would):
$ git clone git@github.com:rust-lang/rust.git
$ cd rust
$ git submodule update --init --recursive
Next, create a subdirectory for all the build artifacts and cd into it:
$ mkdir build
$ cd build
Finally, configure the build to target all five architectures, and set up an appropriate installation prefix. In the following, we’ll install to our home directory:
$ ../configure --target=armv7-apple-ios,armv7s-apple-ios,i386-apple-ios,aarch64-apple-ios,x86_64-apple-ios --prefix=$HOME/rustc-ios
... snipping lots of output ...
At last, start the build:
$ make -j8 && make install
Go watch a movie or something; come back when your laptop fans stop spinning.
All done? Let’s tell multirust about your brand new toolchain, naming it ios:
$ multirust update ios --link-local $HOME/rustc-ios
multirust: creating link from /Users/john/rustc-ios
One final cleanup step. multirust expects to be able to find Cargo, Rust’s package manager and build tool extraordinaire, but we’ve only installed the Rust compiler itself. We don’t really need to go and build Cargo, because the Rust nightly you installed in the previous section also installed Cargo. Instead, we can create a symlink in the right place:
$ ln -s $HOME/.multirust/toolchains/nightly/bin/cargo $HOME/rustc-ios/bin
Ask multirust to run your ios version of rustc and Cargo, just to make sure
all is well:
$ multirust run ios rustc -V
rustc 1.1.0-dev (ce1150b9f 2015-05-04) (built 2015-05-03)
$ multirust run ios cargo -V
cargo 0.2.0-nightly (efb482d 2015-04-30) (built 2015-05-01)
Now that all the painful waiting is done, let’s get to the fun part: writing a Rust library and calling it from an iOS app. Create a clean working space somewhere, and create directories to hold the Rust component and the iOS component:
$ mkdir -p rust-ios-part-1/{ios,rust}
$ cd rust-ios-part-1
You probably noticed above that we were able to use multirust run ios ... to
run commands from the ios toolchain we installed. It would be awfully tedious
to type that every time, so multirust provides a directory-level override. Set
that up now, so that any Rust commands you issue in this directory (or any
descendent directory) will use your ios toolchain:
$ multirust override ios
multirust: using existing install for 'ios'
multirust: override toolchain for '/Users/john/rust-ios-app-part-1' set to 'ios'
We’ll build the Rust library first. Change into your rust directory and tell
Cargo to create a new library called hello for you:
$ cd rust
$ cargo new hello
$ cd hello
If you dig around under hello, you’ll find two files:
Cargo.toml is the manifest file describing your library. Cargo.toml is like a Rust-specific Makefile. It contains the names of your input files and any library or executable targets your project defines, as well as any dependencies your project uses.
src/lib.rs is the placeholder file created for you. This is where we’ll put whatever Rust code we write. (In later posts we’ll use more files, but this one is fine for “Hello, world.”)
Let’s start by updating src/lib.rs. There are Rust plugins for many popular editors; Google around for yours if you’d like. Delete the default code in src/lib.rs and replace it with this:
#[no_mangle]
pub extern fn rust_hello_world() -> i32 {
println!("Hello, I'm in Rust code! I'm about to return 10.");
10
}
Walking through each line:
#[no_mangle] tells the Rust compiler not to perform its default name mangling on the function name. This will result in the rust_hello_world symbol being exported just like it would if it had been written in C.pub marks the function as public, i.e., callable by code outside of this library. extern fn tells the Rust compiler to use C calling conventions for this function, meaning any language that knows how to call C functions can now call this Rust function. rust_hello_world() is the name of the function; the empty parentheses indicate it takes no arguments. -> i32 states that the return value of this function is an i32, i.e., a 32-bit signed integer.println!("..."); will print the string on stdout. It’s analogous to
Swift’s println function. (The ! means that println! in Rust is actually a macro, but that’s not important for our purposes.)10, as the last line of the function without a semicolon, is the value
returned by the function. Rust does have a return keyword, so we could have
written return 10; instead, but that isn’t idiomatic. The Rust book’s
functions chapter discusses this in more detail.At this point, if you were developing a normal Rust library, you could build it
via cargo build and go about your merry way. We have a few more steps to
take to build a library suitable for iOS, though. By default, Cargo will create
libhello.rlib, where rlib stands for “Rust library.” We need a traditional
static library, so update Cargo.toml, adding the [lib] section below:
[package]
name = "hello"
version = "0.1.0"
authors = ["John Gallagher <jgallagher@bignerdranch.com>"]
[lib]
name = "hello"
crate-type = ["staticlib"]
Now we can tell Cargo to build a static library, and we’ll specify that we want one for the 64-bit iOS simulator:
$ cargo build --target x86_64-apple-ios
Compiling hello v0.1.0 (file:///Users/john/github/bignerdranch/rust-ios-app-part-1/rust/hello)
note: link against the following native artifacts when linking against this static library
note: the order and any duplication can be significant on some platforms, and so may need to be preserved
note: library: System
note: library: objc
note: framework: Foundation
note: framework: Security
note: library: pthread
note: library: c
note: library: m
$ ls target/x86_64-apple-ios/debug/
build/ deps/ examples/ libhello.a native/
For real development, we’ll actually want to use lipo to create a fat library
for all five iOS architectures. That isn’t something currently supported by
Cargo, so there is a Makefile in the repo for this blog post that
will tell Cargo to build all five architectures and then combine them into a
fat library.
There’s one last thing we need: a C header file that we can import on the iOS
side. There isn’t a tool (yet) for creating C headers for a
Rust library, so we will create a header manually. Still in your rust
directory, create hello.h and give it the following contents:
#pragma once
#include <stdint.h>
int32_t rust_hello_world(void);
This type signature matches our Rust function above: it takes no arguments and returns a 32-bit signed integer.
Hop into Xcode and create a new Single-View project. Put it into the
rust-ios-part-1/ios directory you created above. I’ll assume you want to use
Swift; if you’re using Objective-C, things are actually simpler, so you can
probably manage just fine.
Find the hello.h and libhello.a files you created in the previous section,
and drag them both into your Xcode project. (Make sure you grab the
libhello.a under target/x86_64-apple-ios/debug, or the one you created
using the Makefile, if you did that.) In order for your Swift code to be able
to see hello.h, you need to include it in your app’s bridging header. By
default, Swift projects don’t have one. You can either create one
manually or add a new class to your project, select
Objective-C as the language, click “Yes” when Xcode asks if you want a bridging
header, then delete the Objective-C files.
Once you have a bridging header, add hello.h to it:
//
// Use this file to import your target's public headers that you would like
// to expose to Swift.
//
#import "hello.h"
Open up AppDelegate.swift, and try calling your Rust function:
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
-> Bool
{
let result = rust_hello_world()
println("I called Rust and got \(result)")
return true
}
Try to build and run your app. You’ll need to have a 64-bit simulator selected,
such as the iPhone 6 simulator. If you get a linker error about missing the
symbol _rust_hello_world, make sure you added the correct libhello.a to
your app target.
You should see the following in the Xcode console:
Hello, I'm in Rust code! I'm about to return 10.
I called Rust and got 10
Congratulations! You’ve written a Rust library and used it on iOS!
In the next post, we’ll build on all the setup you’ve done. We’ll talk about how to pass non-primitive data types like strings to and from Rust, as well as how to pass more complicated data structures and objects. Stay tuned!
All the code, both Rust and Swift, from this post is on GitHub.
Editor’s note: Be sure to check out the other posts in this series: Part 2, Part 3.
Interested in learning more about our basic and advanced iOS Courses?
Learn from the experts at a Big Nerd Ranch Bootcamp!
Interested in leveling up your coding skills from the same authors of the Big Nerd Ranch Guide? Subscribe to The Frontier today!
Chris Downie and Sam Landfried
Chris Downie and Sam Landfried
Nick Teissler
Chris Guzman
Jeremiah Jessel