Safety
swift-bridge
is fully type safe and mostly memory safe.
Type Safety
All of the Rust and Swift FFI code that swift-bridge
generates
for you is type safe.
Memory Safety
You can ensure the memory safety of your Rust and Swift projects by following these rules:
-
In Swift, never use a reference to a Rust type after its lifetime.
-
In Swift, pass a mutable reference to Rust that will live alongside another active reference to that type.
-
In Swift, never use a type after passing ownership to Rust.
Let's look at some examples of code that violates these rules:
Never use a reference after its lifetime
It is possible to pass a reference from Rust
-> Swift
and then have Swift
make use of that
reference after it is no longer safe to do so.
#![allow(unused)] fn main() { #[swift_bridge::bridge] mod ffi { extern "Rust" { type SomeType; #[swift_bridge(init)] fn new(); fn name(&str) -> &str; fn drop(self); } } }
// Swift
let someType = SomeType()
let name: RustStr = someType.name()
someType.drop()
// Undefined behavior since `SomeType` was dropped.
name.toString()
It isn't possible for swift-bridge
to mitigate this, so be mindful when handling references.
Never pass a mutable reference to Rust that will live alongside another active reference
Rust expects that if there is mutable reference to a value, no other references to that value are held.
This rule is not enforced on the Swift side, making it possible to pass aliasing pointers to Rust and trigger undefined behavior.
#![allow(unused)] fn main() { // Rust #[swift_bridge::bridge] mod ffi { extern "Rust" { type MyList; #[swift_bridge(init)] fn new() -> MyList; fn extend(a: &mut self, b: &MyList); } } }
// Swift
let myList = MyList()
// This can cause undefined behavior!
myList.extend(myList)
#![allow(unused)] fn main() { // Rust #[swift_bridge::bridge] mod ffi { extern "Rust" { type MyList; fn new() -> MyList; fn allegedly_immutable(&self, callback: Box<dyn Fn()>); fn mutate(&mut self); } } }
// Swift
let myList = MyList()
myList.allegedly_immutable({
// If the `allegedly_immutable` method calls this
// callback we will take a mutable reference to `MyList`
// while there is an active immutable reference.
// This violates Rust's borrowing rules.
myList.mutate()
})
To stay safe, when passing a mutable reference to a Rust value from Swift to Rust do not pass any other references to that same value.
Never use a value after it is dropped
Today, it is possible to pass ownership of a value from Swift
to Rust
and then unsafely access the value from Swift
.
#![allow(unused)] fn main() { #[swift_bridge::bridge] mod ffi { extern "Rust" { type MyOwnedType; fn drop(ty: MyOwnedType); } } }
let myOwnedType = MyOwnedType()
drop(myOwnedType)
// Undefined behavior since we no longer own this value.
drop(myOwnedType)
After Swift introduces the consume operator we will be able to prevent this issue by enforcing ownership at compile time.