Bridging Rust to Swift, pt. 1: Hello, C

C Anyone?

Swift has great interoperability with C. Given a C header and linked library, it can call into C code quite easily. If we were to build a C wrapper to our Rust code, we should be good to go - with using the C library’s functionality. As it turns out, doing so is relatively painless. Let’s start by creating a sample rust library project.

cargo new --lib swift_bridged

This should create a new rust project with a Cargo.toml and src under folder swift_bridged. Cargo.toml is currently quite basic with the following contents.

[package]
name = "swift_bridged"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

Targets

By default, building our project with Cargo will produce libraries that are compatible with rustc. This can be examined by running cargo build --release in the root of our project. We will find libswift_bridged.d and libswift_bridged.rlib inside release/target. Since, we are looking to access the library from C, we will need to tell rustc to build a C compatible target. Amongst the different rust targets available, we need to tell Cargo to build a cdylib as well.

Let’s add a new lib section to our Cargo.toml with the following content. This gets Cargo to build a rustc compatible staticlib along with a C compatible dynamically shared cdylib.

[lib]
crate_type = ["lib", "staticlib", "cdylib"]

Re-running cargo build --release, shows two additional targets libswift_bridged.a and libswift_bridged.dylib. While we can directly access these libraries from C, they are currently empty. Let’s add some functionality to the library in rust. For illustrative purposes, it’s going to be a simple function that

  1. Accepts two integers as input
  2. Returns an integer that is the sum of the two integer inputs as output.

Modify src/lib.rs to be the following

pub fn add_integers(a: u32, b: u32) -> u32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result =  add_integers(3, 4);
        assert_eq!(result, 7);
    }
}

Simple enough. Let’s examine the content of our generated libraries. On macOS, we can use the nm tool in conjunction with rustfilt. Generally, the function names are mangled in the generated rust libraries. rustfilt unmangles them for easier readability, and can be easily installed via cargo install rustfilt.

$ cargo install rustfilt
$ nm -gU target/release/libswift_bridged.rlib | rustfilt | grep -i integers
target/release/libswift_bridged.rlib:lib.rmeta: no symbols
0000000000000000 T _swift_bridged::add_integers

$ nm -gU target/release/libswift_bridged.dylib | rustfilt | grep -i integers 
target/release/libswift_bridged.dylib: no symbols

Interestingly, while our native rust rlib contains the add_integers function, it is missing in our cdylib. For a function to be made available in the cdylib, it needs to be marked for export in our rust code. rustc strips out all functions that are not exported or used by an exported function while generating the cdylib.

#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn bodevAddIntegers(a: u32, b: u32) -> u32 {
    add_integers(a, b)
}

There is a fair amount going on with the above snippet. Instead of directly exporting fn add_integers() we are going to write a C compatible fn bodevAddIntegers() that will wrap it. Additionally, we are instructing rustc to

  1. Allow non snake case function names and suppress warnings about it.
  2. Not mangle the function name in the exported library. If the function name were to be mangled, we wouldn’t know how to call it from our downstream programs.
  3. Since, the function is going to be accessed from outside rust, we have no real control over what it does or how it is called. The calling program could theoretically pass in invalid values. We mark the function as unsafe to denote this - the rust compiler is unable to check the usage of this function.

Let’s build and examine our libraries again.

# nm -gU target/release/libswift_bridged.dylib | rustfilt | grep -i integers
0000000000003fb0 T _bodevAddIntegers

We now find our C wrapper function in the dynamic library. Coupled with a header file, we could now call into this library from C/Swift. And now, let’s move onto automating the generation of this header file.