Swift Regret: Lazy Vars in Structs

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.