On proliferation of force unwrapping when using UniqueArray<T>

this builds on thoughts i posted over in SE-0527: RigidArray and UniqueArray - #21 by taylorswift , but it’s enough of a tangent that i figured it merits its own thread.

i’ve been delving into the “inline weakrefs” (UniqueArray<T?>) pattern that i talked about in the other thread, and i’m finding that UniqueArray is leading me towards writing force unwraps, which feels really indicative of a language or API flaw.

namely, if you’ve got an underlying buffer of UniqueArray<T?>, and you’re trying to expose that as an abstraction of non-optional T, the pieces just really don’t click together at all.

struct Arena<Object>: ~Copyable where Object: ~Copyable {
    var weak: UniqueArray<Object?>
}
extension Arena where Object: ~Copyable {
    subscript(unowned index: Int) -> Object {
        _read {
            switch self.weak[index] {
            case let value?:
                yield value
            case nil:
                fatalError("bad access, object (index = \(index)) is nil")
            }
        }
        _modify {
            if  var object: Object = self.weak[index].take() {
            //      ^~~~~~
            // Missing reinitialization of closure capture 'object' after consume
                defer {
                    self.weak[index] = consume object
                }
                yield &object
            } else {
                fatalError("bad access, object (index = \(index)) is nil")
            }
        }
    }
}

the only way that this accessor compiles if if i upcast back to T? and then force-unwrap that T? with !.

_modify {
    if  let object: Object = self.weak[index].take() {
        var object: Object? = consume object
        defer { self.weak[index] = object.take() }
        yield &object!
    } else {
        fatalError("bad access, object (index = \(index)) is nil")
    }
}

although it is unreachable in this sketch, i don’t think that “just write yield &object!” is advice that we want to be putting out there. i feel like defer, or at least the highest defer in a particular scope, ought to be able to consume local variables without reinitializing them.

1 Like

Perhaps I am not understanding what your end goal is through the code, but the following does compile. I do want to note that I am using apple/swift-collections and not the toolchain version, so their could be differences.

I also want to note that the particular code you provided doesn't look or feel quite right to me. I would not use noncopyables or UniqueArray in such ways, especially when mixing it with optionals and read/modify accessors (which are not stable and superseded by borrow and mutate accessors in Swift 6.4).

import BasicContainers

struct Arena<Object>: ~Copyable where Object: ~Copyable {
    var weak: UniqueArray<Object?>
}
extension Arena where Object: ~Copyable {
    subscript(unowned index: Int) -> Object {
        _read {
            switch self.weak[index] {
            case let value?:
                yield value
            case nil:
                fatalError("bad access, object (index = \(index)) is nil")
            }
        }
        _modify { // minor change here!
            if self.weak[index] != nil {
                yield &self.weak[index]!
            } else {
                fatalError("bad access, object (index = \(index)) is nil")
            }
        }
    }
}