on
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.
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.
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
.
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
- Accepts two integers as input
- Returns an integer that is the sum of the two integer inputs as output.
Modify src/lib.rs
to be the following
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
.
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
- Allow non snake case function names and suppress warnings about it.
- 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.
- 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 asunsafe
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.