Guard unowned / while unowned

At the moment it doesn't seem possible to define an unowned local variable in a guard statement or while loop. For example, I would like to be able to:

guard unowned let firstNode else {
    return
}

or

while unowned let node = previousNode.next {
    defer {
        previousNode = node
    }
    ...
}

Is there an elegant swifty way to do this? I'm working with large datasets and trying to avoid retain/release overhead.

Just to address this comment quickly, using unowned does not avoid retain/release overhead. Unowned references still contribute to a refcount. Consider this code:

final class Foo {
    unowned let parent: Foo

    init(parent: Foo) {
        self.parent = parent
    }
}

The compiled and optimized 5.8 version of this code is:

output.Foo.init(parent: output.Foo) -> output.Foo:
        push    rbx
        mov     rbx, rdi
        mov     qword ptr [r13 + 16], rdi
        call    swift_unownedRetain@PLT
        mov     rdi, rbx
        call    swift_release@PLT
        mov     rax, r13
        pop     rbx
        ret

Observe the call to swift_unownedRetain.

This header file may provide useful insight into how reference counts are managed in Swift.

1 Like

If you really want to get rid of the swift_unownedRetain you need to mark it as unsafe as well:

final class Foo {
    unowned(unsafe) let parent: Foo

    init(parent: Foo) {
        self.parent = parent
    }
}

This removes the call to swift_unownedRetain in the generated code:

output.Foo.init(parent: output.Foo) -> output.Foo:
        push    rax
        mov     qword ptr [r13 + 16], rdi
        call    swift_release@PLT
        mov     rax, r13
        pop     rcx
        ret

Note that this will get you into unsafe and undefined behaviour land if you access it and the lifetime of the object is not guaranteed.

2 Likes

What's the purpose of "release" that's still there?

The caller performs a (strong) retain before passing it in. For typical properties, this saves some time in the init, but it means that unowned, weak, and non-stored ones add an extra release call.

Because init parameters are implicitly consuming they get passed in with a reference count of +1 so they need to be decremented/released at the end of the call. You can read more about it in the "borrowing and consuming parameter ownership modifiers" proposal.

Once that proposal extends to Copyable types you can get rid of the retain by marking the parameter as borrowing i.e. init(parent: borrowing Foo). In the meantime you can use the underscore __shared modifier which already generates code without swift_release today.

2 Likes

Coming back to the original question: I think this should be solved with this pitch: borrow and inout declaration keywords. This will allow you to write:

guard borrow let firstNode else {
    return
}

This doesn't "copy" the value and therefore should not emit a retain/release pair IIRC.

while isn't called out explicitly in the proposal but I think its just an oversight. Feel free to ask in the pitch thread though.

1 Like