swift-bridge

repository·master·Indexed 22 days ago

https://github.com/chinedufn/swift-bridge

A library for generating FFI bindings to enable safe, high-level interop between Rust and Swift. It supports sharing complex types like structs, transparent enums, and generic types, as well as bidirectional async/await function calls. The tool provides a bridge module system using the #[swift_bridge::bridge] macro to export Rust functions to Swift and import Swift functions into Rust, with support for conditional compilation via #[cfg] attributes.

Tokens
22.5K
Snippets
73
Records
85
Agent score
77%

What's inside swift-bridge

  1. Use Rust Vecs in Swift as RustVec

    master

    When you export a Rust std::vec::Vec through a swift_bridge module, it is represented on the Swift side as a RustVec.

    RustVec implements Swift's IteratorProtocol, which allows you to iterate over its elements using standard Swift for-in loops. It also provides common vector operations such as push(), pop(), get(), and len().

    let vec: RustVec = get_rust_vec_somehow()
    for value in vec {
        print(value)
    }
  2. Declare an FFI interface using a bridge module

    master

    In swift-bridge, you define your Foreign Function Interface (FFI) by creating a "bridge module" decorated with the #[swift_bridge::bridge] procedural macro. This module acts as the single source of truth for your interface.

    • extern "Rust" blocks: Used to export Rust types and functions so they can be called from Swift.
    • extern "Swift" blocks: Used to import Swift types and functions so they can be called from Rust.

    You can include multiple extern "Rust" and extern "Swift" blocks within a single bridge module to declare all necessary types and functions.

    #[swift_bridge::bridge]
    mod ffi {
        // Export Rust types and functions for Swift to use.
        extern "Rust" {
            type SomeRustType;
            fn some_type_method(&mut self) -> String;
        }
    
        // Import Swift types and functions for Swift to use.
        extern "Swift" {
            type SomeSwiftClass;
    
            #[swift_bridge(swift_name = "someClassMethod")]
            fn some_class_method(&self, arg: u8);
        }
    }
  3. Interoperate Rust &str and Swift RustStr

    master

    You can pass Rust's &str types between Rust and Swift using the RustStr type in Swift.

    • From Rust to Swift: A function returning &'static str or &str in a #[swift_bridge::bridge] module will be received as a RustStr in Swift.
    • From Swift to Rust: When a Swift function is marked as extern "Swift" and returns a &str, Swift provides the string and Rust receives it as a standard &str.
    #[swift_bridge::bridge]
    mod ffi {
        extern "Rust" {
            type SomeRustType;
    
            // Becomes a `RustStr` when passed to Swift.
            fn make_str() -> &'static str;
    
            fn get_str(self: &SomeRustType) -> &str;
        }
    
        extern "Swift" {
            type SomeSwiftType;
    
            // Swift returns a `RustStr` and
            // Rust receives a `&str`.
            fn make_string() -> &str;
        }
    }
  4. Understand the Swift and Rust build process

    master

    Because there is no single compiler that handles both Swift and Rust, you must use a two-step approach to create a final binary. You cannot compile both languages simultaneously in one pass; instead, you must compile one language into a native library first, and then use the second language's compiler to compile the final executable while linking against that native library.

    There are two primary directions for this workflow:

    1. Rust as a library: Compile Rust code into a native library (e.g., libmy_rust_crate.a), then compile the Swift code into a final executable while linking the Rust library.
    2. Swift as a library: Compile Swift code into a native library, then compile the Rust code into a final executable while linking the Swift library.

    The choice of direction typically depends on your existing build tools and project requirements.

    ┌──────────────────────────────────┐           ┌───────────────────┐       
    │// Rust code                      │           │// Swift Code      │       
    │                                  │           │                   │       
    │pub extern "C" fn rust_hello() {  │           │rust_hello()       │       
    │    println!("Hi, I'm Rust!")     │           │                   │       
    │}                                 │           │                   │       
    └──────────────────────────────────┘           └───────────────────┘       
                     │                                       │                 
        Compile Rust │                                       │ Compile Swift to
       to native lib │                                       │ executable      
                     │                                       │                 
                     ▼                      Link in Rust     │                 
    ┌────────────────────────────────┐      native lib       │                 
    │       libmy_rust_crate.a       │───────────────────────┤                 
    └────────────────────────────────┘                       │                 
                                                             │                 
                                                             ▼                 
                                           ┌──────────────────────────────────┐
                                           │     Final Executable Binary      │
                                           │                                  │
                                           └──────────────────────────────────┘
  5. Define Transparent Structs shared between Rust and Swift

    master

    You can define structs using the #[swift_bridge(swift_repr = "struct")] attribute within a #[swift_bridge::bridge] module. This allows the struct's fields to be accessible by both Rust and Swift.

    Note that Swift structs are copy-on-write; therefore, swift_bridge does not allow mutating the fields of a struct defined with swift_repr = "struct" because mutations would not affect the original instance.

    // Rust
    #[swift_bridge::bridge]
    mod ffi {
        #[swift_bridge(swift_repr = "struct")]
        struct SomeSharedStruct {
            some_field: u8,
            another_field: Option<u64>
        }
    
        extern "Rust" {
            fn some_function(val: SomeSharedStruct);
        }
    
        extern "Swift" {
            fn another_function() -> SomeSharedStruct;
        }
    }
  6. Use Tuples across the Rust-Swift bridge

    master

    The swift-bridge allows for direct mapping of Rust tuples (A, B, C, ...) to Swift tuples (A, B, C, ...). This enables passing multiple values as a single argument or return value across the FFI boundary without needing to define explicit structs for simple groupings.

    // Rust
    mod ffi {
        extern "Rust" {
            fn get_midpoint(
                point1: (f32, f32, f32),
                point2: (f32, f32, f32),
            ) -> (f32, f32, f32);
        }
    }
  7. Memory safety rules for Swift and Rust FFI

    master

    While swift-bridge provides type safety for all generated FFI code, developers are responsible for maintaining memory safety when interacting between Swift and Rust. Because Swift does not enforce Rust's borrowing and ownership rules at compile time, you must manually follow these three rules to avoid undefined behavior:

    1. Never use a reference after its lifetime: Do not use a reference (like a RustStr) in Swift after the parent Rust type has been dropped.
    2. Avoid aliasing mutable references: When passing a mutable reference to Rust, ensure no other active references to that same value exist in Swift. Do not pass a value to a method that requires &mut self if you are simultaneously holding an immutable reference to that same value.
    3. Never use a value after it is dropped: Once you pass ownership of a Swift-held value to Rust (e.g., via a drop function), do not attempt to access or drop that value again in Swift.
  8. Use swift-bridge-ir directly to generate FFI boundaries

    master

    While swift-bridge provides high-level macros, library authors can use the swift-bridge-ir crate directly to build custom code generators. This is useful for creating alternative frontends, such as custom procedural macros that wrap swift-bridge-ir logic to annotate types for Swift exposure.

    For example, you could implement a third-party library that provides an attribute macro like #[some_third_party_lib::ExposeToSwift] which internally utilizes swift-bridge-ir to generate the necessary Rust+Swift FFI boundary.

    use some_third_party_lib;
    
    /// An imaginary third-party library that wraps `swift-bridge-ir`
    /// in a proc macro attribute that users can annotate their types
    /// with.
    #[some_third_party_lib::ExposeToSwift]
    pub struct User {
        name: String
    }