swift-rs
A Rust crate that lets you call Swift functions from Rust safely, bridging strings, arrays, and NSObject-backed types across the FFI boundary.
Repository Health
Technical Analysis
swift-rs solves a narrow but painful problem: calling native Swift code from a Rust codebase, most commonly to reach macOS/iOS system APIs that only have Swift or Objective-C bindings. It compiles a Swift Package via SwiftLinker in your crate’s build.rs, links the resulting static library and the Swift runtime, and exposes a swift! macro that wraps extern "C" declarations so argument and return-value retain counts stay balanced automatically.
Because plain Rust structs cannot cross the boundary as Objective-C types, the crate provides wrapper types — SRObject<T> for NSObject-backed values, SRString and SRData for text and binary payloads, and SRArray<T>/SRObjectArray<T> for collections — each mirroring the memory layout Swift expects while managing retain/release semantics on drop. Optional Option<T> support covers Swift’s nil for object returns.
It is best known as the bridge Tauri uses on macOS to call Swift APIs, and the same pattern applies to any Rust project that needs a thin, safe-ish channel into Swift-only frameworks without hand-rolling Objective-C runtime calls.
What You Get
swift!macro that wrapsextern "C"function declarations and automatically manages Objective-C retain counts on arguments and return valuesSwiftLinkerbuild-time helper that compiles a Swift Package viaswift build, resolves SDK/target triples per architecture, and emits the correctcargo:rustc-linkdirectivesSRObject<T>wrapper for NSObject-backed return types, withDeref/AsRefaccess to the underlying#[repr(C)]struct and automatic release on dropSRStringandSRDatatypes for passing UTF-8 strings and byte buffers between Rust and Swift without manual encodingSRArray<T>andSRObjectArray<T>for returning primitive and NSObject collections from Swift, accessible as Rust slices- Support for iOS, visionOS, and simulator targets alongside macOS, including cross-compilation handling for recent Xcode toolchains
Common Use Cases
- Calling macOS/iOS system frameworks (e.g. Security, AppKit-adjacent APIs) that are only exposed through Swift, without writing raw Objective-C runtime bindings
- Building Tauri plugins or native modules that need Swift-only functionality on Apple platforms
- Returning structured data (strings, arrays, custom objects) from a Swift helper library back into a Rust application with correct memory management
- Wrapping an existing internal Swift library so the rest of a Rust codebase can call into it as ordinary unsafe extern functions
Under The Hood
Architecture
The crate is organized as a small set of focused modules under src-rs/: swift.rs defines the core swift! macro and the SwiftObject/SwiftRef traits that model a reference to an NSObject-backed value; swift_arg.rs and swift_ret.rs implement the SwiftArg/SwiftRet traits per type so the macro can convert Rust values into their C-compatible argument form and reattach retains on return; and types/ holds the concrete wrapper types (SRObject, SRString, SRData, SRArray, scalars). A separate build.rs, gated behind the build feature, contains SwiftLinker, which shells out to swift -print-target-info and xcrun to resolve SDK paths, invokes swift build for the target triple, and emits cargo:rustc-link-* directives — including special-cased handling (globalize_cdecl_symbols via llvm-objcopy) for Xcode 27’s SwiftPM internalizing @_cdecl exports. What breaks if the core SwiftObject/SwiftRef abstraction changes: every generated argument/return wrapper (SRObject, SRArray, SRString, SRData) depends on its NonNull-based layout and retain/release contract.
Tech Stack
Pure Rust 2021-edition crate with base64 as a runtime dependency and optional serde/serde_json behind the build and serde features for JSON handling in the linker and for serializing wrapped objects. Build-time tooling shells out directly to the swift, xcrun, and (conditionally) nm/llvm-objcopy/codesign binaries rather than depending on a Swift toolchain crate — there is no abstraction layer over Apple’s command-line tools, so the linker logic is tightly coupled to Xcode’s SwiftPM output layout across versions.
Code Quality
Tests live in tests/test_bindings.rs and exercise the FFI boundary against a companion Swift package (tests/swift-pkg/), using serial_test to serialize test execution and an optional leaks-based memory-pressure mode (TEST_RUNNING_UNDER_LEAKS) that codesigns the test binary and runs it under macOS’s leaks tool to catch retain/release imbalances — a notably rigorous approach for FFI memory safety given the domain. Error handling in build.rs favors .unwrap()/panic! on toolchain failures (acceptable for a build script, since failures should hard-stop the build). No linter/CI config beyond a single GitHub Actions workflow was found beyond the standard Cargo toolchain.
What Makes It Unique
Most Rust-Swift interop guidance relies on hand-written Objective-C runtime calls or C shims; swift-rs instead standardizes the retain-count bookkeeping behind a macro and a small set of typed wrappers, and — notably — automates linking against Swift Package Manager targets directly from build.rs, including cross-architecture and simulator target-triple resolution and workarounds for breaking changes in newer Xcode/SwiftPM versions. That build-time automation, combined with the memory-safety test harness, is the crate’s clearest differentiator from ad hoc FFI approaches.