propertyWrapper always evaluates default value?

The built-in lazy lets me do something like:

lazy var myLazy: String = {
  print("initializing...")
  return "my string"
}()

and the closure won't be evaluated until myLazy is first evaluated. In other words, "initializing..." won't print until myLazy is accessed for the first time.

But this doesn't seem to work for propertyWrappers. Given the Lazy wrapper from SE-0258:

@propertyWrapper
enum Lazy<Value> {
  case uninitialized(() -> Value)
  case initialized(Value)

  init(wrappedValue: @autoclosure @escaping () -> Value) {
    self = .uninitialized(wrappedValue)
  }

  var wrappedValue: Value {
    mutating get {
      switch self {
      case .uninitialized(let initializer):
        let value = initializer()
        self = .initialized(value)
        return value
      case .initialized(let value):
        return value
      }
    }
    set {
      self = .initialized(newValue)
    }
  }
}

I'd expect something like the following to behave like the lazy example above:

@Lazy var myLazy: String = {
  print("initializing...")
  return "my string"
}()

But it doesn't. The closure is evaluated immediately printing "initializing..." even if myLazy is never used.

Note that this isn't how @autoclosure usually works. Usually if we give it a called closure like this, it will wrap it for later evaluation. In fact, this works as expected:

@Lazy(wrappedValue: {
  print("initializing")
  return "my string"
}()) var myLazy: String

Like the lazy example, above, this doesn't evaluate the closure (or print "initializing") until myLazy is accessed the first time.

Please circle one?

  • I'm doing it wrong
  • It's a bug
  • It's intended behavior

Looks like [SR-10950] Property wrappers: @autoclosure not working? · Issue #53341 · apple/swift · GitHub

3 Likes

That's it exactly! And I was sure I had searched for "propertywrapper" and "autoclosure". Must have fat-fingered something.

Thanks for pointing me to it!