Safely preserving "call by reference" optimization when casting `inout` parameter

I have a protocol Event and a struct MyEvent. I also have a function that accepts an in-out argument to an instance of Event.

I don't want to lose the in-place/call by reference optimization Swift is able to do when an in-out parameter is backed by a in-memory value, and also to avoid the cast/copy-mutate-set performance penalty I would otherwise have to take.

Is it "safe" to forcibly cast an inout parameter as a pointer in this way? (Or maybe I should simply pass in a pointer to begin with, despite losing compiler safety & exclusivity checks?)

protocol Event { }

struct MyEvent: Event {
    
    var mutated = false
    
}

func mutate(event: inout Event, asMyEvent: (inout MyEvent) -> Void) {
    guard event is MyEvent else { return }
    
    withUnsafeMutablePointer(to: &event) {
        $0.withMemoryRebound(to: MyEvent.self, capacity: 1) {
            asMyEvent(&$0.pointee)
        }
    }
}

var event: Event = MyEvent()

print(event) // MyEvent(mutated: false)

mutate(event: &event) {
    $0.mutated = true
}

print(event) // MyEvent(mutated: false)
1 Like

The Event existential is not the same type and does not have the same layout as MyEvent, so this is not safe, but if you made the event parameter generic, then it would be safe to do this, since you would no longer be changing the actual type being pointed to:

func mutate(event: inout some Event, asMyEvent: (inout MyEvent) -> Void) {
    guard event is MyEvent else { return }
    
    withUnsafeMutablePointer(to: &event) {
        $0.withMemoryRebound(to: MyEvent.self, capacity: 1) {
            asMyEvent(&$0.pointee)
        }
    }
}
3 Likes

That makes sense. Thank you!