Skip to content

Support swift_repr = "class" to use a Swift class for transparent structs #196

@chinedufn

Description

@chinedufn

NOTE: I haven't thought through this very deeply. Just jotting down some quick notes/ideas that we can flesh out later...


Right now a transparent struct can only be represented using a Rust struct and a Swift struct.

#[swift_bridge::bridge]
mod ffi {
    // Notice the `swift_repr = "struct"
    #[swift_bridge(swift_repr = "struct")]
    struct ReprStruct {
        name: String
    }
}

We want it to also be possible to have a transparent struct show up as a struct on the Rust side and a class on the Swift side.

#[swift_bridge::bridge]
mod ffi {
    // Notice the `swift_repr = "class"
    #[swift_bridge(swift_repr = "class")]
    struct MyReprClassStruct {
        name: String
    }
}

We do not currently have a way to pass a reference to a Swift struct from Swift -> Rust.
Whether or not there is a good way to do this will require some research.

We do, however, already support passing a Swift class from Swift -> Rust. This is how opaque types are passed from Swift -> Rust.
We get a pointer to the Swift class and pass that pointer over FFI to Rust.

Supporting swift_repr = "class" will unlock some use cases such as supporting Vec<TransparentStruct> and sharing references to transparent structs between languages (both immutably and mutably).

Essentially, when a transparent struct has swift_repr = "class" it should be passed over FFI in the largely same way that we pass opaque types today.
The only difference is that the generated Swift class should have accessible fields.

So this:

#[swift_bridge::bridge]
mod ffi {
    // Notice the `swift_repr = "class"
    #[swift_bridge(swift_repr = "class")]
    struct MyReprClassStruct {
        name: Vec<u8>
    }
}

would become something along the lines of:

class MyReprClassStruct {
    var name: RustVec<u8>

    // ...
}

similar to how we generate classes for opaque Rust types

/// Test code generation for an extern "Rust" type.
mod extern_rust_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type SomeType;
}
}
}
}
/// Verify that we generate a function that frees the memory behind an opaque pointer to a Rust
/// type.
fn expected_rust_tokens() -> ExpectedRustTokens {
ExpectedRustTokens::Contains(quote! {
#[export_name = "__swift_bridge__$SomeType$_free"]
pub extern "C" fn __swift_bridge__SomeType__free (
this: *mut super::SomeType
) {
let this = unsafe { Box::from_raw(this) };
drop(this);
}
})
}
fn expected_swift_code() -> ExpectedSwiftCode {
ExpectedSwiftCode::ContainsAfterTrim(
r#"
public class SomeType: SomeTypeRefMut {
var isOwned: Bool = true
public override init(ptr: UnsafeMutableRawPointer) {
super.init(ptr: ptr)
}
deinit {
if isOwned {
__swift_bridge__$SomeType$_free(ptr)
}
}
}
public class SomeTypeRefMut: SomeTypeRef {
public override init(ptr: UnsafeMutableRawPointer) {
super.init(ptr: ptr)
}
}
public class SomeTypeRef {
var ptr: UnsafeMutableRawPointer
public init(ptr: UnsafeMutableRawPointer) {
self.ptr = ptr
}
}
"#,
)
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions