Skip to content

Commit

Permalink
Add support for opaque Copy types
Browse files Browse the repository at this point in the history
This commit introduces a `#[swift_bridge(Copy(...))]` attribute that can
be used to indicate that an opaque Rust type should be passed across the
FFI boundary via a copy instead of by boxing it.

For example, the following is now possible:

```rust
mod ffi {
    extern "Rust" {
        #[swift_bridge(Copy(16))]
        type UserId;

        #[swift_bridge(Copy(16))]
        type OrganizationId;
    }
}

use uuid:Uuid;

\#[derive(Copy)]
struct UserId(Uuid);

\#[derive(Copy)]
struct OrganizationId(Uuid);
```
  • Loading branch information
chinedufn committed Apr 24, 2022
1 parent f2e8586 commit e42b73d
Show file tree
Hide file tree
Showing 38 changed files with 2,062 additions and 890 deletions.
12 changes: 9 additions & 3 deletions README.md
Expand Up @@ -41,7 +41,7 @@ mod ffi {

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

Expand All @@ -52,15 +52,18 @@ mod ffi {
#[swift_bridge(init)]
fn new(config: AppConfig);

fn insert_user(&mut self, user_id: u32, user: User);
fn insert_user(&mut self, user_id: UserId, user: User);
fn get_user(&self, lookup: UserLookup) -> Option<&User>;
}

extern "Rust" {
type User;

#[swift_bridge(Copy(4))]
type UserId;

#[swift_bridge(init)]
fn new(user_id: u32, name: String, email: Option<String>) -> User;
fn new(user_id: UserId, name: String, email: Option<String>) -> User;
}

// Import Swift classes and functions for Rust to use.
Expand All @@ -69,6 +72,9 @@ mod ffi {
fn save_file(&self, name: &str, contents: &[u8]);
}
}

#[derive(Copy)]
struct UserId(u32);
```
<!-- ANCHOR_END: bridge-module-example -->

Expand Down
Expand Up @@ -25,6 +25,7 @@ class FunctionAttributeIdentifiableTests: XCTestCase {
func testIdentifiable() throws {
XCTAssertEqual(verifyIsIdentifiable(IdentifiableFnNamedId()).id(), 123)
XCTAssertEqual(IdentifiableFnNotNamedId().id, 123)
XCTAssertEqual(OpaqueCopyTypeIdentifiable().id(), 123)

XCTAssertEqual(verifyIsIdentifiable(IdentifiableU8()).id(), 123)
XCTAssertEqual(verifyIsIdentifiable(IdentifiableI8()).id(), 123)
Expand Down
Expand Up @@ -56,5 +56,17 @@ class OpaqueRustStructTests: XCTestCase {
XCTAssertEqual(ref1.len(), 2)
XCTAssertEqual(ref1.len(), ref2.len())
}

/// Verify that we can pass a Copy opaque Rust type between Rust and Swift.
func testOpaqueRustTypeImplCopy() throws {
let val = RustCopyType()
let val2 = RustCopyType()

// Because `val` is copy we can still use it after calling
// a method that takes an owned `self` .
val.consume()

XCTAssert(val.eq(val2))
}
}

Expand Up @@ -23,7 +23,7 @@ class OpaqueTypeAttributeTests: XCTestCase {
/// where the opaque Rust type was defined.
/// This ensures that our code generation properly generates Swift convenience initializers inside of class extensions.
/// See crates/swift-integration-tests/src/type_attributes/already_declared.rs
func testExternRustAlreadyDeclaredCallInitializer() throws {
func testExternRustAlreadyDeclaredOpaqueRustType() throws {
let val = AlreadyDeclaredTypeTest()

XCTAssert(val.a_ref_method())
Expand All @@ -32,7 +32,17 @@ class OpaqueTypeAttributeTests: XCTestCase {

XCTAssert(AlreadyDeclaredTypeTest.an_associated_function())
}

func testExternRustAlreadyDeclaredCopyOpaqueRustTypeType() throws {
let val = AlreadyDeclaredCopyTypeTest()

XCTAssert(val.a_ref_method())
XCTAssert(val.an_owned_method())

XCTAssert(AlreadyDeclaredCopyTypeTest.an_associated_function())
}


func testPerformanceExample() throws {
// This is an example of a performance test case.
self.measure {
Expand Down
2 changes: 2 additions & 0 deletions book/src/SUMMARY.md
Expand Up @@ -13,6 +13,8 @@
- [Functions](./bridge-module/functions/README.md)
- [Opaque Types](./bridge-module/opaque-types/README.md)
- [Transparent Types](./bridge-module/transparent-types/README.md)
- [Transparent Structs](./bridge-module/transparent-types/structs/README.md)
- [Transparent Enums](./bridge-module/transparent-types/enums/README.md)
- [Async Functions](./bridge-module/async-functions/README.md)
- [Conditional Compilation](./bridge-module/conditional-compilation/README.md)

Expand Down
35 changes: 35 additions & 0 deletions book/src/bridge-module/opaque-types/README.md
Expand Up @@ -165,4 +165,39 @@ mod ffi_dev_utils {
}
```

#### #[swift_bridge(Copy($SIZE))]

If you have an opaque Rust type that implements `Copy`, you will typically want to be
able to pass it between Swift and Rust by copying the bytes instead of allocating.

For example, let's say you have some new type wrappers for different kinds of IDs
within your system.

```
use uuid:Uuid;
#[derive(Copy)]
struct UserId(Uuid);
#[derive(Copy)]
struct OrganizationId(Uuid);
```

You can expose them using:

```rust
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(Copy(16))]
type UserId;

#[swift_bridge(Copy(16))]
type OrganizationId;
}
}
```

The `16` indicates that a `UserId` has 16 bytes.

`swift-bridge` will add a compile time assertion that confirms that the given size is correct.
198 changes: 2 additions & 196 deletions book/src/bridge-module/transparent-types/README.md
@@ -1,198 +1,4 @@
# Transparent Types

... TODO: OVERVIEW ...

## Transparent Structs

You can define structs whos fields can be accessed by both Rust and Swift.

```rust
// Rust

#[swift_bridge::bridge]
mod ffi {
#[swift_bridge::bridge(swift_repr = "struct")]
struct SomeSharedStruct {
some_field: u8,
another_field: Option<u64>
}

extern "Rust" {
fn some_function(val: SomeSharedStruct);
}

extern "Swift" {
fn another_function() -> SomeSharedStruct;
}
}

fn some_function (val: ffi::SomeSharedStruct) {
// ...
}
```

```swift
// Swift

func another_function() -> SomeSharedStruct {
return SomeSharedStruct(some_field: 123, another_field: nil)
}
```

### Struct Attributes

#### #[swift_bridge::bridge(already_declared)]

```rust
#[swift_bridge::bridge]
mod ffi_1 {
#[swift_bridge(swift_repr = "struct")]
struct SomeSharedStruct {
some_field: u8
}
}

use ffi_1::SomeSharedStruct;

#[swift_bridge::bridge]
mod ffi_2 {
// The `already_declared` indicates that instead of creating a new Struct
// we should use super::SomeSharedStruct;
#[swift_bridge(already_declared, swift_repr = "struct")]
struct SomeSharedStruct;

extern "Rust" {
fn some_function() -> SomeSharedStruct;
}
}
```

#### #[swift_bridge::bridge(swift_repr = "...")]

_Valid values are "struct" or "class"._

How the struct should appear on the Swift side.

Swift structs are copy-on-write, so we do not allow you to mutate the fields of a `swift_repr = "struct"`
since you wouldn't be changing the original struct.

The `swift_repr ="class"` representation allows you to pass mutable references to shared structs between Rust and Swift.

```rust
// Rust

#[swift_bridge::bridge]
mod ffi {
#[swift_bridge(struct_repr = "struct")]
struct SomeStructReprStruct {
field: UInt8,
}

#[swift_bridge(struct_repr = "class")]
struct SomeStructReprClass {
field: UInt8,
}

// NOTE: This is aspirational. Exposing methods on shared structs
// doesn't actually work yet.
extern "Rust" {
// All structs can expose `&self` methods.
fn repr_struct_ref(self: &SomeStructReprStruct);
fn repr_class_ref(self: &SomeStructReprStruct);

// Only structs with `swift_repr = "class"` can expose `&self` methods.
fn repr_class_ref_mut(self: &mut SomeStructReprStruct);
}
}

impl ffi::SomeStructReprStruct {
fn repr_struct_ref(&self) {
// ...
}

// swift-bridge cannot expose this method since mutable methods
// on `swift_repr = "struct"` structs are not supported.
fn repr_struct_ref_mut(&mut self) {
// ...
}
}

impl ffi::SomeStructReprClass {
fn repr_class_ref(&self) {
// ...
}

fn repr_class_ref_mut(&mut self) {
// ...
}
}
```

```swift
// Generated Swift

struct SomeStructReprStruct {
var field: UInt8

func repr_struct_ref() {
// ... call Rust's SomeStructReprStruct.repr_struct_ref() ...
}
}

class SomeStructReprClass: SomeStructReprClassRefMut {
// ...
}
class SomeStructReprClassRefMut: SomeStructReprClassRef {
// ...
}
class SomeStructReprClassRef {
// ...
}
```


## Transparent Enums

You can define enums that can be created by both Rust and Swift.

```rust
// Rust

#[swift_bridge::bridge]
mod ffi {
enum BarCode {
Upc(i32, i32, i32, i32),
QrCode {
code: String
}
}

extern "Rust" {
fn get_link(code: BarCode) -> String;
}

extern "Swift" {
fn create_bar_code(upc: bool) -> BarCode;
}
}

fn get_link (code: BarCode) -> String {
// ...
}
```

```swift
// Swift

func create_bar_code(upc: Bool) -> BarCode {
if upc {
return BarCode.Upc(8, 85909, 51226, 3)
} else {
return BarCode.QrCode(code: "ABCDEFG")
}
}
```

### Enum Attributes

... TODO
`swift-bridge` supports defining structs and enums who's fields can be
seen by both Swift and Rust.
45 changes: 45 additions & 0 deletions book/src/bridge-module/transparent-types/enums/README.md
@@ -0,0 +1,45 @@
# Transparent Enums

You can define enums that can be created by both Rust and Swift.

```rust
// Rust

#[swift_bridge::bridge]
mod ffi {
enum BarCode {
Upc(i32, i32, i32, i32),
QrCode {
code: String
}
}

extern "Rust" {
fn get_link(code: BarCode) -> String;
}

extern "Swift" {
fn create_bar_code(upc: bool) -> BarCode;
}
}

fn get_link (code: BarCode) -> String {
// ...
}
```

```swift
// Swift

func create_bar_code(upc: Bool) -> BarCode {
if upc {
return BarCode.Upc(8, 85909, 51226, 3)
} else {
return BarCode.QrCode(code: "ABCDEFG")
}
}
```

### Enum Attributes

... TODO

0 comments on commit e42b73d

Please sign in to comment.