Swift Regret: Lazy Vars in Structs
(Small) Swift regret: lazy var in structs
— Jordan Rose (@UINT_MIN) December 3, 2021
`lazy` is a convenience feature in Swift; you can build it pretty easily out of an Optional private stored property and a public computed property with a `mutating` getter. But it *is* convenient, so it’s been in the language since 1.0.
Part of the Swift Regrets series.
lazy
is a convenience feature in Swift; you can build it pretty easily out of an Optional private stored property and a public computed property with a mutating
getter. But it is convenient, so it’s been in the language since 1.0.
Even after years and years, the implementation is still a little janky, unfortunately, and on top of that the implementation doesn’t do any synchronization across threads, which can sometimes trip people up. (Mutating getters are weird.) But lazy
isn’t just allowed for class properties; it’s also allowed in structs. And that leads to behavior that’s not wrong but odd. Consider:
struct Ick {
lazy var x: Int = {
print("initialized!")
return 42
}()
}
var ick = Ick() // 1
use(ick.x) // 2
var icky = ick // 3
use(icky.x) // 4
This will print “initialized!” once (line 2). But with this slight change:
var ick = Ick() // 1
var icky = ick // 2A
use(ick.x) // 3A
use(icky.x) // 4
it gets printed twice! Once for 3A and once for 4. This is because the state of x
gets copied during the assignment, and after that the two instances are independent.
It’s not unreasonable behavior, but it is subtle. Personally, I think that’s just enough confusing that it should have been forbidden. Again, you can build it manually if you really want, and then the behavior on copy (assignment) is obvious.