I'm very positive about this feature, but the motivation for wrapperValue is still not clear to me.
Is there any functional difference compared to writing .wrapperValue at the use site? (Other than hiding the property wrapper's API, which seems like it should be done via the standard access controls.)
Here are some of my thoughts on the motivating examples from the proposal, and from this thread.
As mentioned in my previous comment, this one seems to implement copy-on-read, so I'm not going to comment further here.
If Box conformed to dynamic member lookup in exactly the same way that Ref does, wouldn't the code work exactly the same, without the need for the wrapperValue magic?
The main motivation here seems to be that the implementation can be changed without affecting call site code. But, isn't that what protocols are for? If we introduce a PointerStorage protocol, then we can get the same resilience at the call site:
protocol PointerStorage {
associatedtype Value
var pointer: UnsafeMutablePointer<Value> { get }
}
@propertyWrapper
struct LongTermStorage<Value>: PointerStorage {
// unchanged from the proposal, except without the
// `wrapperValue` property.
}
@propertyWrapper
struct ArenaStorage<Value>: PointerStorage {
// unchanged from the proposal, except without the
// `wrapperValue` property.
}
// and in use...
// The only change here is the addition of the explicit `.pointer` reference.
@LongTermStorage(manager: manager, initialValue: "Hello")
var someValue: String
// $someValue accesses the wrapper instance, which is a `PointerStorage<String>`
let world = someValue.pointer.move() // take value directly from the storage
someValue.pointer.initialize(to: "New value")
The concern here appears to be that a property wrapper and its stored properties can have different semantics (value vs reference).
This is a valid concern, but it applies whether StatefulWrapper is a property wrapper, or just a regular struct. I think the correct way to do this would be to have StatefulWrapper and Observer either both be classes, or both be structs.
If they are both classes, then taking a new reference to the wrapper would create a reference:
@propertyDelegate class StatefulWrapper { ... }
class Observer { ... }
@StatefulWrapper var a: Int = 0
let b = $a
// both of these notify the same set of observers
a = 1
b = 2
If they're both structs, taking a new reference to the wrapper would create a separate (observable) property.
@propertyDelegate struct StatefulWrapper { ... }
struct Observer { ... }
@StatefulWrapper var a: Int = 0
let b = $a
// Each of these notifies a separate set of observers
a = 1
b = 2