swiftc + Cargo

One approach to building Rust and Swift is to use Cargo to compile your Rust code and swiftc to compile your Swift code.

In this case, either Cargo needs to link to a compiled Swift native library, or swiftc needs to link to a compiled Rust native library.

We'll use an example project to explore both of these approaches.

Project Setup

cargo new --lib swift-and-rust
cd swift-and-rust
# Cargo.toml

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

[dependencies]
swift-bridge = "0.1"
// src/main.rs
fn main() {
    swift_and_rust::print_hello_swift();
}

#![allow(unused)]
fn main() {
// src/lib.rs

pub use ffi::print_hello_swift;

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

    extern "Swift" {
        fn print_hello_swift();
    }
}

fn print_hello_rust() {
    println!("Hello from Rust")
}
}
// main.swift
run()
// lib.swift
func print_hello_swift() {
    print("Hello from Swift")
}

func run() {
    print_hello_rust()
}
# briding-header.h

#ifndef BridgingHeader_h
#define BridgingHeader_h

#include "./generated/SwiftBridgeCore.h"
#include "./generated/swift-and-rust/swift-and-rust.h"

#endif /* BridgingHeader_h */
mkdir generated

Add the following to your Cargo.toml

# Cargo.toml

[lib]
crate-type = ["staticlib"]
// 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-swiftc-links-rust.sh

#!/bin/bash
set -e

cargo build --target x86_64-apple-darwin
swiftc -L target/x86_64-apple-darwin/debug/ -lswift_and_rust -import-objc-header bridging-header.h \
  main.swift lib.swift ./generated/swift-and-rust/swift-and-rust.swift ./generated/SwiftBridgeCore.swift
chmod +x build-swiftc-links-rust.sh
./build-swiftc-links-rust.sh
./main
# The output should be "Hello from Rust"

Unlike the when we had swiftc in the Rust code, you do not need to set the crate-type when you have Cargo linking in the Swift code.

// 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"));

    println!("cargo:rustc-link-lib=static=swiftc_link_rust");
    println!("cargo:rustc-link-search=./");
}
# build-swift-static-lib.sh

#!/bin/bash
set -e

# The swift-bridge CLI does not exist yet. Open an issue if you need to use
# this approach and I'll happily whip up the CLI.
swift-bridge -f src/lib.rs > generated

swiftc -emit-library -static -module-name my_swift -import-objc-header bridging-header.h \
  lib.swift ./generated/swift-and-rust/swift-and-rust.swift
chmod +x build-swift-static-lib.sh
./build-swift-static-lib.sh

cargo build
./target/debug/swift_and_rust
# The output should be "Hello from Swift"