Integrating Rust Library Into Swift Code

published on 13 March 2023
Picture1-ud6r8

So, there’s a Rust library you put your eye on. It’s useful, well-written and has a bunch of GitHub stars, but you are writing a Swift code.

I had just this case with my recent Sudoku Application (Click to view).

The generation logic was written by my brother in Rust, and I needed a way to incorporate it into Xcode.

I tried various ways, most didn’t work very well - also because I’m using an Apple Silicon Mac which needs some compatibility adjustments. But at the end, I found an (sort of) easy way that works great.

P.S before we start: This is the way that worked the best for me - I’m sure there are other ways to make this integration 🙂

Let’s Get Right To It

Initialize CocoaPods In Your Xcode Project

This is pretty straight forward:

  1. Install CocoaPods: brew install cocoapods
  2. Open a terminal in your .xcodeproj folder and run pod init and then pod install

This will create .xcodeworkspace file in your project folder.

.xcodeworkspace
.xcodeworkspace

Download The Required Rust Library

Download the library to your computer, and put it in the same folder as your .xcodeworkspace beside your app folder, so the hierarchy is:

Hierarchy #1
Hierarchy #1

Create Another Folder For Your Own Rust Code

In this stage, we will initialize our own rust code, that will define C functions to be used later by the Swift bridge.

The steps:

  1. Create a new folder beside your .xcodeworkspace, I named it Native - so the hierarchy is:
Hierarchy #2
Hierarchy #2

2. Initialize a Cargo package in the new directory with cargo init

3. Edit the Cargo.toml file with these two important additions: 

- create-type should include “lib” and “staticlib”

- Add a dependency for our Rust library (now that I think of it, SudokuRust folder could go inside the Native folder)

[package]
name = "Native"
version = "0.1"
edition = "2021"

[lib]
crate-type = ["lib", "staticlib"]

[dependencies]
sudoku = {path = "../SudokuRust"}

Edit src/lib.rs

Ok, this is important part #1:

- Define functions that use the sudoku library and return the result

- Define types that will be used by Swift later on

  1. Import the dependency
use sudoku::prelude::*;

2. Define the bridging function, in my case - generate a Sudoku board and return a Result (to be defined)

no_mangle - turns off Rust’s name mangling, so that it is easier to link to.

difficulty - a param of type usize (UInt in Swift)

We simply generate two boards, one full board and one with trimmed numbers for the player to solve.

After generating those, we return a pointer to each array with sudoku_to_ptr

#[no_mangle]
pub extern "C" fn generate_sudoku(difficulty: usize) -> Result {
    let config = Config {
        difficulty: Difficulty::try_from(difficulty).unwrap(),
    };

    let filled = Sudoku::new_filled(config);
    let unique = SudokuAlgorithm.generate_from(filled.clone(), config);

    Result {
        filled: sudoku_to_ptr(filled),
        unique: sudoku_to_ptr(unique),
    }
}

3. The way you point to the array

- The function gets a Sudoku as a parameter

- The function returns a *mut usize

- The rest is some rust magic, but the main thing is using into_boxed_slice::into_raw in order to return a valid pointer

fn sudoku_to_ptr(sudoku: Sudoku) -> *mut usize {
    let vec = sudoku
        .into_inner()
        .map(|cell| cell.digit.map_or(0, |digit| *digit as usize))
        .to_vec();

    let slice = vec.into_boxed_slice();
    Box::into_raw(slice) as _
}

4. We also need a way to dispose of the pointers after we use them in Swift

#[no_mangle]
pub extern "C" fn dispose(result: Result) {
    drop(result)
}

5. And lastly, we need to define the Result type, with a destructor

#[repr(C)]
pub struct Result {
    filled: *mut usize,
    unique: *mut usize,
}

impl Drop for Result {
    fn drop(&mut self) {
        unsafe {
            drop(Box::from_raw(self.filled));
            drop(Box::from_raw(self.unique));
        }
    }
}

Phew, that was intense - but we’re almost there!

Now we have a code that defines a simple function and the relevant types for our actual Swift code bridge to use.

Using Our Rust Code

First, we will transform our rust code into an additional project to be used by our workspace.

The tool i chose to use is [cargo-xcode](https://crates.io/crates/cargo-xcode), it’s simple and gets the job done.

  1. run cargo install cargo-xcode
  2. Inside our Native folder, run cargo xcode, this will generate <rust-project-name>.xcodeproj - but don’t open it yet!
CleanShot 2023-03-13 at 19.14.14-olq4u

3. Open your workspace in Xcode and add the <rust-project-name>.xcodeproj to the workspace (drag the file into the parent project's sidebar).

CleanShot 2023-03-13 at 19.16.36-2uj1k

Bridging Into Swift

And this is important part #2

The last part before we can call our generate_sudoku function, is to write the Swift bridge.

Create a new folder, I called it Bridge, in your project’s folder like so - with 3 files:

Hierarchy #3
Hierarchy #3

1. RustSudoku-Bridging-Header.h (Yes, a one-liner)

#include "bridge.h"

2. bridge.c (Yes, a one-liner as well)

#include "bridge.h"

3. bridge.h - here we define our relevant functions and types: Result (names RustSudokuResult for Swift uniqueness), generate_sudoku and dispose

#ifndef bridge_h
#define bridge_h

#include <stdint.h>

typedef struct RustSudokuResult {
    uintptr_t* filled;
    uintptr_t* unique;
} RustSudokuResult;

RustSudokuResult generate_sudoku(uintptr_t);
void dispose(RustSudokuResult);

#endif
The Bridge Folder
The Bridge Folder

Finally - Calling Our Bridge Functions

That’s it! now we can call generate_sudoku and dispose from anywhere in our Swift code

CleanShot 2023-03-13 at 19.20.49-kmuan

A final important note about how to parse our UInt pointer to actual Int array:

// size = 9 since a Sudoku board contains 81 cells
private func parseArray(_ pointer: UnsafeMutablePointer<UInt>?) -> [Int]{
    return Array(UnsafeMutableBufferPointer(start: pointer, count: size * size))
}

Thanks for reading!

You're more then welcome to reach out to me via projects@royshavit.com 🙂

Built on Unicorn Platform