It looks to me like $0 is retained when returned from the closure:
return _a!._withUnsafeGuaranteedRef { $0 }
Making it identical to this in terms of reference counting:
return _a!.takeUnretainedValue()
Although the takeUnretainedValue version emits an "unoptimizable" retain, while the withUnsafeGuaranteedRef version emits an regular "optimizable" retain.
Do there currently exist both of these APIs on Unmanaged?
Take and retain.
Take and do not retain.
If so, what are they each called?
As a user, those are the semantics I care about: sometimes I have a value which needs to be retained when taken, and sometimes I have a value which needs to not be retained when taken.
With the current confusing names, I am not sure if those things are possible, nor if so how they are spelled.
The APIs on Unmanaged are "take at +0", which will do a retain in practice, and "take at +1", which will take ownership of an existing retain. There's nothing that says "borrow for now without retaining and without taking ownership of a retain", which is why _withUnsafeGuaranteedRef is useful.
I'm still not sure why takeUnretainedValue has an "unoptimizable" retain. @lorentey's logic makes sense to me except for the conclusion:
The conclusion I take is that the compiler will insert a retain when a release might happen. If I just do this:
final class IntWrapper {
var value: Int = 0
}
func test(_ obj: Unmanaged<IntWrapper>) {
let wrapper = obj.takeUnretainedValue()
let value = wrapper.value
print(value)
}
Then there's no reason to perform an actual retain, because accessing a known-stored property can't cause anything to get deinitialized. The programmer-provided assumption that obj is valid at the time of takeUnretainedValue hasn't been invalidated yet.
Of course, _withUnsafeGuaranteedRef is still useful, because as soon as you do anything the compiler can't see (or that definitely could cause deinitialization), that assumption is invalidated. So in practice I wouldn't expect this change to help too many programs. But it definitely could help, and given the documentation for Unmanaged.takeUnretainedValue it's a valid optimization to make.
Just to make sure I'm understanding the extra retain, doing the following explicitly should avoid the extra retain, right? unowned(unsafe) let wrapper = obj.takeUnretainedValue()
This is intended to be used semantically as a "conversion" like
instruction from unmanaged to strong and thus should never be
removed by the optimizer.
Note, Unmanaged<T>._withUnsafeGuaranteedRef is not a public API and will go away in the future. Therefore, don't use it in code that you can not change in the future.
I'm shipping a networking library, and I can't use _withUnsafeGuaranteedRef if it's going to eventually be removed and cause customer code in shipped applications to start crashing when users upgrade their devices to a new version of Swift. Am I correct in my assumption that this is going to be the case?
The Swift runtime libraries are ABI-stable, so we wouldn't intentionally make a change that could break existing binaries. We also take source stability seriously. If we were to deprecate that API, we would do so in such a way that wouldn't affect existing shipped binaries, and would hopefully provide a warning when you try to compile your code with a new SDK that introduced the deprecation.
Thank you for the clarification @Joe_Groff! That's what I had hoped to hear. I assumed that level of care was put in, but after reading that line I quoted above, I wanted to make sure before shooting myself in the foot here. I appreciate the quick response!