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.

Roadmap

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:

Getting Started with Rust on iOS (that’s this post) Passing Data between Rust and iOS Sharing a View Model between Rust and iOS Writing a Flickr Client in Rust Tying it All Together: Rustorama

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.

Installing Rust with multirust

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!

Building a Cross Compiler

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)

Hello, World: Building the Rust Library

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.

Hello, World: Building the iOS App

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!

Next Steps

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.