Some confusion about ownership modifiers

Hi everyone. While experimenting with creating a simple Box type in Swift (6.3.3), I've ran into some issues and I'm wondering if you guys could enlighten me. In practice I would use indirect.

The code:

do {
    struct Box<T: ~Copyable>: ~Copyable {
        private let p = UnsafeMutablePointer<T>.allocate(capacity: 1)

        /*
        var pointee: T {
            borrowing get { p.pointee }
            // Error: 'self.p.pointee' is borrowed and cannot be consumed 
        }
        */

        func with(_ f: (borrowing T) -> ()) {
            f(p.pointee)
        }

        init (_ v: consuming T) {
            p.initialize(to: v)
        }

        deinit {
            p.deallocate()
        }
    }

    struct A: ~Copyable { let a: Int }
    struct Foo: ~Copyable {
        let a: A
        let b: A
    }

    enum Node: ~Copyable {
        case cons(Int, Box<Node>)
        case empty
    }

    let l = Node.cons(5, Box(Node.cons(4, Box(Node.empty))))
    if case let .cons(_, b) = l {
        /*
        b.with { p in
            // error: 'p' is borrowed and cannot be consumed 
            if case .cons(let i, _) = p {
                print(i)
            }
        }
        */

        b.with { p in
            switch p {
            case .cons(let i, _): print(i)
            default: break
            }
        }

    }
}
  1. Why does borrowing get { p.pointee } cause an error, but func with(_ f: (borrowing T) -> ()) work? Are they not doing similar things?

  2. Why does the if case fail while the switch works?

For 1 —

No, borrowing get means you're borrowing self but then (as with any get accessor) returning an owned value—which in this case would require consuming p.pointee, which you can't. You get the same error if you write borrowing func get() -> T { p.pointee }.

By contrast, in the call to with, you're borrowing p.pointee, which is fine.

I think what you're trying to express is the borrow accessor (SE-0507) or the yielding borrow accessor (SE-0474). However, using borrow with unsafe pointers is explicitly a future direction not supported by SE-0507 (see that proposal for an explanation as to why not), and yielding borrow has been accepted but isn't yet shipping by default—not even in the future Swift 6.5—except as the precursor (unsupported) _read accessor.

For 2 —

This is a bug, which @broken-circle recently described in a comment on: Disparity between `switch` pattern matching on non-copyable enums vs. other control flow statements · Issue #78573 · swiftlang/swift · GitHub

5 Likes

Thanks for the very detailed response, and yes what I was trying to express was the upcoming borrow accessor. Though I do remember seeing it's not available until 6.4 :person_facepalming:. I guess a part of the issue is borrowing is a bit confusing since methods seem to implicitly borrow self without it. Thanks for pointing out that borrow, as of now, would not have worked here anyways, and I've seen why.

FYI: Swift 6.4 added UniqueBox. See implementation here