Big Nerd Ranch Logo • • •
  • work
  • team training
  • bootcamps
  • books
  • screencasts
  • resources
  • blog
  • work
  • team training
  • bootcamps
  • books
  • screencasts
  • resources
  • blog

Building an iOS App in Rust, Part 2: Passing Primitive Data Between Rust and iOS

John Gallagher
John Gallagher
Aug 21, 2015 • iOS
  • Bird
  • Facebook
  • LinkedIn__x28_alt_x29_
  • Shape
  • Combined Shape

Editor’s note: This is the second post in our series on building an iOS app in Rust.

Welcome to Part 2 of our “Building an iOS App in Rust” series! If you haven’t read Part 1 already, please do that. Running the code in this post will require you to have set up a Rust toolchain for iOS.

Last time, we built a simple “Hello, World!” library in Rust and successfully linked it into an iOS app. This time, we’re going to explore more of Rust’s FFI, or foreign function interface, layer. The Rust book has an excellent chapter on Rust’s FFI facilities; however, it is almost exclusively about how to call C libraries from Rust, whereas we want to go the other direction and call Rust from Swift.

Unlike Part 1, I’m not going to walk you through generating a Rust library and an iOS project to run all the sample code in this post. The code is hosted on GitHub, so you can check it out at your leisure.

Note: In Part 1, we scoped out a five-part plan for this series, and Part 2 was supposed to be “Passing Data Between Rust and iOS.” It started to get a little long, though, so we’ve split the data passing into two. There will be more than five parts in the end.

Designing an API for our Rust layer

API design is hard in any language. We’re throwing a peculiar wrench into things with this project:

  • We’re writing a library in Rust, but need to expose its public interface in a C header, so we’re restricted to data types that are compatible with C. This means, for example, no generics or enums that hold data. Swift supports both of those itself, but not in a way that’s compatible with Rust.
  • On the Swift side, it looks like we’re talking to a C library. This will be a little painful at times, too, as anyone who has worked with Core Foundation in Swift has already experienced.
  • We’re going to make use of Swift 2’s ability to pass pointers to Swift functions as C function pointers. To accomplish all the parts of this post in Swift 1, you would need to drop down to Objective-C some.

Basically, we have two modern, advanced languages talking to each other… but they both think they’re talking to C. Great.

At CppCon 2014, Stefanus DuToit gave an excellent talk called Hourglass Interfaces for C++ APIs. It’s well worth watching, even if you don’t speak C++. The talk is largely a sales pitch to C++ library writers, giving them reasons to create, and advice on creating, C-compatible wrappers for their APIs. We’re going to follow a lot of his advice when wrapping Rust code:

  • We’ll make use of pointers to forward-declared C structs, also known as opaque pointers. This will allow us to send arbitrarily complex data structures across the language boundary. Rust and Swift don’t generally know how each other’s data structures are laid out in memory, but both of them know how to pass pointers around.
  • We’ll avoid the plain C integral types like int and long in favor of explictly sized types like int32_t and uint8_t. We would end up doing this anyway in Rust: idiomatic Rust doesn’t use a basic integer type whose size changes depending on your target architecture, like int in C or Int in Swift. Rust tends to use i32 instead, which is always guaranteed to be 32 bits.
  • We’ll use C function pointers to handle callbacks between the two languages.
  • Breaking a little with the Hourglass Interfaces talk, we will use a couple of very basic non-opaque structs. Rust has an annotation that tells it to lay out structs in memory just like a C compiler would, so Rust and Swift will agree on where to find the struct members.

Let’s start with exchanging primitive numbers.

Sending primitives between Rust and iOS

In Part 1 of this series, you already saw a Rust function that returns an i32, a 32-bit signed integer:

#[no_mangle]
pub extern fn rust_hello_world() -> i32 {
    println!("Hello, I'm in Rust code! I'm about to return 10.");
    10
}

Here are more examples of Rust functions that take and return various primitives:

// C declaration: int32_t return_int32(void);
#[no_mangle]
pub extern fn return_int32() -> i32 {
    10
}

// C declaration: uint16_t triple_a_uint16(uint16_t x);
#[no_mangle]
pub extern fn triple_a_uint16(x: u16) -> u16 {
    x * 3
}

// C declaration: float return_float(void);
#[no_mangle]
pub extern fn return_float() -> f32 {
    10.0
}

// C declaration: double average_two_doubles(double x, double y);
#[no_mangle]
pub extern fn average_two_doubles(x: f64, y: f64) -> f64 {
    (x + y) / 2.0
}

Rust’s i32 is C’s int32_t is Swift’s Int32. All the normal sizes (8, 16, 32, 64) are available in all three languages, and unsigned variants are as well (u32 <-> uint32_t <-> UInt32, etc.). Swift’s Float is Rust’s f32, and Swift’s Double is Rust’s f64.

There’s one last primitive I want to discuss. I said earlier that Rust didn’t have a default integer type that changes size based on the target platform (like how Swift’s Int can be either 32 or 64 bits). That’s true, but it does have something similar that comes up in several important Rust APIs: usize is a “pointer-sized unsigned integer.” Rust uses usize for things like the length of a string, or the number of elements in an array.

Rust uses an unsigned integer for things like array length; Swift, on the other hand, uses Int, which is signed. To reconcile this, we’ll pass Rust’s usize as a C size_t. Because size_t is used in C for things like length, Swift bridges it in as Int. We’ll need to do a little casting on the Rust side, but that’s ok. Here is a Rust function that works with size_t, internally converting to usize:

use libc::size_t;

#[no_mangle]
pub extern fn sum_sizes(x: size_t, y: size_t) -> size_t {
    let x_usize = x as usize;
    let y_usize = y as usize;
    (x_usize + y_usize) as size_t
}

The first line is importing a type, size_t, from the libc crate. libc provides platform-specific bindings to native C data types, like size_t (and things like void - that’ll come up in a few minutes).

In the body of the function, we use the as keyword to cast between size_t and usize. Like Swift, Rust doesn’t do implicit conversions, so we must be explicit.

We can call all of these from Swift just like regular functions:

func exercisePrimitives() {
    let a: Int32 = return_int32()
    let b: UInt16 = triple_a_uint16(10)
    let c: Float = return_float()
    let d: Double = average_two_doubles(10, 20)
    let e: Int = sum_sizes(20, 30)
    print("primitives: \(a) \(b) \(c) \(d) \(e)")
}

Sending strings from iOS to Rust

In the modern Unicode world, strings are surprisingly complex. Rust’s built-in string types are always guaranteed to hold valid UTF-8 encoded data. This means you can’t create a string out of arbitrary bytes unchecked; instead, you can use the std::str::from_utf8 function to attempt to interpret a slice of bytes into a string. from_utf8 returns a Result<&str, Utf8Error> type: an enum that is either Ok with an associated &str value or Err with an associated Utf8Error value.

Here’s a Rust function that uses Rust’s match construct (analogous to Swift’s switch) to unpack the returned Result:

use std::str;

// Take a slice of bytes and try to print it as a UTF-8 string.
fn print_byte_slice_as_utf8(bytes: &[u8]) {
    match str::from_utf8(bytes) {
        Ok(s)    => println!("got {}", s),
        Err(err) => println!("invalid UTF-8 data: {}", err),
    }
}

Note that this function is not marked pub or extern and doesn’t have the #[no_mangle] attribute. Without pub, it’s a private function: we’re going to use it internally, but not expose it to Swift.

Let’s write a Rust function that takes a raw pointer and a length in bytes, converts it to a Rust slice, and calls our print_byte_slice_as_utf8 function:

use std::slice;

// C declaration: void utf8_bytes_to_rust(const uint8_t *bytes, size_t len);
#[no_mangle]
pub extern fn utf8_bytes_to_rust(bytes: *const u8, len: size_t) {
    let byte_slice = unsafe { slice::from_raw_parts(bytes, len as usize) };
    print_byte_slice_as_utf8(byte_slice);
}

The function declaration is straightforward. We take two arguments: the first is a pointer to 8-bit unsigned integers (i.e., bytes), and the second is the length. This is standard fare for passing around arrays in C.

The first line of the function body is a little hairy; let’s unpack it from the inside out:

  • slice::from_raw_parts(bytes, len as usize) passes our two arguments to the std::slice::from_raw_parts function from Rust’s standard library. That function returns a &[u8], a slice of bytes.
  • unsafe { … } is our first encounter with Rust’s unsafe keyword. One of Rust’s claims to fame is memory safety: in pure, safe Rust, it’s impossible to ever access uninitialized or invalid memory. Unfortunately, when we’re calling Rust from another language, none of those guarantees exist. Someone might pass NULL as the argument to bytes, for example. from_raw_parts is marked as an unsafe function because the compiler can’t guarantee (at compile time) that the pointer and length you’re providing are actually valid. It also doesn’t know how long the pointer will be valid. Because both of these issues are giant holes that could cause our program to crash and burn, we’re forced to wrap the call in an unsafe block to tell the compiler, “I know this isn’t safe, but I want you to go ahead and do it anyway. I promise it’ll be okay.”
  • Finally, we store the returned slice into the byte_slice variable. Rust, like Swift, supports type inference; we could’ve written let byte_slice: &[u8]= … if we wanted to.

To call this function from Swift, we need to convert a String into UTF-8-encoded data, then pass the data pointer and length:

let myString = "Hello from Swift"
let data = myString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
utf8_bytes_to_rust(UnsafePointer<UInt8>(data.bytes), data.length)

Rust and Swift both know how to work with old style, null-terminated C strings, as well. Here’s the Rust side:

use std::ffi::CStr;

// C declaration: void c_string_to_rust(const char *null_terminated_string);
#[no_mangle]
pub extern fn c_string_to_rust(null_terminated_string: *const c_char) {
    let c_str: &CStr = unsafe { CStr::from_ptr(null_terminated_string) };
    let byte_slice: &[u8] = c_str.to_bytes();
    print_byte_slice_as_utf8(byte_slice);
}

This time, we use the std::ffi::CStr type, which represents a “borrowed” C string. (We’ll talk more about borrowing and ownership shortly; for now, think of it as “a string someone else is responsible for deallocating”.) Like from_raw_parts, CStr::from_ptr is an unsafe function because the Rust compiler can’t know at compile time whether the argument is actually a valid null-terminated string.

Once we have a &CStr, we can use its to_bytes() method to get a view into it as a slice of bytes, and then we can call our same print_byte_slice_as_utf8 function. Note that no memory copies happen in this function: the first line just wraps up the pointer into a new Rust type, and the second line gives us a view into those same bytes.

Calling this function from Swift is trivial: Swift will automatically convert Strings into C-style strings when you try to pass them to functions that take const char *:

let myString = "Hello from Swift"
c_string_to_rust(myString)

Sending strings from Rust to iOS

I mentioned earlier that Rust stores strings encoded as UTF-8. This does not include a C-style null-terminating byte. The cleanest way to pass a Rust string out across the FFI boundary is to pass out an array of bytes and a length. We could write a C interface that needs to return both, but then we have the always-slightly-unwieldy output pointer type; e.g.,

// Unwieldy: Return a pointer to bytes and fill in `len` with its length.
const uint8_t *get_string_from_rust(size_t *len);

Instead, we’ll define a Rust struct and tell the compiler to use a binary representation compatible with C:

#[repr(C)]
pub struct RustByteSlice {
    pub bytes: *const u8,
    pub len: size_t,
}

Now our C interface looks like this, where we pass back a small, 16-byte structure by value:

struct RustByteSlice {
    const uint8_t *bytes;
    size_t len;
};

struct RustByteSlice get_string_from_rust(void);

Finally, the Rust implementation:

#[no_mangle]
pub extern fn get_string_from_rust() -> RustByteSlice {
    let s = "This is a string from Rust.";
    RustByteSlice{
        bytes: s.as_ptr(),
        len: s.len() as size_t,
    }
}

We used type inference, but the type of s is &str; that is, a “borrowed string” (more on “borrowed” momentarily). Two methods on &str are .as_ptr(), which returns a pointer to the string’s bytes, and .len(), which returns the number of bytes in the string. Note that this is different from Swift, which does not define a String’s length. Swift provides several different views into the components of a String; see Strings in Swift 2.0 on the official Swift blog for details.

Did you notice anything unusual about the Rust implementation? Even though we’re trafficking in raw C pointers, we never had to use the unsafe keyword. Why not? The Rust compiler can statically guarantee that everything inside the get_string_from_rust function is safe. It’s perfectly safe to create a raw pointer to s’s bytes; in general, it’s perfectly safe to create and pass around raw pointers. Dereferencing pointers is unsafe, but get_string_from_rust doesn’t do that itself. Presumably whoever calls get_string_from_rust is going to dereference the returned pointer, but that’s their problem to deal with.

That does bring up a question, though. How long is the pointer get_string_from_rust returns going to be valid? Is it really safe for Swift to dereference it? Does someone need to deallocate it? (How would they? free, or some other function?)

To answer that, we need the full type of s. A few paragraphs ago, I said it was &str, but that’s only part of the story. All Rust references have an associated lifetime. Most of the time you don’t have to work with lifetimes yourself, as the Rust compiler can infer them. Our s variable actually has a special lifetime. Because we initialized s with a string literal, its full type is &'static str. The static lifetime outlives all other lifetimes. For us, this means that the string s points to will always be valid (similar to a static const char * string in a C program). It is safe for Swift to access the pointer we return at any time, as it will always point to the bytes making up the string This is a string from Rust..

So how do we call this function from Swift? The RustByteSlice struct gets bridged in as this Swift type:

struct RustByteSlice {
    var bytes: UnsafePointer<Int8>
    var len: Int
}

We can write an extension on this bridged type to convert to an UnsafeBufferPointer<UInt8> (which will always succeed) and to a String (which will only succeed if the byte slice contains valid data according to the expected encoding):

extension RustByteSlice {
    func asUnsafeBufferPointer() -> UnsafeBufferPointer<UInt8> {
        return UnsafeBufferPointer(start: bytes, count: len)
    }

    func asString(encoding: NSStringEncoding = NSUTF8StringEncoding) -> String? {
        return String(bytes: asUnsafeBufferPointer(), encoding: encoding)
    }
}

Now we can ask Rust for a String, and print it:

let rustString = get_string_from_rust()

if let stringFromRust = rustString.asString() {
    print("got a string from Rust: \(stringFromRust)")
} else {
    print("Could not parse Rust string as UTF-8")
}

Next Steps

So far, all the data passing we’ve seen is quite limited. We can pass around primitives without any problem: in Rust and Swift (as well as C and a host of other languages), primitives are passed by value. Our string-passing functions are more problematic:

  • In the Swift-to-Rust functions utf8_bytes_to_rust and c_string_to_rust, Swift owns the memory backing the string. The pointer it gives to Rust is valid for the duration of the function call, but may not be valid afterwards. If Rust were to squirrel away the pointer Swift gives it and try to access it later, Bad Things™ will probably happen.
  • Our Rust-to-Swift function get_string_from_rust is safe, but only because it’s passing a pointer to a static string.

Both directions are insufficient for building a real library. We need the ability to pass objects that live for longer than one function call, but we need the flexibility to work with dynamic data. Ultimately, we need to be able to reason about the ownership of our objects, and that will be the subject of the next post.

Editor’s note: Be sure to check out Part 3 of the series, where John covers how to pass more complex data than strings, and how to correctly and safely manage ownership of that data.

  • Bird
  • Facebook
  • LinkedIn__x28_alt_x29_
  • Shape
  • Combined Shape
John Gallagher
more by
John Gallagher

Big Nerd Ranch iOS Courses

Interested in learning more about our basic and advanced iOS Courses?
Learn from the experts at a Big Nerd Ranch Bootcamp!

LEARN MORE

Big Nerd Ranch Screencasts

Interested in leveling up your coding skills from the same authors of the Big Nerd Ranch Guide? Subscribe to The Frontier today!

LEARN MORE

Related Posts:

Pro Pattern-Matching in Swift

Nick Teissler

Life in Technicolor

Dan Berry

Core Graphics, Part 3: Lines

Mark Dalrymple

Core Graphics, Part 2: Contextually Speaking

Mark Dalrymple

Producing a CircleCI Test Summary with Fastlane

Jeremy Sherman

Recent Comments

comments powered by Disqus
  • Twitter
  • Facebook
  • Instagram
  • Github
  • LinkedI
  • Youtube
Subscribe to Our Newsletter

App Development

  • Work

Books

  • All books
  • Front-end
  • Swift
  • iOS
  • Android

Team Training

  • Team Training Overview
  • iOS
  • Android
  • Web
  • Design

Bootcamps

  • Bootcamp Overview
  • iOS
  • Android
  • Web
  • Schedule
  • Georgia Ranch
  • California Ranch
  • Sign In

Company

  • Who We Are
  • News
  • Resources
  • Careers
  • Blog
  • Contact

Copyright© 1998 - 2018 Big Nerd Ranch, LLC. All Rights Reserved. | Privacy Policy