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

Is &Vec<T> supported? #261

Open
PrismaPhonic opened this issue Mar 15, 2024 · 2 comments
Open

Is &Vec<T> supported? #261

PrismaPhonic opened this issue Mar 15, 2024 · 2 comments

Comments

@PrismaPhonic
Copy link
Contributor

PrismaPhonic commented Mar 15, 2024

I can't seem to get &Vec<T> to work - I looked through the swift rust bridge book at the type support table but don't see that one listed. Is it supposed to be supported yet?

@chinedufn
Copy link
Owner

chinedufn commented Mar 15, 2024

No.

A temporary solution if you can afford a clone:

use std::clone::Clone;

#[swift_bridge::bridge]
mod ffi {
    #[swift_bridge(return_with = Clone::clone)]
    fn get_vec() -> Vec<String>;
}

fn get_vec() -> &Vec<String> {
    unimplemented!()
}

If you or a future person cannot use that workaround and need &Vec<T> support I would be happy to write up step by step hand-held instructions on how to add it.
In the meantime that temporary workaround may help for non performance sensitive cases.




I'll leave a high-level summary of how to support it in case you or some future person ever needs &Vec<T> and cannot use the temporary workaround.
If anyone sees this comment and wants more fine-grained hand-held instructions just let me know and I'll write them up.

  • look over the contributing docs https://github.com/chinedufn/swift-bridge/blob/master/book/src/contributing/adding-support-for-a-signature/README.md

  • adding a RustVecRef<T: Vectorizable> and RustVecRefMut<T: Vectorizable> here

    public class RustVec<T: Vectorizable> {
    var ptr: UnsafeMutableRawPointer
    var isOwned: Bool = true
    public init(ptr: UnsafeMutableRawPointer) {
    self.ptr = ptr
    }
    public init() {
    ptr = T.vecOfSelfNew()
    isOwned = true
    }
    public func push (value: T) {
    T.vecOfSelfPush(vecPtr: ptr, value: value)
    }
    public func pop () -> Optional<T> {
    T.vecOfSelfPop(vecPtr: ptr)
    }
    public func get(index: UInt) -> Optional<T.SelfRef> {
    T.vecOfSelfGet(vecPtr: ptr, index: index)
    }
    public func as_ptr() -> UnsafePointer<T.SelfRef> {
    UnsafePointer<T.SelfRef>(OpaquePointer(T.vecOfSelfAsPtr(vecPtr: ptr)))
    }
    /// Rust returns a UInt, but we cast to an Int because many Swift APIs such as
    /// `ForEach(0..rustVec.len())` expect Int.
    public func len() -> Int {
    Int(T.vecOfSelfLen(vecPtr: ptr))
    }
    deinit {
    if isOwned {
    T.vecOfSelfFree(vecPtr: ptr)
    }
    }
    }

  • making RustVec<T: Vectorizable> inherit from RustVecRefMut which inherits from RustVecRef, similar to this

    ```swift
    // Equivalent to `SomeType` in Rust
    class SomeType: SomeTypeRefMut {
    // ...
    }
    // Equivalent to `&mut SomeType` in Rust
    class SomeTypeRefMut: SomeTypeRef {
    // ...
    }
    // Equivalent to `&SomeType` in Rust
    class SomeTypeRef {
    // ...
    }
    ```
    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
    }
    }

  • Add codegen tests for passing &Vec<T> as an arg and as a return value, similar to these two

    /// Test code generation for Rust function that returns a Vec<T> where T is an opaque Rust type.
    mod extern_rust_fn_return_vec_of_opaque_rust_type {
    use super::*;
    fn bridge_module_tokens() -> TokenStream {
    quote! {
    mod ffi {
    extern "Rust" {
    type MyRustType;
    fn some_function() -> Vec<MyRustType>;
    }
    }
    }
    }
    fn expected_rust_tokens() -> ExpectedRustTokens {
    ExpectedRustTokens::Contains(quote! {
    pub extern "C" fn __swift_bridge__some_function() -> *mut Vec<super::MyRustType> {
    Box::into_raw(Box::new(super::some_function()))
    }
    })
    }
    fn expected_swift_code() -> ExpectedSwiftCode {
    ExpectedSwiftCode::ContainsAfterTrim(
    r#"
    func some_function() -> RustVec<MyRustType> {
    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_opaque_rust_type() {
    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 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>);
    }
    }
    }
    }
    fn expected_rust_tokens() -> ExpectedRustTokens {
    ExpectedRustTokens::Contains(quote! {
    pub extern "C" fn __swift_bridge__some_function(
    arg: *mut Vec<super::MyRustType>
    ) {
    super::some_function(unsafe { * Box::from_raw(arg) })
    }
    })
    }
    fn expected_swift_code() -> ExpectedSwiftCode {
    ExpectedSwiftCode::ContainsAfterTrim(
    r#"
    func some_function(_ arg: RustVec<MyRustType>) {
    __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_opaque_rust_type() {
    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();
    }
    }

  • Add an integration test similar to the one for Vec<T>

    /// 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")
    }

  • Get the tests passing by tweaking the codegen

    /// Represents a type that can be passed between Rust and Swift.
    https://github.com/chinedufn/swift-bridge/tree/53b118d17f2f1a2922e969de528b99a2ffbc7dde/crates/swift-bridge-ir/src/codegen

@PrismaPhonic
Copy link
Contributor Author

Thank you for the thoughtful reply. I'm pretty busy at the moment so will work around it - but if I get time in the future and it's not done yet I'll try to tackle it

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

No branches or pull requests

2 participants