This is a tumbled version of my two-type suggestion from earlier. There's one type for the delegate, and it contains an inner type called Storage
. The delegate is stored statically while the storage is stored inline where the property is.
Unlike the previous iteration I came with in the pitch #1 thread, this one does not give access to self
, but allows a subscript to be used to access the property's value instead of custom get
and set
functions.
@propertyDelegate
struct Lazy<Value> {
enum Storage {
case uninitialized
case initialized(Value)
subscript (delegate: Lazy) {
get {
switch self {
case .uninitialized:
value = delegate.initialValue()
self = .initialized(value)
return value
case .initialized(let value):
return value
}
}
set {
self = .initialized(newValue)
}
}
}
var initialStorage: Storage { return .uninitialized }
var initialValue: () -> Value
init(initialValue: @autoclosure @escaping () -> Value) {
self.initialValue = initialValue
}
}
Then this code:
$Lazy var p: Int = 5
Would generate something like this:
// the delegate lives as a static variable
static var _p_delegate: Lazy<Int> = Lazy(initialValue: 5)
// the storage is an instance variable
var _p_storage: Lazy<Int>.Storage = Self._p_delegate.initialStorage
// the computed p accesses the value through the delegate
var p: Lazy<Int>.Value {
mutating get {
// mutating only when Lazy.get takes `self` as `inout`
return _p_storage[_p_delegate]
}
set {
// mutating only when Lazy.set takes `self` as `inout`
_p_storage[_p_delegate] = newValue
}
}
An approach like this could line up well in a world where custom attributes are statically stored structs: the "property delegate" attribute would be a custom attribute like others with the addition that it provides a storage type.