Skip to content

Instantly share code, notes, and snippets.

@Jomy10
Last active May 2, 2024 05:59
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jomy10/a4873dd43942ed1bf54d387dbc888795 to your computer and use it in GitHub Desktop.
Save Jomy10/a4873dd43942ed1bf54d387dbc888795 to your computer and use it in GitHub Desktop.
Calling Rust library from Swift code (creating executable)

Calling a Rust library from Swift

This gist shows a quick overview of calling Rust in a Swift project, with Swift Package Manager configured.

Let's go ahead and create a folder containing all our sources:

mkdir swift-rs && cd $_

Setting up Swift project

First, let's create our Swift project.

mkdir swift && cd $_
swift package init --type executable

This will create a new executable Swift project.

Next, we'll call our Rust code which we'll create later.

cd Sources/swift

Edit main.swift:

run()

And add a file lib.swift:

func run() {
    print_hello_rust()
}

Setting up Rust project

Let's go back to our parent directory (cd ../../..).

Now, we'll set up our Rust project:

cargo new rust --lib && cd rust

We should now have 2 folders in our root. One contains a Rust project, the other a Swift project.

To call our Rust code from Swift, we will use Swift Bridge. Go ahead and add it to your cargo.toml. Also add the crate-type to it:

# Cargo.toml
[lib]
crate-type = ["staticlib"]

[build-dependencies]
swift-bridge-build = "0.1"

[dependencies]
swift-bridge = "0.1"

Next, we'll create our library:

// src/lib.rs

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn print_hello_rust();
    } 
}

fn print_hello_rust() {
    println!("Hello from Rust!");
}

Adding the header file

Go back to the parent directory cd ...

Create a new file called bridging-header.h:

#ifndef BridgingHeader_h
#define BridgingHeader_h

#include "./generated/SwiftBridgeCore.h"
#include "./generated/rust/rust.h"

#endif /* BridgingHeader_h */

Then, create a new directory called generated.

mkdir generated

Building

Rust build file

There's one more thing we need to do on the rust side, so go ahead and move back into the rust project cd rust and add a new file called build.rs.

use std::path::PathBuf;

fn main() {
    let out_dir = PathBuf::from("../generated");
    let bridges = vec!["src/lib.rs"];
    for path in &bridges {
        println!("cargo:rerun-if-changed={}", path);
    }
    
    swift_bridge_build::parse_bridges(bridges)
        .write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"));
}

Build file

Let's go back to the root folder of our project cd ...

Now, let's add our build script that will compile both our Rust and Swift projects.

touch build.sh
# build.sh

#!/bin/bash
set -e

export SWIFT_BRIDGE_OUT_DIR="$(pwd)/generated"

cargo build --manifest-path rust/Cargo.toml --target x86_64-apple-darwin

swiftc -L rust/target/x86_64-apple-darwin/debug -lrust -import-objc-header bridging-header.h \
    `find swift/Sources/swift -name *.swift` \
    ./generated/rust/rust.swift
 

Build the project

Make this file executable:

chmod +x build.sh

And now run it:

./build.sh

If everything went well, you will now have an executable file called main in the parent directory, let's run it:

./main

Output:

Hello from Rust!

Folder structure

For completeness, here's the full folder structure:

Folder structure

(NOTE gist should be called swift-rs if you followed this guide)

Notes

We have succesfully build an executable using Swift that calls Rust code. This executable can only be called on MacOS, as this is the only target we have added. Adding more targets should be as easy as ading another cargo build and swiftc line to the build process.

If you want to learn more about Swift-bridge, go read the book: Swift-bridge book

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment