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 $_
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()
}
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!");
}
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
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"));
}
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
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!
For completeness, here's the full folder structure:
(NOTE gist
should be called swift-rs
if you followed this guide)
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