-
Notifications
You must be signed in to change notification settings - Fork 65
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
Enforce ownership in generated Swift code #155
Comments
Swift 5.9 introduces some new ownership features. I haven't thought through what we can do with all of them yet. Linking to some resources here:
Some Early Thoughts
|
I think a good approach would be to introduce Swift compile-time enforced ownership support behind an attribute in the So, something like this would lead our code generators to emit Swift code that enforces ownership at compile time: #[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(__unstable__swift_compile_time_ownership)]
type Computer
fn eat(&self, meal: Food);
fn shutdown(self);
}
} Then after things stabilize we'd remove the One approach would be to start by just diving in and writing some tests and implementations for some basic cases such as bridging a Rust struct that has a single primitive field, then using what we learn to get a better sense of the extent of the work that is needed and how to best approach it. |
What is bridging a Rust struct? You mean opaque types? |
Yeah I meant an opaque Rust type: #[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(__unstable__swift_compile_time_ownership)]
type Computer
}
}
struct Computer {
field: u8
} |
Swift recently accepted the "Noncopyable generics" proposal https://github.com/swiftlang/swift-evolution/blob/main/proposals/0427-noncopyable-generics.md Once that lands we should have everything that we need to implement cross-language ownership. |
Here's what I'm thinking in pseudocode. // Rust
#[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(__unstable__swift_compile_time_ownership)]
type Counter;
fn get(&self);
fn increment(&mut self);
fn take(self) -> u32;
fn give_me_counter_owned(counter: Counter);
fn give_me_counter_ref(counter: &Counter);
fn give_me_counter_ref_mut(counter: &mut Counter);
}
}
struct Counter {
count: u32
} // Generated Swift code (ROUGH SKETCH)
// NEW: The owned type is `~Copyable`
struct Counter: ~Copyable {
ptr: UnsafeMutableRawPointer
consuming func take() {
__swift_bridge__$Counter$take(ptr)
}
}
struct CounterRefMut: ~Copyable {
ptr: UnsafeMutableRawPointer
}
struct CounterRef {
ptr: UnsafeMutableRawPointer
}
protocol __swift_bridge__CounterRef {
var ptr: UnsafeMutableRawPointer
func get() -> UInt32 {
__swift_bridge__$Counter$get(self.ptr)
}
}
protocol __swift_bridge__CounterRefMut {
var ptr: UnsafeMutableRawPointer
func increment() {
__swift_bridge__$Counter$increment(self.ptr)
}
}
extension CounterRef: __swift_bridge__CounterRef {}
extension CounterRefMut: __swift_bridge__CounterRef {}
extension Counter: __swift_bridge__CounterRef {}
extension Counter: __swift_bridge__CounterRefMut {}
func give_me_counter_owned(consuming counter: Counter) {
__swift_bridge__give_me_counter_owned(counter.ptr)
}
func give_me_counter_ref(borrowing counter: __swift_bridge__CounterRef) {
__swift_bridge__give_me_counter_ref(counter.ptr)
}
func give_me_counter_ref_mut(borrowing counter: __swift_bridge__CounterRefMut) {
__swift_bridge__give_me_counter_ref_mut(counter.ptr)
} // Using the Counter from Swift
func foo() {
let counter = Counter();
// 0
counter.get()
counter.increment()
// 1
counter.get()
// Ok, takes a reference
give_me_counter_ref(counter)
// Ok, takes a mutable reference
give_me_counter_ref_mut(counter)
// `counter` ownership has now been transferred to Rust
counter.take()
// COMPILE TIME ERROR
give_me_counter_owned(counter)
} I'm planning to experiment with a design like this using Swift (without Then if that works I can land something in Not sure when I'll work on this though. Could be months from now. |
Alright I have a working demo of how we can support ownership. I downloaded the Then I ran the following script: #!/bin/bash
# Downloaded a Swift main branch build from:
# https://www.swift.org/download/
# Found out about this `--toolchain` flag here:
# https://www.swift.org/install/macos/package_installer/
xcrun --toolchain swift swiftc \
experiment.swift
./experiment With the following struct Counter: ~Copyable {
var ptr: UInt
consuming func take() {
// __swift_bridge__$Counter$take(ptr)
}
}
struct CounterRefMut: ~Copyable {
var ptr: UInt
}
struct CounterRef {
var ptr: UInt
}
protocol __swift_bridge__CounterRef: ~Copyable {
var ptr: UInt { get }
}
extension __swift_bridge__CounterRef where Self: ~Copyable {
borrowing func get() -> UInt32 {
// __swift_bridge__$Counter$get(self.ptr)
return 12345
}
}
protocol __swift_bridge__CounterRefMut: ~Copyable {
var ptr: UInt { get }
}
extension __swift_bridge__CounterRefMut where Self: ~Copyable {
borrowing func increment() {
// __swift_bridge__$Counter$increment(self.ptr)
}
}
extension CounterRef: __swift_bridge__CounterRef {}
extension CounterRefMut: __swift_bridge__CounterRef {}
extension CounterRefMut: __swift_bridge__CounterRefMut {}
extension Counter: __swift_bridge__CounterRef {}
extension Counter: __swift_bridge__CounterRefMut {}
func give_me_counter_owned(_ counter: consuming Counter) {
// __swift_bridge__give_me_counter_owned(counter.ptr)
}
func give_me_counter_ref<T: __swift_bridge__CounterRef & ~Copyable>(_ counter: borrowing T) {
// __swift_bridge__give_me_counter_ref(counter.ptr)
}
func give_me_counter_ref_mut<T: __swift_bridge__CounterRefMut & ~Copyable>(_ counter: borrowing T) {
// __swift_bridge__give_me_counter_ref_mut(counter.ptr)
}
func foo() {
let counter = Counter(ptr: 5);
// 0
_ = counter.get()
counter.increment()
// 1
_ = counter.get()
// Ok, takes a reference
give_me_counter_ref(counter)
// Ok, takes a mutable reference
give_me_counter_ref_mut(counter)
// `counter` ownership has now been transferred to Rust
counter.take()
// COMPILE TIME ERROR
give_me_counter_owned(consume counter)
}
foo() And I get the following error:
|
@chinedufn |
I'm planning to work on this when Swift 6 is released. Looks like there's already a 6.0 release branch, so hopefully we aren't too far away from Swift 6. I want to feel out what's needed to seamlessly transition users between the old no-ownership codegen to the new codegen that will have ownership. After I do enough work to figure some of that stuff out we can split up the remaining work. |
Swift 6 has been released https://www.swift.org/blog/announcing-swift-6/ |
Hmmm, You can't implement In my own codebase I only have 2 functions that attempt to return an At the very least it looks like if we supported generated Perhaps something like (very rough sketch): #[swift_bridge::bridge]
mod ffi {
extern "Rust" {
#[swift_bridge(swift_repr = noncopyable)]
type Foo;
#[swift_bridge(swift_repr = class)]
type Bar;
}
} So, the user would control whether the Rust type got emitted as a Swift |
As I outlined in #309 (comment), it's okay for those types to be |
Hey quick update: Haven't ready any of your recent comments yet I'm mostly off the grid for another week and a half then I'll get caught up so that we can unblock your Linux test suite work. |
By the way, it seems like there is work planned to lift the limitation that
|
Swift recently accepted a proposal for a
consume
operator.The
consume
operator will let us declare that a function takes unique ownership of a type https://github.com/apple/swift-evolution/blob/main/proposals/0366-move-function.md?plain=1#L175-L184 .Any attempts to use the type after it is consumed lead to a compile time error.
This new
consume
operator will make it possible for us to solve one of the memory safety issues described in the bookswift-bridge/book/src/safety/README.md
Lines 48 to 73 in d4f28b3
After the
consume
operator lands in stable Swift we can use it in our generated code to enforce ownership.So, the following bridge module:
would generate Swift code along the lines of:
I'm not sure when the
consume
operator will land in stable Swift, or how we can track its progress.The text was updated successfully, but these errors were encountered: