Giter Site home page Giter Site logo

chinedufn / swift-bridge Goto Github PK

View Code? Open in Web Editor NEW
725.0 12.0 51.0 6.67 MB

swift-bridge facilitates Rust and Swift interop.

Home Page: https://chinedufn.github.io/swift-bridge

License: Apache License 2.0

C 0.11% Swift 7.00% Rust 92.62% Shell 0.27%
rust swift ffi interop ios

swift-bridge's Introduction

swift-bridge Actions Status docs crates.io

swift-bridge facilitates Rust and Swift interop.

swift-bridge makes it easy to pass and share high-level types between Rust and Swift, such as String, Option<T>, Result<T, E>, struct, class and more.

It also helps you bridge higher level language features, such as async functions and generics.

Using swift-bridge should be safer, more performant and more ergonomic than managing Rust and Swift FFI by hand.

Installation

# In your Cargo.toml

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

[dependencies]
swift-bridge = "0.1"

Book

You can find information about using Rust and Swift together in The swift-bridge Book.

Quick Peek

You use swift-bridge by declaring the types and functions that you want to import and export in a "bridge module", and then annotating that bridge module with the #[swift_bridge::bridge] macro.

Then, at build time, you use either the swift-bridge-build API or the swift-bridge-cli CLI to parse your annotated bridge modules and generate the Swift and C side of the FFI layer.

Here's a quick peek at how you might describe an FFI boundary between Swift and Rust using a bridge module.

// We use the `swift_bridge::bridge` macro to declare a bridge module.
// Then at build time the `swift-bridge-build` crate is used to generate
// the corresponding Swift and C FFI glue code.
#[swift_bridge::bridge]
mod ffi {
    // Create "transparent" structs where both Rust and Swift can directly access the fields.
    struct AppConfig {
        file_manager: CustomFileManager,
    }

    // Transparent enums are also supported.
    enum UserLookup {
        ById(UserId),
        ByName(String),
    }

    // Export opaque Rust types, functions and methods for Swift to use.
    extern "Rust" {
        type RustApp;

        #[swift_bridge(init)]
        fn new(config: AppConfig) -> RustApp;
        
        fn get_user(&self, lookup: UserLookup) -> Option<&User>;
    }

    extern "Rust" {
        type User;
        type MessageBoard;

        #[swift_bridge(get(&nickname))]
        fn informal_name(self: &User) -> &str;
    }

    // Import opaque Swift classes and functions for Rust to use.
    extern "Swift" {
        type CustomFileManager;
        fn save_file(&self, name: &str, contents: &[u8]);
    }
}

struct User {
    nickname: String
}

Quick Start

The swift-bridge repository contains example applications that you use to quickly try out the library, or as a starting point for your own Swift + Rust based application.

For example, here's how to run the codegen-visualizer example project locally.

git clone https://github.com/chinedufn/swift-bridge
cd swift-bridge/examples/codegen-visualizer

open CodegenVisualizer/CodegenVisualizer.xcodeproj
# *** Click the "Run" button at the top left of Xcode ***

You can find information about using Rust and Swift together in The swift-bridge Book.

Built-In Types

In addition to allowing you to share your own custom structs, enums and classes between Rust and Swift, swift-bridge comes with support for a number of Rust and Swift standard library types.

name in Rust name in Swift notes
u8, i8, u16, i16... etc UInt8, Int8, UInt16, Int16 ... etc
bool Bool
String, &String, &mut String RustString, RustStringRef, RustStringRefMut
&str RustStr
Vec<T> RustVec<T>
SwiftArray<T> Array<T> Not yet implemented
&[T] Not yet implemented
&mut [T] Not yet implemented
Box<T> Not yet implemented
Box<dyn FnOnce(A,B,C) -> D> (A, B, C) -> D Passing from Rust to Swift is supported, but Swift to Rust is not yet implemented.
Box<dyn Fn(A,B,C) -> D> (A, B, C) -> D Not yet implemented
Arc<T> Not yet implemented
[T; N] Not yet implemented
*const T UnsafePointer<T>
*mut T UnsafeMutablePointer<T>
Option<T> Optional<T>
fn x() -> Result<T, E> func x() throws -> T
fn x(arg: Result<T, E>) func x(arg: RustResult<T, E>)
(A, B, C, ...) (A, B, C, ...)
Have a Rust standard library type in mind?
Open an issue!
Have a Swift standard library type in mind?
Open an issue!

Performance

swift-bridge aims to be useful in performance critical environments.

None of its generated FFI code uses object serialization, cloning, synchronization or any other form of unnecessary overhead.

To Test

To run the test suite.

# Clone the repository
git clone [email protected]:chinedufn/swift-bridge.git
cd swift-bridge

# Run tests
cargo test --all && ./test-swift-rust-integration.sh && ./test-swift-packages.sh 

Contributing

If you're interesting in contributing to swift-bridge, check out the contributor's guide.

After getting familiar with the contribution process, try looking at some of the good first issues to see if any peak your interest.

These issues come with step-by-step instructions that should help guide you towards implementing your first patch.

Acknowledgements

  • cxx inspired the idea of using a bridge module to describe the FFI boundary.

License

Licensed under MIT or Apache-2.0.

swift-bridge's People

Contributors

aiongg avatar akesson avatar antoniusnaumann avatar bes avatar chinedufn avatar conectado avatar gabbifish avatar itamarmu avatar jfaust avatar jomy10 avatar mikuroxina avatar naturecodevoid avatar niwakadev avatar onato avatar pouriaafshari avatar prismaphonic avatar rkreutz avatar samnm avatar shritesh avatar simlay avatar timwedde avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

swift-bridge's Issues

Natively support generic extern "Rust" opaque types

Something like:

struct MyStruct<A, B> {
    field1: A,
    field2: B,
}

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type MyStruct<u32, String>;
        
        #[swift_bridge(associated_to = MyStruct<u32, String>)]
        fn new() -> MyStruct<u32, String>;
    }
}
// Swift

let myStruct = MyStruct_u32_String.new();

Note that generics are possible today using type aliases:

struct MyStruct<A, B> {
    field1: A,
    field2: B,
}

type MyStruct_u32_String = MyStruct<u32, String>;

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type MyStruct_u32_String;
    }
}

Fix bug when defining `-> OpaqueRustType` even though the real function is `-> &OpaqueRustType`

Right now this compiles and it shouldn't

struct SomeType;

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        // The real implementation below returns &SomeType
        fn foo() -> SomeType;
    }
}

fn foo () -> &SomeType {
    unimplemented!()
}

This also compiles, even though it shouldn't.

struct SomeType;

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        // The real implementation below returns Option<SomeType>
        fn foo() -> SomeType;
    }
}

fn foo () -> Option<SomeType> {
    unimplemented!()
}

Implementation

UI Test

We can add these signatures to an incorrect_return_type.rs UI test, similar to the

//! # To Run
//! cargo test -p swift-bridge-macro -- ui trybuild=incorrect-argument-type.rs
// We declare functions that have an argument with a type that does not match that
// of the real implementation below.
//
// Non FFI safe types such as `&str` are passed over the FFI boundary using different types
// such as `swift_bridge::string::RustStr`, so our tests confirm that our codegen preserves
// the span of the original argument type.
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(rust_name = "some_function")]
fn fn1(arg: &str);
// TODO: Add more types.
// #[swift(bridge(rust_name = "some_function"))]
// fn fn2(arg: String);
// #[swift(bridge(rust_name = "some_function"))]
// fn fn3(arg: Option<u8>);
}
}
fn some_function(_arg: u16) {}
ui tests and then fix the code generation so that we get compile time errors for these mistakes.

Codegen Tests

Change the return opaque Rust type codegen test to use an explicit type annotation

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function () -> *mut super::SomeType {
Box::into_raw(Box::new(super::some_function())) as *mut super::SomeType
}
})
}

Something like:

Box::into_raw(Box::new({let val: super::SomeType = super::some_function(); val})) as *mut super::SomeType

Same idea for the reference and reference mut codegen tests

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function () -> *const super::SomeType {
super::some_function() as *const super::SomeType
}
})
}

fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$some_function"]
pub extern "C" fn __swift_bridge__some_function () -> *mut super::SomeType {
super::some_function() as *mut super::SomeType
}
})
}

Get tests passing

You can get tests passing by modifying the BridgeableType implementation in this file

Box::into_raw(Box::new(#expression)) as *mut super::#ty_name #generics

Improve the documentation for `swift-bridge-build::parse_bridges`

Right now the book doesn't really introduce or call out swift_bridge_build::parse_bridges, which can lead to issues like #54 where someone might not notice that you need to explicitly specify the files that you want to parse.

We should make sure that the book properly introduces swift-bridge-build::parse_bridges as well as the equivalent CLI command (which we haven't written yet).


One place to improve is the Building chapter which mentions compiling Rust and Swift but does not mention bridging headers.

  • We can also improve each of the different building approach chapters to call out what's going on in the build.rs file.

  • We should also show an example of a non hard-coded list of bridges. i.e. doing something like std::fs::read_dir("src/bridges") and turning that into an iterator that we pass to swift_bridge_build::parse_bridges.

  • We should also add some examples to the swift_bridge_build::parse_bridges doc comment showing both hard coded file paths as well as getting all files from a directory that match a certain pattern (i.e. files that end with _bridge.rs)

Type `CbWrapper` cannot be nested in generic function, when using strings in async params

I just tested the async methods and functions, while they compile on the Rust side, they don't seem to compile on the Swift/Xcode side. This only seems to happen when there's a parameter to the method/function, and to be more specific, numbers, booleans, vectors and Option (of numbers or booleans) all work.

Seems like any async functions/methods that uses String or &str has the same error. I tested it out with normal non-async functions/methods, and it doesn't have this error.

(worst case scenario I could encode the string using an array/vector of numbers)

async method

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type RustApp;

        async fn connect(&mut self, username: &str);
    }
}

impl RustApp {
    async fn connect(&mut self, username: &str) {}
}
pub struct RustApp {}

Produces the following:

extension RustAppRefMut {
    public func connect<GenericToRustStr: ToRustStr>(_ username: GenericToRustStr) async {
        class CbWrapper {
            var cb: (Result<(), Never>) -> ()

            public init(cb: @escaping (Result<(), Never>) -> ()) {
                self.cb = cb
            }
        }

        func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?) {
            let wrapper = Unmanaged<CbWrapper>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
            wrapper.cb(.success(()))
        }

        return await withCheckedContinuation({ (continuation: CheckedContinuation<(), Never>) in
            let callback = { rustFnRetVal in
                continuation.resume(with: rustFnRetVal)
            }

            let wrapper = CbWrapper(cb: callback)
            let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()

            username.toRustStr({ usernameAsRustStr in
                    __swift_bridge__$RustApp$connect(wrapperPtr, onComplete, ptr, usernameAsRustStr)
                })
        })
    }
}

With the error at line 3

Type 'CbWrapper' cannot be nested in generic function 'connect'

async function

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type RustApp;

        async fn connect(username: &str);
    }
}

async fn connect(username: &str) {}

pub struct RustApp {}

Produces the following:

public func connect<GenericToRustStr: ToRustStr>(_ username: GenericToRustStr) async {
    class CbWrapper {
        var cb: (Result<(), Never>) -> ()

        public init(cb: @escaping (Result<(), Never>) -> ()) {
            self.cb = cb
        }
    }

    func onComplete(cbWrapperPtr: UnsafeMutableRawPointer?) {
        let wrapper = Unmanaged<CbWrapper>.fromOpaque(cbWrapperPtr!).takeRetainedValue()
        wrapper.cb(.success(()))
    }

    return await withCheckedContinuation({ (continuation: CheckedContinuation<(), Never>) in
        let callback = { rustFnRetVal in
            continuation.resume(with: rustFnRetVal)
        }

        let wrapper = CbWrapper(cb: callback)
        let wrapperPtr = Unmanaged.passRetained(wrapper).toOpaque()

        username.toRustStr({ usernameAsRustStr in
            __swift_bridge__$connect(wrapperPtr, onComplete, usernameAsRustStr)
        })
    })
}

At line 2, there following error is present

Type 'CbWrapper' cannot be nested in generic function 'connect'

Introduce `#[swift_bridge(get(field_name))]` attribute

I'm finding that I often want to return the value of a field of an opaque Rust struct, but to do so requires some getter function boilerplate.

#[swift_bridge::bridge]
mod ffi {
   extern "Rust" {
       type SomeType;
       
       fn field(&self) -> u8;
   } 
}

pub struct SomeType {
    field: u8
}

impl SomeType {
    fn field(&self) -> u8 {
        self.field
    }
}

It doesn't feel good to need to write a getter function that I'm only using to expose a field.


Instead of this, it would be useful to be able to expose a field without needing to create a getter function.

Something along the lines of:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type SomeType;

        // Returns self.my_u8
        #[swift_bridge(get(my_u8))]
        fn my_u8(&self) -> u8;

        // Returns ui_to_i16(self.my_u8)
        #[swift_bridge(get_with(my_u8 = u8_to_i16))]
        fn my_u8_converted(&self) -> i16;

        // Returns &self.my_string
        #[swift_bridge(get(&my_string))]
        fn my_string_reference(&self) -> &str;

        // Returns Clone::clone(&self.my_string)
        #[swift_bridge(get_with(&my_string = Clone::clone))]
        fn my_string_cloned(&self) -> String;

        // Returns string_to_u32(&self.my_string)
        #[swift_bridge(get_with(&my_string = string_to_u32))]
        fn my_string_parsed(&self) -> String;
    }
}

pub struct SomeType {
    my_u8: u8,
    my_string: String,
}

fn u8_to_i16 (num: u8) -> i16 {
    num as i16
}

fn string_to_u32(string: String) -> u32 {
    string.parse().unwrap()
}

Running Swift From Rust

This could just me missing some basic things, but I wasn't able to find in the book how to compile swift code to link and use in Rust. The book seems to focus heavily on calling Rust FROM Swift but not the other way around.

I'm currently going down the route to use a Swift Package (code is here https://github.com/lorenzolewis/tauri-ffi/tree/main/examples/swift-bridge/src-swift) to hold my Swift code. I don't see anything in the docs on how you're supposed to annotate Swift code to be exposed to Rust so I'm annotating functions with _cdecl which I've used from another implementation to text.

On the Rust side I'm using Tauri and have the Rust code here https://github.com/lorenzolewis/tauri-ffi/tree/main/examples/swift-bridge/src-tauri.

I'm assuming there's some sort of build/compile step I'm missing with Swift to generate the needed header files that I may have just missed in the docs?

Use cargo-xcode instead of cargo-lipo for XCode setup tutorial

Since cargo-lipo is considered deprecated by its developer, the tutorial for setting up swift-bridge in an xcode project should use a properly maintained alternative instead.
cargo-xcode seems to be a promising alternative since it also does some of the required XCode project configuration and therefore eliminates the need for additional setup/build scripts (build.rs is still necessary but minimal).

I was able to set up and built a project using swift-bridge + cargo-xcode with the Apple Multiplatform target, so I could do a draft for the revised tutorial section if wanted.

Support both ARM64 and x86-64 MacOS builds at once

If I supply --macos target/aarch64-apple-darwin/debug/libmy.a or --macos target/x86_64-apple-darwin/debug/libmy.a they work.

I'd like a way to supply both to build an XCFramework that'll work on the still supported and older Intel Macs.

Fix `get_with` attribute allowing incorrect signature

Problem

The following should not compile since we're saying #[swift_bridge(get_with(&name = Clone::clone))] instead of #[swift_bridge(get_with(&events = Clone::clone))]

But it does.. and leads to undefined behavior if you call the events method.

pub struct SomeStruct {
    name: String,
    events: AnotherStruct
}
#[derive(Clone)]
pub struct AnotherStruct;

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type SomeStruct;

        // INVALID: Should not compile (since `name` is not `AnotherStruct`), but it currently does.
        #[swift_bridge(get_with(&name = Clone::clone))]
        fn events(&self) -> AnotherStruct;
    }

    extern "Rust" {
        type AnotherStruct;
    }
}

Related

This issue is very similar to issue #12 and it's associated pull request #169.

Looking over those should give you a better sense of how to approach this issue.

Potential Solution

  • Read the guide on contributing new compile time errors https://chinedufn.github.io/swift-bridge/contributing/adding-compile-time-errors/index.html

  • Add a new UI test where we return an error if we use the get_with attribute on a function that does not specify a return type

    • https://github.com/chinedufn/swift-bridge/tree/c75d6e744ac64e21145afbe4d251486f861e90cd/crates/swift-bridge-macro/tests/ui
    • We can pass the test by pushing a ParseError near here
      for arg in func.sig.inputs.iter() {
      let is_mutable_ref = fn_arg_is_mutable_reference(arg);
      let is_copy_opaque_type =
      if let Some(TypeDeclaration::Opaque(o)) = associated_type.as_ref() {
      o.attributes.copy.is_some()
      } else if let Some(ty) =
      bridgeable_type_from_fn_arg(arg, &self.type_declarations)
      {
      ty.has_swift_bridge_copy_annotation()
      } else {
      false
      };
      if is_mutable_ref && is_copy_opaque_type {
      self.errors
      .push(ParseError::ArgCopyAndRefMut { arg: arg.clone() });
      }
      }
      let func = ParsedExternFn {
      func,
      associated_type,
      is_swift_initializer: attributes.is_swift_initializer,
      is_swift_identifiable: attributes.is_swift_identifiable,
      host_lang,
      rust_name_override: attributes.rust_name,
      swift_name_override: attributes.swift_name,
      return_into: attributes.return_into,
      return_with: attributes.return_with,
      args_into: attributes.args_into,
      get_field: attributes.get_field,
      };
  • Update the current get_with test to have return types such as -> SomeType

    fn bridge_module_tokens() -> TokenStream {
    quote! {
    #[swift_bridge::bridge]
    mod ffi {
    extern "Rust" {
    type SomeType;
    #[swift_bridge(get_with(field = a::b::c))]
    fn some_function(&self);
    #[swift_bridge(get_with(&field = a::b::c))]
    fn some_function_ref(&self);
    #[swift_bridge(get_with(&mut field = a::b::c))]
    fn some_function_ref_mut(&mut self);
    }
    }
    }
    }

  • Add a UI test for the bug described in the Problem section of the issue

  • In the newly modified get_with codegen test we can modify the Rust tokens to have explicit types. Something like { let val: String = super::a::b::c( (unsafe { &*this }).field ); val }, assuming that the functions return type is String. Getting the test passing should then cause our UI test to correctly have a compile time error

    fn expected_rust_tokens() -> ExpectedRustTokens {
    ExpectedRustTokens::ContainsMany(vec![
    quote! {
    pub extern "C" fn __swift_bridge__SomeType_some_function(
    this: *mut super::SomeType
    ) {
    super::a::b::c( (unsafe { &*this }).field )
    }
    },
    quote! {
    pub extern "C" fn __swift_bridge__SomeType_some_function_ref(
    this: *mut super::SomeType
    ) {
    super::a::b::c( & (unsafe { &*this }).field )
    }
    },
    quote! {
    pub extern "C" fn __swift_bridge__SomeType_some_function_ref_mut(
    this: *mut super::SomeType
    ) {
    super::a::b::c( &mut (unsafe { &mut *this }).field )
    }
    },
    ])
    }

First Time Contributor?

Feel free to comment if you'd like to work on this and we'd be happy to help answer any questions!

Building Swift Packages

I'll leave some of my thoughts here after writing that book chapter on how we should implement the build process for Swift packages into swift-bridge.

We'll probably want an extra option in build.rs to generate a Swift package, maybe something like:

swift_bridge_build::parse_bridges(bridges)
        .write_all_concatenated(out_dir, env!("CARGO_PKG_NAME"))
        .generate_package();

For generating the xcframework, we could either use the xcodebuild command, or we could write the .plist file manually, which I think could be a good option.

I think all of the other steps should be pretty straight forward, following the chapter of the book.

Stop using a custom FFI representation for unit/empty shared structs

Right now if you define a shared struct

#[swift_bridge::bridge]
mod ffi {
    struct SomeStruct;
}

We generate an FFI representation that looks something like this:

#[repr(C)]
struct __swift_bridge__SomeStruct {
    _private_: u8,
}

impl swift_bridge::SharedStruct for SomeStruct {
    type FfiRepr = __swift_bridge__SomeStruct;
}

When passing SomeStruct across the FFI boundary, we create and pass a __swift_bridge__SomeStruct.

This is unnecessary since we control both sides of the FFI boundary.

For example ... say you have

#[swift_bridge::bridge]
mod ffi {
    struct SomeStruct;
    
    extern "Rust" {
        fn reflect(arg: SomeStruct, num: u8) -> SomeStruct;
    }
}

We should generate things like:

// Notice how the FFI function doesn't take a `SomeStruct` argument.
pub extern "C" fn __swift_bridge__reflect(num: u8) {
    super::reflect(SomeStruct, num)
}

The same idea would go for function return types.

Benefits

  • We no longer need a weird FFI representation for empty structs

Support nested fields in `get` and `get_with` attributes

Add support for a dot . notation that lets us expose nested fields.

For example, the following should be possible:

#[swift_bridge::bridge]
mod ffi {
   extern "Rust" {
        type SomeType;
       
        #[swift_bridge(get(field.another_field))]
        fn another_field(&self) -> u16;
   } 
}

pub struct SomeType {
    field: AnotherType
}

struct AnotherType {
    another_field: u16
}

Give users more control over the async runtime

Right now we lazily create an async runtime whenever you first call an async Rust function from Swift.

pub static ASYNC_RUNTIME: Lazy<TokioRuntime> = Lazy::new(|| {
let (sender, receiver) = std::sync::mpsc::sync_channel(10_000);
let runtime = TokioRuntime { sender };
runtime.start_runtime(receiver);
runtime
});
type AsyncFnToSpawn = Pin<Box<dyn Future<Output = ()> + 'static + Send>>;

Users currently have no control over the async runtime. We chose this approach as a quick way to quickly get async functions working, but now that we have a few users it's time to revisit it.

This issue was inspired by a comment #50 (comment) showing an error due to our current async implementation expecting futures to be Send.


This issue is dedicated to thinking through how much control we want to give users over the async runtime.

Do we want users to be able to supply their own async runtime?

#[swift_bridge(async_runtime = MyImplementationOfALazyRuntime)]
mod ffi {
    extern "Rust" {
        async fn foo();
    }
}

Do we want to just provide one multi threaded and one single threaded async runtime and give users the ability to choose which one to use? For example:

mod ffi {
    extern "Rust" {
        #[swift_bridge(async_runtime = "single")]
        async fn foo();
    }
}

Or some other approach?

This needs some thinking. We should have a good sense of what people need to be able to do.. and then use that to inform our design.

I generally lean towards giving users as much flexibility as possible.. which has me leaning towards first exploring approaches where users can supply their own async runtimes.. but yeah let's investigate all of this a bit more.

Vec of shared struct

Currently something like the following Swift code:

class Test {
    func testFunction(things: RustVec<Thing>) { /* Do stuff */ }
}

and this Rust code:

#[swift_bridge::bridge]
mod ffi {
    #[swift_bridge(swift_repr = "struct")]
    pub struct Thing {
        pub some_str: String
    }

    extern "Swift" {
        type Test;

        #[swift_bridge(init)]
        fn new() -> Test;

        fn testFunction(&self, things: Vec<Thing>);
    }
}

// Then somewhere else
let test = ffi::Test::new();
test.testFunction(vec![Thing { some_str: "foo".to_string() }, /* ... */]);

doesn't work, evidently due to the shared struct Thing not implmenting Vectorizable on the Swift side. Am I missing something and this sort of thing is possible? If not, could support for this be added?

Add UI test for incorrect return type

Given the following bridge module

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn some_function() -> u32;
    }
}
fn some_function() -> String {
    unimplemented!()
}

You'll something along the lines of this not-so-useful error message

  --> path/to/file.rs:44:1
   |
44 | #[swift_bridge::bridge]
   | ^^^^^^^^^^^^^^^^^^^^^^^
   | |
   | expected `u32`, found `String`
   | expected `String` because of return type

We need to fix the span to point to the u32 so that the u32 gets underlined.

Add documentation on creating a Swift Package

Hi, I'm the one who asked about using Rust in Swift on Reddit.

I've made a Gist that follows your steps, but slightly modified to set up a Swift project with Swift Package Manager (https://gist.github.com/Jomy10/a4873dd43942ed1bf54d387dbc888795).

I've tried to add dependencies to Package.swift, but I don't know how to include them in the compiling process. Swiftc documentation seems to be rather scarce.

Here's an example of the Package.swift file:

// swift-tools-version:5.5
import PackageDescription

let package = Package(
    name: "swift",
    dependencies: [
        .package(url: "/path/to/HelloSwift", .branch("master"))
    ],
    targets: [
        .target(
            name: "swift",
            dependencies: [
                .product(name: "HelloSwift", package: "HelloSwift")
            ]),
        .testTarget(
            name: "swiftTests",
            dependencies: ["swift"]),
    ]
)

Folder structure is also in the gist.

I think it would be great if we could also use Swift Package Manager in the Swift project using the Rust library. Or maybe import the Rust library as a package in Package.Swift.

Are generics possible?

I have the following struct type defined currently APIResponseObject<T, MT>, where both T and MT are supposed to be deserialized by Rust.

For some reason, if I try to extern them, I get a weird error, probably because generics (e.g. MyStruct<T>) aren't implemented for the bridge yet?

Following code is my bridge:

#[swift_bridge::bridge]
mod ffi {
    // inferred from https://chinedufn.github.io/swift-bridge/bridge-module/opaque-types/index.html
    extern "Rust" {
        type APIResponseList<T, MT>;
        type PteroClientServer;
        type APIResponsePaginationMetaData;
        type APIResponseObject<T, MT>;
    }

    extern "Rust" {
        type PteroClient;

        #[swift_bridge(init)]
        fn new(base_url: String, api_key: String) -> PteroClient;

        // inferred from https://github.com/chinedufn/swift-bridge/blob/ecc6704985ea260ae502d7ccdd776602e5f263ea/examples/async-functions/src/lib.rs
        async fn get_servers(&self) -> Option<APIResponseList<APIResponseObject<PteroClientServer, APIResponsePaginationMetaData>, APIResponsePaginationMetaData>>;
    }
}

The error I receive:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("expected `,`")', /home/cozygalvinism/.cargo/registry/src/github.com-1ecc6299db9ec823/swift-bridge-ir-0.1.29/src/bridged_type.rs:335:82

Am I doing something wrong or is my assumption correct?

Optimize passing going from a Rust `std::string::String` to a Swift `String`

This issue was born from a discussion on the Rust reddit


Problem

Today, in order to go from a Rust std::string::String to a Swift String we need to:

  1. Box::into_raw(Box::new(string)) the rust std::string::String to get a raw pointer to the String (one allocation)

    swift_bridge::string::RustString(super::some_function()).box_into_raw()

  2. Send the pointer to Swift

  3. Allocate a RustString(ptr: ptr) class instance

    func some_function() -> RustString {
    RustString(ptr: __swift_bridge__$some_function())
    }

  4. Call rustString.toString(), which allocates a swift String

    extension RustString {
    func toString() -> String {
    let str = self.as_str()
    let string = str.toString()
    return string
    }
    }
    extension RustStr {
    func toBufferPointer() -> UnsafeBufferPointer<UInt8> {
    let bytes = UnsafeBufferPointer(start: self.start, count: Int(self.len))
    return bytes
    }
    func toString() -> String {
    let bytes = self.toBufferPointer()
    return String(bytes: bytes, encoding: .utf8)!
    }
    }

  5. The class RustString on the Swift side has a deinit method that calls into Rust to deallocate the Rust std::string::String


When returning a Rust &mut String such as:

extern "Rust" {
    type Foo;
    fn get_mut_string(&self) -> &mut String;
}

we want a class RustStringRefMut on the Swift side that exposes methods to access / mutate the underlying Rust std::string::String.

However, when returning an owned String such as:

extern "Rust" {
    type Foo;
    fn get_owned_string(&self) -> String;
}

there is no reason to have the intermediary class RustString since we don't need Rust to manage the underlying std::string::String.

Instead we want to go directly from Rust std::string::String to Swift String.

Open Questions

This entire issue still needs some more thought on planning... We need to think through all of the implications.

Here are a list of things to think through that we can add to over time:

  • If we called shrink_to_fit on the std::string::String before passing it over to Swift we'd only need the pointer and length in order to de-allocate it later.. If we use a CFAllocatorContext (illustrated in the comment below... func rustDeallocate) in order to de-allocate the std::string::String whenever Swift no longer needed it... we'd have the pointer to the bytes.. but how would we get that len? Or do we need another approach..? Could have a global de-allocator on the Rust side that looked up lengths in something like a HashMap<String pointer, String length> in order to de-allocate Strings... But perhaps there's a simpler/smarter approach to all of this..?

Support doc comments on functions

We should store doc comments in the ParsedExternFn and then emit them when generating the function on the Swift side (and vice versa.. doc comments on extern "Swift" functions should appear on the generated Rust function.

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        /// This comment should be used when generating the 
        /// corresponding Swift function.
        fn some_function();
    }
}

Make `return_into` attribute work for enums.

Right now the following doesn't compile:

#[swift_bridge::bridge]
mod ffi {
    enum MyEnum {
        Variant
    }

    extern "Rust" {
        #[swift_bridge(return_into)]
        fn some_function(&self) -> MyEnum;
    }
}

The current generated code looks like:

(unsafe { &*this }).some_function().into().into_ffi_repr()

We should instead generate something like:

{let val: MyEnum = (unsafe { &*this }).some_function().into(); val}.into_ffi_repr()

Compiler warning: Initialization of 'UnsafePointer<Element>' results in a dangling pointer

The swift compiler emits warnings about this line:

image

SwiftBridgeCore.swift:210:36: Initialization of 'UnsafePointer' results in a dangling pointer
SwiftBridgeCore.swift:210:50: Implicit argument conversion from 'Array' to 'UnsafePointer' produces a pointer valid only for the duration of the call to 'init(_:)'
SwiftBridgeCore.swift:210:50: Use the 'withUnsafeBufferPointer' method on Array in order to explicitly convert argument to buffer pointer valid for a defined scope

The warning is totally ignoreable, but it'd be nice to fix.

Generate public initializer for shared structs

When using swift-bridge in a Swift package it is not possible to initialize a shared struct from Swift due to the default initializer being internal.

I believe the only solution for this is to generate an initializer so that we can explicitly mark it as public.

CLI create-package command

Alright, so the last piece of the puzzle for Swift Packages would be the CLI.

Since the cli crate is currently empty, I suggest we use Clap. I've used this before, and it's really easy to set up and it takes care of a lot of the boilerplate for a CLI. There are a lot of examples to draw from as well.

In the previous issue, you commented this as a suggestion for the CLI:

# In Bash
swift-bridge generate-package \
  --bridges-dir some-out-dir \
  --ios target/aarch64-apple-ios/debug/libmy_rust_lib.a \
  --macos target/x86_64-apple-darwin/debug/libmy_rust_lib.a \
  --out-dir ./my-swift-package

Cannot use multiple local modules with their own FFI

I am trying to create a modular port of a library using multiple modules, e.g. account and record, into a single Swift Package using the documentation's instructions on doing so. To combine multiple modules, I was following this example in the docs: https://chinedufn.github.io/swift-bridge/bridge-module/transparent-types/structs/index.html#struct-attributes. I have provided the code I am using below with this file structure:

  • swift
    • build-rust.sh
    • build.rs
    • src
      • lib.rs
      • main.rs
      • account
        • account.rs
        • mod.rs
      • record
        • record.rs
        • mod.rs

Compiling this using ./build-rust.sh as below gives an empty generated/swift.swift file as well as its header, while the SwiftBridgeCore are correctly populated. After running main.rs, it generates the xcframework with swift.swift empty other than import RustXcframework. If I just use the account module and comment out all the other files, it doesn't generate the Swift structs either. The only way I got them to work is by moving them all to lib.rs without any additional modules, it then works perfectly.

I am using swift-bridge and swift-bridge-build version 0.1.32, which is latest.

# build-rust.sh

#! /bin/bash

set -e

THISDIR=$(dirname $0)
cd $THISDIR

export SWIFT_BRIDGE_OUT_DIR="$(pwd)/generated"
# Build the project for the desired platforms:
cargo build --target x86_64-apple-darwin
cargo build --target aarch64-apple-ios
cargo build --target x86_64-apple-ios
// 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"));
}
// src/main.rs

use std::path::PathBuf;
use std::collections::HashMap;
use swift_bridge_build::{CreatePackageConfig, ApplePlatform};

fn main() {
    swift_bridge_build::create_package(CreatePackageConfig {
        bridge_dir: PathBuf::from("./generated"),
        paths: HashMap::from([
            (ApplePlatform::IOS, "../target/x86_64-apple-ios/debug/libswift.a".into()),
            (ApplePlatform::Simulator, "../target/aarch64-apple-ios/debug/libswift.a".into()),
            (ApplePlatform::MacOS, "../target/x86_64-apple-darwin/debug/libswift.a".into())
        ]),
        out_dir: PathBuf::from("../"),
        package_name: "SwiftP".to_string()
    });
}
// src/lib.rs

pub mod account;
pub mod record;
// src/account/account.rs

use rand::{rngs::StdRng, SeedableRng};
use std::{str::FromStr};

#[swift_bridge::bridge]
pub mod ffi_account {
    #[swift_bridge(swift_repr = "struct")]
    pub struct Account {
        pub private_key: String,
        pub view_key: String,
        pub address: String,
    }

    extern "Rust" {
        pub fn new_account() -> Account;
        pub fn account_from_private_key(private_key: String) -> Account;
    }
}

pub fn new_account() -> ffi_account::Account {
    let rng = &mut StdRng::from_entropy();

    let account = AccountNative::new(rng);

    return convert_account(account);
}

pub fn account_from_private_key(private_key: String) -> ffi_account::Account {
    let p_key_string = private_key.to_string();

    let private_key = PrivateKey::from_str(&*p_key_string).unwrap();

    let account = AccountNative::from(private_key);

    return convert_account(account);
}
// src/account/mod.rs

pub mod account;
pub use account::*;
// src/record/record.rs

#[swift_bridge::bridge]
pub mod ffi_record {
    #[swift_bridge(swift_repr = "struct")]
    pub struct Record {
        pub(crate) record_native: RecordNative,
        pub is_dummy: Bool,
        pub owner: String,
        pub value: i64,
    }

    extern "Rust" {
        pub fn record_from_string(record_string: String) -> Record;
        pub fn decrypt_record(decryption_key: String, ciphertext: String) -> Record;
        pub fn record_to_string(record: Record) -> String;
    }
}

pub fn record_from_string(record_string: String) -> ffi_record::Record {
    let record = RecordNative::from_str(&record_string).unwrap();

    return record_from_native(record);
}

pub fn decrypt_record(decryption_key: String, ciphertext: String) -> ffi_record::Record {
    let decryption_key: DecryptionKey = match ViewKey::from_str(&decryption_key) {
        Ok(view_key) => view_key.into(),
        Err(_) => DecryptionKey::from_record_view_key(&RecordViewKey::from_str(&decryption_key).unwrap())
    };

    let ciphertext = CiphertextNative::from_str(&ciphertext).unwrap();

    let record = RecordNative::decrypt(&decryption_key, &ciphertext).unwrap();

    return record_from_native(record);
}

pub fn record_to_string(record: ffi_record::Record) -> String {
     return record.record_native.toString();
}
// src/record/mod.rs

pub mod record;
pub use record::*;

Add UI test and error for unsupported module item

We should add a UI test with the following contents:

#[swift_bridge::bridge]
mod ffi {
    use some_crate;
}

Right now you'll see an error along the lines of:

thread 'main' panicked at 'not yet implemented:
                          Push an error that the module may only contain `extern` blocks, structs
                          and enums
                          '

Xcode 13.3 requires artifact name matches target name for RustXcframework

When running the test-integration.sh I see this at the end:

     Running `/Users/simlay/projects/infinyon/swift-bridge/target/debug/integration-test-create-swift-package`
objc[48421]: Class AppleTypeCRetimerRestoreInfoHelper is implemented in both /usr/lib/libauthinstall.dylib (0x1ee779eb0) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x10816c4f8). One of the two will be used. Which one is undefined.
objc[48421]: Class AppleTypeCRetimerFirmwareAggregateRequestCreator is implemented in both /usr/lib/libauthinstall.dylib (0x1ee779f00) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x10816c548). One of the two will be used. Which one is undefined.
objc[48421]: Class AppleTypeCRetimerFirmwareRequestCreator is implemented in both /usr/lib/libauthinstall.dylib (0x1ee779f50) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x10816c598). One of the two will be used. Which one is undefined.
objc[48421]: Class ATCRTRestoreInfoFTABFile is implemented in both /usr/lib/libauthinstall.dylib (0x1ee779fa0) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x10816c5e8). One of the two will be used. Which one is undefined.
objc[48421]: Class AppleTypeCRetimerFirmwareCopier is implemented in both /usr/lib/libauthinstall.dylib (0x1ee779ff0) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x10816c638). One of the two will be used. Which one is undefined.
objc[48421]: Class ATCRTRestoreInfoFTABSubfile is implemented in both /usr/lib/libauthinstall.dylib (0x1ee77a040) and /Library/Apple/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/MobileDevice (0x10816c688). One of the two will be used. Which one is undefined.
2022-04-20 22:31:29.235 xcodebuild[48421:8143814] Requested but did not find extension point with identifier Xcode.IDEKit.ExtensionSentinelHostApplications for extension Xcode.DebuggerFoundation.AppExtensionHosts.watchOS of plug-in com.apple.dt.IDEWatchSupportCore
2022-04-20 22:31:29.235 xcodebuild[48421:8143814] Requested but did not find extension point with identifier Xcode.IDEKit.ExtensionPointIdentifierToBundleIdentifier for extension Xcode.DebuggerFoundation.AppExtensionToBundleIdentifierMap.watchOS of plug-in com.apple.dt.IDEWatchSupportCore
xcframework successfully written out to: /Users/simlay/projects/infinyon/swift-bridge/SwiftRustIntegrationTestRunner/swift-package-rust-library-fixture/MySwiftPackage/rust_framework.xcframework
'myswiftpackage': error: artifact not found for target 'RustXcframework'

I've only found this on https://stackoverflow.com/a/71546817 that backs this up. I looked at the xcode 13.3 release notes to see if it mentioned this. I'm guessing it's because a new version of xcode shipped a new version of swift.

The temporary work around I've done is https://github.com/infinyon/fluvio-client-swift/blob/ef3bebfa917d59dfd1ac55f3b9694eb96c8d4e4c/build-rust.sh#L46-L50

Support transparent structs that have `Option<OpaqueRustType>` fields

I'm converting a Rust crate to Swift to test out the whole framework in an actual project.

It seems like optional types with structs are not yet supported?

So for example something like this:

pub struct MyStruct {
    pub val: u8
}
pub fn optional_return() -> Option<MyStruct> {
    Some(MyStruct{val: 7})
}

The Swift counterpart is pretty straight forward:

public class MyStruct { // or public struct
   var val: u8
}

public fund optional_return() -> Optional<MyStruct> {
    return MyStruct(val: 7)
}

I'm not sure how the bridge between these types would look like/how hard that would be to implement.

Support `Vec<String>`

For example, the following does not work right now:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        fn some_function(arg: Vec<String>) -> Vec<String>;
    }
}

Swiftc flags in Swift Package?

I was wondering if we could replace this command:

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

with swift build


I have tried the following in Package.swift

// swift-tools-version:5.5

import PackageDescription

let package = Package(
    name: "LinuxSwiftPackage",
    dependencies: [],
    targets: [
        .executableTarget(
            name: "LinuxSwiftPackage",
            dependencies: [],
            linkerSettings: [
                .unsafeFlags(["-L lib_mac", "-lmy_rust_lib", "-import-objc-header bridging-header.h"])
            ]
        )
    ]
)

It didn't seem to work, though.

All settings can be found in the Apple documentation, there is CSettings, SwiftSettings, LinkerSettings, ...

These are all things we can specify in the Package.swift.

here you can also find some information under "Settings".


It looks like you can also specify a json file using swift build --destination file.json. This also looks like a promising option.

I tried swift build --destination file.json

// file.json
{
    "version": 1,
    "sdk": "/swift/usr/bin/swift",
    "toolchain-bin-dir": "/swift/usr/bin",
    "target": "x86_64-apple-macosx",
    "dynamic-library-extension": "lib",
    "extra-cc-flags": [
    ],
    "extra-swiftc-flags": [
        "-L lib_mac",
        "-lmy_rust_lib",
        "-import-objc-header",
        "bridging-header.h"
    ],
    "extra-cpp-flags": []
}

but I'm getting

error: no such module 'Foundation'
import Foundation

This would allow us to link our library more easily in executable Swift packages, which I think would be useful for Linux as an alternative for generating a Swift Package.

I can't seem to find any more information on this and I'm not really knowledgeable in linking header files.

Introduce `#[swift_bridge(Equatable)]` attribute for opaque Rust types

Given the following bridge:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        #[swift_bridge(Equatable)]
        type SomeType;
    }
}

#[derive(PartialEq)]
struct SomeType;

We should emit some Swift along the lines of:

extension SomeTypeRef: Equatable {
    public static func == (lhs: SomeTypeRef, rhs: SomeTypeRef) -> Bool {
        __swift_bridge__$SomeType$_partial_eq(rhs.ptr, lhs.ptr)
    }
}

And on the Rust side we'd generate something like:

#[export_name = "__swift_bridge__$SomeType$_partial_eq"]
pub extern "C" fn __swift_bridge__SomeType__partial_eq (
    lhs: *const super::SomeType,
    rhs: *const super::SomeType
) -> bool {
    unsafe { &*lhs == &*rhs }
}

Support parameters for async functions and async methods

Thanks for this crate! I'm looking into using librespot with Swift, but can't seem to have async methods or async functions with parameters.

async method

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type SpotifyApp;

        async fn connect(&mut self);
    }
}

pub struct SpotifyApp {}

impl SpotifyApp {
    pub async fn connect(&mut self) {
        print!("Connecting to Spotify...");
    }
}
error[E0425]: cannot find value `this` in this scope
 --> src/lib.rs:6:1
  |
6 | #[swift_bridge::bridge]
  | ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
  |
  = note: this error originates in the attribute macro `swift_bridge::bridge`

async function with parameters

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        type SpotifyApp;

        async fn connect(track_id: &str);
    }
}

pub struct SpotifyApp {}

pub async fn connect(track_id: &str) {
    print!("Connecting to Spotify... {track_id}");
}
error[E0425]: cannot find value `track_id` in this scope
  --> src/lib.rs:11:26
   |
11 |         async fn connect(track_id: &str);
   |                          ^^^^^^^^ not found in this scope

Without one of these, I'm unable to pass data from the Swift app to Rust within async functions, do you think it is possible to have async methods or async functions with parameters? Thank you again :)

Current version usable on M1?

I've tried both the book example + running the tests and both fail ("error[E0658]: destructuring assignments are unstable" and "panic in main thread" respectively). Do I need nightly compiler?

I can follow up with stack traces/needed info, if someone could confirm it's buildable/usable. I really like the idea of this crate, so hoping to succeed

`rust-binary-calls-swift-package` example doesn't work on a Linux environment

Hello.
I'm trying to build a CLI application that is mainly written in Rust and calls some Swift functions, and I'm developing it on a Linux environment.
I tried to run rust-binary-calls-swift-package example, but it didn't run with following errors.
I'm using WSL2 with Ubuntu and swift-5.6.2-RELEASE-ubuntu20.04.

Do you know how I can make it work? Or, are Linux environments not supported?
Thank you in advance!

$ cargo run -p rust-binary-calls-swift-package
   Compiling proc-macro2 v1.0.42
   Compiling unicode-ident v1.0.2
   Compiling quote v1.0.20
   Compiling syn v1.0.98
   Compiling libc v0.2.126
   Compiling fastrand v1.8.0
   Compiling remove_dir_all v0.5.3
   Compiling cfg-if v1.0.0
   Compiling tempfile v3.3.0
   Compiling swift-bridge-ir v0.1.35 (/home/yoshikuni/GitHub/Rust/swift-bridge/crates/swift-bridge-ir)
   Compiling swift-bridge-build v0.1.35 (/home/yoshikuni/GitHub/Rust/swift-bridge/crates/swift-bridge-build)
   Compiling swift-bridge-macro v0.1.35 (/home/yoshikuni/GitHub/Rust/swift-bridge/crates/swift-bridge-macro)
   Compiling rust-binary-calls-swift-package v0.1.0 (/home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package)
   Compiling swift-bridge v0.1.35 (/home/yoshikuni/GitHub/Rust/swift-bridge)
error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" "-m64" "/tmp/rustcBQCunq/symbols.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.1w9emz731ogimejr.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.24kdsva5pt5pnbco.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.26896tw8fqt0v9fu.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.2tpci647um6thpo2.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.4dhdhlqomt6smcj1.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.4h8n84jaxycwap6v.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.4hpkoi5vvq6zq02l.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.abt62j993v3u99r.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.dj4o1vryudkth0e.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.zjy3tzpqmre36eq.rcgu.o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2.1zen2gw3lpr0rw58.rcgu.o" "-Wl,--as-needed" "-L" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps" "-L" "/home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/.build/debug" "-L" "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/" "-L" "/usr/lib/swift" "-L" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "-lswift-library" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/libswift_bridge-882327837dd14bed.rlib" "-Wl,--start-group" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-69edc9ac8de4d39c.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libpanic_unwind-254ac8a4d96ed89e.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libobject-8bed7ede368468ef.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libmemchr-9da1150e6aeada6b.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libaddr2line-dec39a40a22b358c.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libgimli-7a90882341200e38.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_demangle-6a5da46a37549d0a.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd_detect-6dc3d1df7e408d5b.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libhashbrown-9d89ad14c568d44a.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libminiz_oxide-ecbdf47025adae10.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libadler-29db3ffec771ef22.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_alloc-8b55ad9e1a2080d1.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libunwind-f37a55941b8b3bbd.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcfg_if-c1d45d3d6971ce0d.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liblibc-18ac7cbb154ecd62.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/liballoc-80c5909c82700e85.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-aa7f747e2f6af7d5.rlib" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-60d544c9f51ce476.rlib" "-Wl,--end-group" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-d1bd89f2a607e488.rlib" "-Wl,-Bdynamic" "-lgcc_s" "-lutil" "-lrt" "-lpthread" "-lm" "-ldl" "-lc" "-Wl,--eh-frame-hdr" "-Wl,-znoexecstack" "-L" "/home/yoshikuni/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/home/yoshikuni/GitHub/Rust/swift-bridge/target/debug/deps/rust_binary_calls_swift_package-292e71af283f5de2" "-Wl,--gc-sections" "-pie" "-Wl,-zrelro,-znow" "-nodefaultlibs"
  = note: /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/.build/debug/libswift-library.a(swift_library.swift.o): in function `$s13swift_library0A14_multiply_by_43nums5Int64VAE_tF':
          /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `$ss27_allocateUninitializedArrayySayxG_BptBwlFyp_Tg5'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `$sSSN'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `$sypN'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `$ss5print_9separator10terminatoryypd_S2StF'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `swift_bridgeObjectRelease'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `swift_bridgeObjectRelease'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:2: undefined reference to `swift_release'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:4: undefined reference to `$ss27_allocateUninitializedArrayySayxG_BptBwlFyp_Tg5'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:4: undefined reference to `$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:4: undefined reference to `$ss5print_9separator10terminatoryypd_S2StF'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:4: undefined reference to `swift_bridgeObjectRelease'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:4: undefined reference to `swift_bridgeObjectRelease'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:4: undefined reference to `swift_release'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:8: undefined reference to `$ss27_allocateUninitializedArrayySayxG_BptBwlFyp_Tg5'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:8: undefined reference to `$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:8: undefined reference to `$ss5print_9separator10terminatoryypd_S2StF'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:9: undefined reference to `swift_bridgeObjectRelease'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:9: undefined reference to `swift_bridgeObjectRelease'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/Sources/swift-library/swift_library.swift:9: undefined reference to `swift_release'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/.build/debug/libswift-library.a(swift_library.swift.o): in function `$ss27_finalizeUninitializedArrayySayxGABnlF':
          /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/<compiler-generated>:(.text+0x2a7): undefined reference to `$sSaMa'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/<compiler-generated>:(.text+0x2c0): undefined reference to `swift_retain'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/<compiler-generated>:(.text+0x2c9): undefined reference to `swift_release'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/.build/debug/libswift-library.a(swift_library.swift.o): in function `$ss5print_9separator10terminatoryypd_S2StFfA0_':
          /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/<compiler-generated>:(.text+0x2f6): undefined reference to `$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC'
          /usr/bin/ld: /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/.build/debug/libswift-library.a(swift_library.swift.o): in function `$ss5print_9separator10terminatoryypd_S2StFfA1_':
          /home/yoshikuni/GitHub/Rust/swift-bridge/examples/rust-binary-calls-swift-package/swift-library/<compiler-generated>:(.text+0x316): undefined reference to `$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC'
          collect2: error: ld returned 1 exit status
          
  = help: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

error: could not compile `rust-binary-calls-swift-package` due to previous error

trouble with CodegenVisualizer example

Entitlements file seems to be missing (I added one)

Then getting

Generated/codegen-visualizer/codegen-visualizer.swift:36:24: error: initializer cannot be declared public because its parameter uses an internal type
    public convenience init(_ generated_code_holder: GeneratedCodeHolder) {
                       ^                             ~~~~~~~~~~~~~~~~~~~
/Users/holliday/swift-bridge/examples/codegen-visualizer/CodegenVisualizer/CodegenVisualizer/ContentView.swift:119:7: note: type declared here
class GeneratedCodeHolder: ObservableObject {

Xcode 13.3.1 (13E500a)

Does .github/workflows/test.yaml test that example projects build with Xcode? I didn't see any lines of xcodebuild in there.

Rename `into_return_type` to `return_into`

Right now you can use the into_return_type attribute to call .into() on the real return type to convert it into the type that you want to pass over FFI.

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        #[swift_bridge(into_return_type)]
        fn some_function() -> String;
    }
}
fn some_function() -> &'static str {
    "hello world"
}

We want to rename this to return_into. This makes the name more consistent with other attributes that we have such as args_into and return_with.

A find and replace of into_return_type -> return_with should do the trick.

A find and replace of into_return_type -> return_into should do the trick.

swift-bridge panics when trying to build

To reproduce:

  1. clone https://github.com/audulus/rui-ios
  2. git checkout 2b9e9cb
  3. open rui-ios.xcodeproj
  4. Uncomment ./build-rust.sh in the Build Rust build phase
  5. Fiddle with provisioning as usual
  6. Build

I'm probably doing something wrong, but also the error message isn't very helpful. Which file can't it find?

BUIlDING FOR DEBUG
[INFO  cargo_lipo::meta] Will build universal library for ["rui-ios"]
[INFO  cargo_lipo::lipo] Building "rui-ios" for "aarch64-apple-ios"
   Compiling swift-bridge v0.1.31
error: failed to run custom build command for `swift-bridge v0.1.31`

Caused by:
  process didn't exit successfully: `/Users/holliday/rui-ios/target/debug/build/swift-bridge-ba173a17e88ff9c5/build-script-build` (exit status: 101)
  --- stdout
  cargo:rerun-if-env-changed=SWIFT_BRIDGE_OUT_DIR
  cargo:rerun-if-changed=src/std_bridge/string.swift
  cargo:rerun-if-changed=src/std_bridge/rust_vec.swift

  --- stderr
  thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', /Users/holliday/.cargo/registry/src/github.com-1ecc6299db9ec823/swift-bridge-0.1.31/build.rs:21:47
  note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

swift bridge test suite fails to compile

To reproduce:

  1. clone swift-bridge
  2. git checkout 1951b4a
  3. cargo test --all && ./test-integration.sh
   Compiling hir_def v0.0.0 (https://github.com/rust-analyzer/rust-analyzer#c6995a37)
error[E0308]: mismatched types
  --> /Users/holliday/.cargo/git/checkouts/rust-analyzer-5e0f1308176aaeda/c6995a3/crates/hir_def/src/intern.rs:62:25
   |
62 |             None => Err(shard),
   |                         ^^^^^ expected struct `parking_lot::RawRwLock`, found struct `dashmap::RawRwLock`
   |
   = note: expected struct `lock_api::RwLockWriteGuard<'_, parking_lot::RawRwLock, HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>>`
              found struct `lock_api::RwLockWriteGuard<'_, dashmap::RawRwLock, hashbrown::map::HashMap<Arc<T>, SharedValue<()>, BuildHasherDefault<FxHasher>>>`
rustc 1.60.0 (7737e0b5c 2022-04-04)
cargo 1.60.0 (d1fd9fe2c 2022-03-01)
macOS 12.3.1 (21E258)
Xcode  13.3.1 (13E500a)

Separate running Swift/Rust integration tests from running Swift Package tests

Right now all of our Swift+Rust integration tests are stuffed into https://github.com/chinedufn/swift-bridge/blob/master/test-integration.sh

We should instead have ./test-swift-rust-integration.sh and ./test-swift-packages.sh.

Then call both of those from separate MacOS CI jobs.

- name: Run integration tests
run: ./test-integration.sh


We should also get rid of the messy cleaning that's going on

# Delete previous generated files/folders
rm -r swift-package-rust-library-fixture/generated || true
rm -r swift-package-rust-library-fixture/MySwiftPackage || true
rm -r swift-package-rust-library-fixture/target || true
rm -r swift-package-test-package/.build || true

and instead just copy what we need to a temp dir.. and then have all of the building and test running happen in a temp directory

Introduce `#[swift_bridge(Hashable)]` attribute for opaque Rust types

Given the following bridge:

#[swift_bridge::bridge]
mod ffi {
    extern "Rust" {
        #[swift_bridge(Hashable)]
        type SomeType;
    }
}

#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
struct SomeType;

We should emit some Swift along the lines of:

extension SomeTypeRef: Hashable {
    public func hash(into hasher: inout Hasher) {
        hasher.combine(__swift_bridge__$SomeType$_hash(rhs.ptr))
    }
}

And on the Rust side we'd generate something like:

#[export_name = "__swift_bridge__$SomeType$_hash"]
pub extern "C" fn __swift_bridge__SomeType__hash (
    this: *const super::SomeType,
) -> u64 {
    let mut s = DefaultHasher::new();
    {unsafe &*this}.hash(&mut s);
    s.finish()
}

How can I get the CLI?

Hello. Thank you for this great library and your effort for making this!

I'm trying to make a project following swiftc+Cargo page of the book, especially by linking swift native library to Rust, which is this part.
https://github.com/chinedufn/swift-bridge/tree/master/book/src/building/swiftc-and-cargo#rust-links-to-a-swift-native-library

However, in the shell script, swift-bridge command is used, and I couldn't find where to get the command.
Could you tell me how I can use the command and make the project as written there?

Thank you.

Make `swift-bridge-build` write `SwiftBridgeCore` files alongside the library's bridges

Right now swift-bridge writes SwiftBridgeCore.h and SwiftBridgeCore.swift to the SWIFT_BRIDGE_OUT_DIR during the swift-bridgecrate's build script.

The problem with this is that if that build script doesn't run (say.. it already ran and then after it ran you deleted the auto generated files) you won't generate the SwiftBridgeCore files.

Instead.. when you use swift_bridge_build we should generate the core files alongside your library's bridge files.

This way you always get what you need when you build, as opposed to today's situation where you won't get the core files if swift-bridge's build script doesn't run.


Once we solve this issue we can comment back in the CI job that was commented out in #90

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.