Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vec of shared struct #91

Open
smolck opened this issue May 5, 2022 · 10 comments
Open

Vec of shared struct #91

smolck opened this issue May 5, 2022 · 10 comments
Labels
good first issue Good for newcomers

Comments

@smolck
Copy link

smolck commented May 5, 2022

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?

@chinedufn
Copy link
Owner

Hey.

Yup you're right that Vec<SharedStruct> isn't implemented yet. You aren't missing anything.

Right now we only support Vec<OpaqueRustType>

/// Test code generation for Rust function that has an argument
/// Vec<T> where T is an opaque Rust type.
mod extern_rust_fn_arg_vec_of_opaque_rust_type {
use super::*;
fn bridge_module_tokens() -> TokenStream {
quote! {
mod ffi {
extern "Rust" {
type MyRustType;
fn some_function(arg: Vec<MyRustType>);

#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
type ARustTypeInsideVecT;
#[swift_bridge(init)]
fn new(text: &str) -> ARustTypeInsideVecT;
fn text(&self) -> &str;
}
extern "Rust" {
fn rust_reflect_vec_opaque_rust_type(
arg: Vec<ARustTypeInsideVecT>,
) -> Vec<ARustTypeInsideVecT>;
}
}

/// Verify that a Vec<T> of opaque Rust types can be used as an argument and return
/// type for extern "Rust" functions.
func testReflectVecOfOpaqueRustType() throws {
let vec: RustVec<ARustTypeInsideVecT> = RustVec()
vec.push(value: ARustTypeInsideVecT("hello world"))
let reflected = rust_reflect_vec_opaque_rust_type(vec)
XCTAssertEqual(reflected.len(), 1)
XCTAssertEqual(reflected.get(index: 0)!.text().toString(), "hello world")
}


Normally I'd try to get this to you right away but I'm pushing up against a deadline right now.

I can implement support for this over the weekend if that works for you?

Otherwise if it's urgent I'd be happy to provide guidance on how to implement this.


Thanks for opening this issue!

@smolck
Copy link
Author

smolck commented May 6, 2022

I can implement support for this over the weekend if that works for you?

Yes that’d be great! Thank you!

@oeed
Copy link

oeed commented Nov 26, 2022

I'm in need of this too, would be awesome to get added!

@oeed
Copy link

oeed commented Dec 20, 2022

@chinedufn this is blocking me currently so I'd be happy to try and implement, do you have any pointers as to how to do so?

@chinedufn
Copy link
Owner

Hey! Thanks for the heads up. I'll write up some detailed instructions either tonight or tomorrow.

@chinedufn
Copy link
Owner

Hey. Sorry I'll have to get to this today or tomorrow! Will definitely write these for you just need to finish a couple more unrelated things. Thanks!

@chinedufn
Copy link
Owner

chinedufn commented Dec 24, 2022

Thanks for waiting! I've written up a guide to adding support for Vec<TransparentStruct>. Please let me know if you have any questions and I'll be more than happy to help.

Implementing Support for Vec<TransparentStruct>

Background

Integration Tests

Add an integration test for Vec<TransparentStruct>.

You can use Vec<TransparentEnum> as a guide:

  • /// Verify that a Vec<T> of transparent enums can be used as an argument and return
    /// type for extern "Rust" functions.
    func testReflectVecOfTransparentEnum() throws {
    let vec: RustVec<TransparentEnumInsideVecT> = RustVec()
    vec.push(value: TransparentEnumInsideVecT.VariantB)
    let reflected = rust_reflect_vec_transparent_enum(vec)
    XCTAssertEqual(reflected.len(), 1)
    XCTAssertEqual(reflected.get(index: 0)!, TransparentEnumInsideVecT.VariantB)
    XCTAssertEqual(reflected.pop()!, TransparentEnumInsideVecT.VariantB)
    }
  • extern "Rust" {
    fn rust_reflect_vec_transparent_enum(
    arg: Vec<TransparentEnumInsideVecT>,
    ) -> Vec<TransparentEnumInsideVecT>;
    }

Codegen Tests

Add codegen tests, similar to the ones for transparent enums

  • /// Verify that we emit Rust, Swift and C header code that allows a transparent enum be used
    /// within a Vec<T>.
    mod transparent_enum_vec_support {
    use super::*;
    fn bridge_module_tokens() -> TokenStream {
    quote! {
    mod ffi {
    enum SomeEnum {
    VariantA,
    VariantB
    }
    }
    }
    }
    fn expected_rust_tokens() -> ExpectedRustTokens {
    ExpectedRustTokens::Contains(quote! {
    const _: () = {
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$new"]
    pub extern "C" fn _new() -> *mut Vec<SomeEnum> {
    Box::into_raw(Box::new(Vec::new()))
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$drop"]
    pub extern "C" fn _drop(vec: *mut Vec<SomeEnum>) {
    let vec = unsafe { Box::from_raw(vec) };
    drop(vec)
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$len"]
    pub extern "C" fn _len(vec: *const Vec<SomeEnum>) -> usize {
    unsafe { &*vec }.len()
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$get"]
    pub extern "C" fn _get(vec: *const Vec<SomeEnum>, index: usize) -> __swift_bridge__Option_SomeEnum {
    let vec = unsafe { &*vec };
    let val = vec.get(index).map(|v| *v);
    __swift_bridge__Option_SomeEnum::from_rust_repr(val)
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$get_mut"]
    pub extern "C" fn _get_mut(vec: *mut Vec<SomeEnum>, index: usize) -> __swift_bridge__Option_SomeEnum {
    let vec = unsafe { &mut *vec };
    let val = vec.get_mut(index).map(|v| *v);
    __swift_bridge__Option_SomeEnum::from_rust_repr(val)
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$push"]
    pub extern "C" fn _push(vec: *mut Vec<SomeEnum>, val: __swift_bridge__SomeEnum) {
    unsafe { &mut *vec }.push(val.into_rust_repr())
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$pop"]
    pub extern "C" fn _pop(vec: *mut Vec<SomeEnum>) -> __swift_bridge__Option_SomeEnum {
    let vec = unsafe { &mut *vec };
    let val = vec.pop();
    __swift_bridge__Option_SomeEnum::from_rust_repr(val)
    }
    #[doc(hidden)]
    #[export_name = "__swift_bridge__$Vec_SomeEnum$as_ptr"]
    pub extern "C" fn _as_ptr(vec: *const Vec<SomeEnum>) -> *const SomeEnum {
    unsafe { & *vec }.as_ptr()
    }
    };
    })
    }
    fn expected_swift_code() -> ExpectedSwiftCode {
    ExpectedSwiftCode::ContainsAfterTrim(
    r#"
    extension SomeEnum: Vectorizable {
    public static func vecOfSelfNew() -> UnsafeMutableRawPointer {
    __swift_bridge__$Vec_SomeEnum$new()
    }
    public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {
    __swift_bridge__$Vec_SomeEnum$drop(vecPtr)
    }
    public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) {
    __swift_bridge__$Vec_SomeEnum$push(vecPtr, value.intoFfiRepr())
    }
    public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional<Self> {
    let maybeEnum = __swift_bridge__$Vec_SomeEnum$pop(vecPtr)
    return maybeEnum.intoSwiftRepr()
    }
    public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {
    let maybeEnum = __swift_bridge__$Vec_SomeEnum$get(vecPtr, index)
    return maybeEnum.intoSwiftRepr()
    }
    public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {
    let maybeEnum = __swift_bridge__$Vec_SomeEnum$get_mut(vecPtr, index)
    return maybeEnum.intoSwiftRepr()
    }
    public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {
    __swift_bridge__$Vec_SomeEnum$len(vecPtr)
    }
    }
    "#,
    )
    }
    fn expected_c_header() -> ExpectedCHeader {
    ExpectedCHeader::ContainsAfterTrim(
    r#"
    void* __swift_bridge__$Vec_SomeEnum$new(void);
    void __swift_bridge__$Vec_SomeEnum$drop(void* vec_ptr);
    void __swift_bridge__$Vec_SomeEnum$push(void* vec_ptr, __swift_bridge__$SomeEnum item);
    __swift_bridge__$Option$SomeEnum __swift_bridge__$Vec_SomeEnum$pop(void* vec_ptr);
    __swift_bridge__$Option$SomeEnum __swift_bridge__$Vec_SomeEnum$get(void* vec_ptr, uintptr_t index);
    __swift_bridge__$Option$SomeEnum __swift_bridge__$Vec_SomeEnum$get_mut(void* vec_ptr, uintptr_t index);
    uintptr_t __swift_bridge__$Vec_SomeEnum$len(void* vec_ptr);
    void* __swift_bridge__$Vec_SomeEnum$as_ptr(void* vec_ptr);
    "#,
    )
    }
    #[test]
    fn transparent_enum_vec_support() {
    CodegenTest {
    bridge_module: bridge_module_tokens().into(),
    expected_rust_tokens: expected_rust_tokens(),
    expected_swift_code: expected_swift_code(),
    expected_c_header: expected_c_header(),
    }
    .test();
    }
    }
  • /// Test code generation for Rust function that returns a Vec<T> where T is a transparent enum.
    mod extern_rust_fn_return_vec_of_transparent_enum {
    use super::*;
    fn bridge_module_tokens() -> TokenStream {
    quote! {
    mod ffi {
    enum SomeEnum {
    A
    }
    extern "Rust" {
    fn some_function() -> Vec<SomeEnum>;
    }
    }
    }
    }
    fn expected_rust_tokens() -> ExpectedRustTokens {
    ExpectedRustTokens::Contains(quote! {
    pub extern "C" fn __swift_bridge__some_function() -> *mut Vec<SomeEnum> {
    Box::into_raw(Box::new(super::some_function()))
    }
    })
    }
    fn expected_swift_code() -> ExpectedSwiftCode {
    ExpectedSwiftCode::ContainsAfterTrim(
    r#"
    func some_function() -> RustVec<SomeEnum> {
    RustVec(ptr: __swift_bridge__$some_function())
    }
    "#,
    )
    }
    fn expected_c_header() -> ExpectedCHeader {
    ExpectedCHeader::ContainsAfterTrim(
    r#"
    void* __swift_bridge__$some_function(void);
    "#,
    )
    }
    #[test]
    fn extern_rust_fn_return_vec_of_transparent_enum() {
    CodegenTest {
    bridge_module: bridge_module_tokens().into(),
    expected_rust_tokens: expected_rust_tokens(),
    expected_swift_code: expected_swift_code(),
    expected_c_header: expected_c_header(),
    }
    .test();
    }
    }
  • /// Test code generation for Rust function that has an argument
    /// Vec<T> where T is a transparent enum.
    mod extern_rust_fn_arg_vec_of_transparent_enum {
    use super::*;
    fn bridge_module_tokens() -> TokenStream {
    quote! {
    mod ffi {
    enum SomeEnum {
    A
    }
    extern "Rust" {
    type MyRustType;
    fn some_function(arg: Vec<SomeEnum>);
    }
    }
    }
    }
    fn expected_rust_tokens() -> ExpectedRustTokens {
    ExpectedRustTokens::Contains(quote! {
    pub extern "C" fn __swift_bridge__some_function(
    arg: *mut Vec<SomeEnum>
    ) {
    super::some_function(unsafe { * Box::from_raw(arg) })
    }
    })
    }
    fn expected_swift_code() -> ExpectedSwiftCode {
    ExpectedSwiftCode::ContainsAfterTrim(
    r#"
    func some_function(_ arg: RustVec<SomeEnum>) {
    __swift_bridge__$some_function({ let val = arg; val.isOwned = false; return val.ptr }())
    }
    "#,
    )
    }
    fn expected_c_header() -> ExpectedCHeader {
    ExpectedCHeader::ContainsAfterTrim(
    r#"
    void __swift_bridge__$some_function(void* arg);
    "#,
    )
    }
    #[test]
    fn extern_rust_fn_arg_vec_of_transparent_enum() {
    CodegenTest {
    bridge_module: bridge_module_tokens().into(),
    expected_rust_tokens: expected_rust_tokens(),
    expected_swift_code: expected_swift_code(),
    expected_c_header: expected_c_header(),
    }
    .test();
    }
    }

Vec support tests

  • Add a mod vec_of_transparent_struct

  • Model it after the mod vec_of_transparent_enum

  • Same idea with the Swift impl

    • extension {enum_name}: Vectorizable {{
      public static func vecOfSelfNew() -> UnsafeMutableRawPointer {{
      __swift_bridge__$Vec_{enum_name}$new()
      }}
      public static func vecOfSelfFree(vecPtr: UnsafeMutableRawPointer) {{
      __swift_bridge__$Vec_{enum_name}$drop(vecPtr)
      }}
      public static func vecOfSelfPush(vecPtr: UnsafeMutableRawPointer, value: Self) {{
      __swift_bridge__$Vec_{enum_name}$push(vecPtr, value.intoFfiRepr())
      }}
      public static func vecOfSelfPop(vecPtr: UnsafeMutableRawPointer) -> Optional<Self> {{
      let maybeEnum = __swift_bridge__$Vec_{enum_name}$pop(vecPtr)
      return maybeEnum.intoSwiftRepr()
      }}
      public static func vecOfSelfGet(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {{
      let maybeEnum = __swift_bridge__$Vec_{enum_name}$get(vecPtr, index)
      return maybeEnum.intoSwiftRepr()
      }}
      public static func vecOfSelfGetMut(vecPtr: UnsafeMutableRawPointer, index: UInt) -> Optional<Self> {{
      let maybeEnum = __swift_bridge__$Vec_{enum_name}$get_mut(vecPtr, index)
      return maybeEnum.intoSwiftRepr()
      }}
      public static func vecOfSelfLen(vecPtr: UnsafeMutableRawPointer) -> UInt {{
      __swift_bridge__$Vec_{enum_name}$len(vecPtr)
      }}
      }}"#

Getting everything working

You should be able to run the tests and tweak code in https://github.com/chinedufn/swift-bridge/blob/2bffcb08c1f0ca8bbcba9c78c5ae28a9cddfb632/crates/swift-bridge-ir/src/bridged_type.rs until the tests pass

@chinedufn chinedufn added the good first issue Good for newcomers label Dec 24, 2022
@antoniusnaumann
Copy link
Contributor

If there is currently nobody working on this, I would like to try to implement this (probably next week).

@chinedufn
Copy link
Owner

All yours!
Please don’t hesitate to ask questions here if you have any.

@antoniusnaumann
Copy link
Contributor

All yours!

Please don’t hesitate to ask questions here if you have any.

Unfortunately I got some project deadlines the next weeks that require more work than expected, so I am not able to start working on this any time soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

4 participants