Opaque Types
This chapter explains how to expose opaque handles to Swift classes and Rust structs.
Exposing Opaque Rust Types
extern "Rust
sections are used to expose Rust types and their associated methods and functions
so that they can be used from Swift code.
#![allow(unused)] fn main() { mod science; use science::{ScienceLab, Hydrogen, Oxygen, make_water}; #[swift_bridge::bridge] mod ffi { extern "Rust" { type Water; #[swift_bridge(associated_to = Water)] fn new() -> Water; fn is_wet(&self) -> bool; } extern "Rust" { type ScienceLab; type Hydrogen; type Oxygen; fn make_water( lab: &ScienceLab, hydrogen: Hydrogen, oxygen: Oxygen ) -> Water; } } pub struct Water; impl Water { fn new () -> Self { Water } fn is_wet(&self) -> bool { unreachable!("Seriously...?") } } }
Owned, Ref and RefMut
When you define a type in an extern "Rust"
block, three corresponding Swift classes get generated.
// Equivalent to `SomeType` in Rust
class SomeType: SomeTypeRefMut {
// ...
}
// Equivalent to `&mut SomeType` in Rust
class SomeTypeRefMut: SomeTypeRef {
// ...
}
// Equivalent to `&SomeType` in Rust
class SomeTypeRef {
// ...
}
Here's an example of how &Type
and &mut Type
are enforced:
#![allow(unused)] fn main() { // Rust extern "Rust" { type SomeType; #[swift_bridge(init)] fn new() -> SomeType; // Callable by SomeType, SomeTypeRef and SomeTypeRefMut. fn (&self) everyone(); // Callable by SomeType, and SomeTypeRefMut. fn (&mut self) only_owned_and_ref_mut(); // Only callable by SomeType. fn (self) only_owned(); } extern "Rust" { fn make_ref() -> &'static SomeType; fn make_ref_mut() -> &'static mut SomeType; } }
// Swift
func methods() {
let someType: SomeType = SomeType()
let someTypeRef: SomeTypeRef = make_ref()
let someTypeRefMut: SomeTypeRefMut = make_ref_mut()
someType.everyone()
someType.only_owned_and_ref_mut()
someType.only_owned()
someTypeRefMut.everyone()
someTypeRefMut.only_owned_and_ref_mut()
someTypeRef.everyone()
}
func functions() {
let someType: SomeType = SomeType()
let someTypeRef: SomeTypeRef = make_ref()
let someTypeRefMut: SomeTypeRefMut = make_ref_mut()
takeReference(someType)
takeReference(someTypeRef)
takeReference(someTypeRefMut)
}
// Can be called with SomeType, SomeTypeRef and SomeTypeRefMut
func useSomeType(someType: SomeTypeRef) {
// ...
}
Opaque Type Attributes
#[swift_bridge(already_declared)]
The already_declared
attribute allows you to use the same type in multiple bridge modules.
#![allow(unused)] fn main() { use some_crate::App; mod ffi { extern "Rust" { type App; #[swift_bridge(init)] fn new() -> App; } } #[swift_bridge::bridge] #[cfg(feature = "dev-utils")] mod ffi_dev_utils { extern "Rust" { // We won't emit Swift and C type definitions for this type // since we've already declared it elsewhere. #[swift_bridge(already_declared)] type App; fn create_logged_in_user(&mut self, user_id: u8); } } }
#[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:
#![allow(unused)] fn main() { #[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.
#[swift_bridge(Equatable)]
The Equatable
attribute allows you to expose a Rust PartialEq
implementation via Swift's
Equatable
protocol.
#![allow(unused)] fn main() { #[swift_bridge::bridge] mod ffi { extern "Rust" { #[swift_bridge(Equatable)] type RustPartialEqType; } } #[derive(PartialEq)] struct RustPartialEqType(u32); }
// In Swift
let val1 = RustPartialEqType(5)
let val2 = RustPartialEqType(10)
if val1 == val2 {
print("Equal")
} else {
print("Not equal")
}
#[swift_bridge(Hashable)]
The Hashable
attribute allows you to expose a Rust Hash
trait implementation via Swift's
Hashable
protocol.
#![allow(unused)] fn main() { #[swift_bridge::bridge] mod ffi { extern "Rust" { #[swift_bridge(Hashable)] type RustHashType; } } #[derive(Hash, PartialEq)] struct RustHashType(u32); }
// In Swift
let val = RustHashType(10);
let table: [RustHashType: String] = [:]
table[val] = "hello"
table[val] = "world"
//Should print "world"
print(table[val])
#[swift_bridge(Sendable)]
The Sendable
attribute can be added to both opaque Rust and opaque Swift types.
When applied to an opaque Rust type, the generated Swift type will implement Swift's Sendable
protocol.
swift-bridge
will emit code that, at compile time, confirms that the Rust type implements Send + Sync
.
When applied to an opaque Swift type, the generated Rust type will implement Rust's Send + Sync
traits.
swift-bridge
will emit code that, at compile time, confirms that the Swift type implements Sendable
.
#![allow(unused)] fn main() { #[swift_bridge::bridge] mod ffi { extern "Rust" { #[swift_bridge(Sendable)] type MyRustType; } extern "Swift" { #[swift_bridge(Sendable)] type MySwiftType; } } }
#[swift_bridge(__experimental_ownership)]
The __experimental_ownership
attribute instructs swift-bridge
to emit code that takes advantage of Swift 6's
ownership features.
Once swift-bridge
's support for Swift's ownership features stabilizes, this attribute will be removed and the behavior
that it enabled will become the default.
When swift-bridge
's Swift ownership support is complete, the following will be supported:
- use Swift's
~Copyable
extension to:- guarantee at compile time that Swift code cannot use a Rust type that it no longer owns
- prevent Swift from automatically copying mutable references to Rust types
Note that support for this attribute is a work in progress.
Work is tracked in Enforce ownership in generated Swift code
https://github.com/chinedufn/swift-bridge/issues/155 .
#![allow(unused)] fn main() { #[swift_bridge::bridge] mod foo { extern "Rust" { #[swift_bridge(__experimental_swift_ownership)] type SomeType; } } }