Using slightly modified example from the init accessor proposal
@propertyWrapper
struct Wrapper<T> {
// changed from var to let
let wrappedValue: T
}
struct S {
@Wrapper private var value: Int
init(value: Int) {
self.value = value // error: cannot assign to property: 'value' is a get-only property
}
}
Why does the compiler emit error? The property wrapper still has initializer which init accessor supposed to use.
If I understood you correctly, it contradicts with the proposal:
Property wrappers support bespoke definite initialization that allows initializing the backing property wrapper storage via the computed property, always re-writing initialization-via-wrapped-property in the form self.value = value to initialization of the backing storage in the form of _value = Wrapper(wrappedValue: value)
Also my example compiles if wrappedValue is mutable property.
For the time being this is how the feature was implemented. Depending on the effort you want to invest, you might want to consider writing an attachedmacro, that will create something like an immutable property wrapper for you AND supports the syntax on init, that you are looking for (I think).
This is a rough sketch on how this would work. Key here is the generation of an init accessor with storage restriction and to make use of a generated hidden wrapper value.
/// Defined in Macro library:
public struct Wrapper {
public let value: String
}
/// Example:
class ExampleClass {
// Developer writes:
@Wrapper
var value: String
// Macro expands to:
let _$valueWrapper: Wrapper // Generated by the attached macro (via 'peer' implementation).
var value: String { // Generated by the attached macro (via `accessor` implementation).
@storageRestrictions(initializes: _$valueWrapper)
init(initialValue) {
_$valueWrapper = Wrapper(value: initialValue)
}
get {
_$valueWrapper.value
}
}
// Now this will work:
init() {
value = "test"
}
}
That section of the proposal does not apply to immutable wrappedValues, but it's also just not consistent with how the language works otherwise and so I recommend against using it.
// Does not crash when initialized.
struct S {
var value = 1 { didSet { fatalError() } }
init(value: Int) {
self.value = value
self.value = value
// Keep reassigning. `didSet` will never be called.
}
}
@propertyWrapper struct Wrapper<T> {
var wrappedValue: T
}
struct S {
@Wrapper var value: Int { didSet { fatalError() } }
init(value: Int) {
self.value = value
self.value = value // crash here, the first time `init` is not secretly called.
}
}
@propertyWrapper struct Wrapper<T> {
// Same behavior if `didSet` is moved here:
var wrappedValue: T { didSet { fatalError() } }
}
struct S {
@Wrapper var value: Int
init(value: Int) {
self.value = value
self.value = value // crash here, the first time `init` is not secretly called.
}
}
I would not argue that it is. But, we can at least acknowledge that property wrappers are not mentioned in the section on Init accessors for read-only properties. (There's a typo in this code, and reassignment crashes the compiler, but it works otherwise!)