i’ve been investigating a lot of excessive-COW + overretain bugs lately, and i am having a really hard time understanding how the new anchored ARC lifetime rules work.
for the examples in this post, i am using this dummy class:
final
class Connection
{
let id:Int
init(id:Int)
{
self.id = id
}
deinit
{
print("deinitialized: \(id)")
}
}
and i am building everything with -O
.
to start, if i do not use any anchoring, the Connection
objects get deinitialized instantly, even if the local variable bindings referencing them are in use:
@main
enum Main
{
static
func main()
{
let a:Connection = .init(id: 0)
print("x")
let _:Connection = a
print("y")
}
}
deinitialized: 0
x
y
this was surprising to me, because i assumed a
would live for at least until after the print("x")
executes, because there is a usage after it:
let _:Connection = a
but whatever, the optimizer does weird stuff and i didn’t really expect this to work in the first place.
next, if i do use anchoring, by passing a
through a function:
@main
enum Main
{
static
func main()
{
let a:Connection = .init(id: 0)
print("x")
let _:Void = { (_:__owned Connection) in }(a)
print("y")
}
}
then it suddenly lives for the entire duration of main
:
x
y
deinitialized: 0
this was surprising to me, because i assumed passing a
to a function would make it live at least until the print("x")
executes, but no longer afterwards. but it even outlasts the print("y")
.
but whatever, the optimizer does weird stuff and i didn’t really expect this to work in the first place because formally a
is still loitering around for the entire scope, it is just not being used after passing it to the closure literal.
now i try to explicitly get rid of the a
binding, by overwriting it with a new binding:
@main
enum Main
{
static
func main()
{
let a:Connection = .init(id: 0)
let b:Int? = 5
print("x")
let _:Void = { (_:__shared Connection) in }(a)
print("y")
guard let a:Int = b
else
{
return
}
let _:Int = a
print("z")
}
}
but this doesn’t work either! the connection still lives for the entire duration of main
!
x
y
z
deinitialized: 0
this was surprising to me, and unlike the previous two examples, i did expect this to work because formally the old binding a:Connection
no longer exists by the time print("z")
happens, and the guard let
should have forced a deinitialization of the old value.
there seems to be no way to kill the binding in the middle of a lexical scope, either it dies instantly, or it lives for the entire duration of the scope. at this point, i am quite confused about how this new “anchored lifetimes” system is actually supposed to behave.