Thank you for the quick reply!
May I go a little bit off-topic explaining my mental model in order to understand its flaws and consequently the reasons behind SE-0258? It partly resembles Allow Property Wrappers with Multiple Arguments to Defer Initialization when wrappedValue is not Specified.
Borrowing a simplified example from the SE-0258 proposal:
@propertyWrapper
struct Field<Value: DatabaseValue> {
let name: String
private var cachedValue: Value?
init(name: String) { self.name = name }
var wrappedValue: Value { get set ... }
}
struct Person: DatabaseModel {
@Field(name: "first_name") var firstName: String
@Field(name: "last_name") var lastName: String
@Field(name: "date_of_birth") var birthdate: Date
}
Field is a property wrapper that doesn't provide any .init(wrappedValue:) or .init(wrappedValue:...) initializer. Thus properties annotated with @Field cannot be initialized with an initial value in the struct and this implicitly means that their initial value should be provided by the property wrapper type itself (in this case, the property initial value would be the one fetched from the database). In line with @Andrew_Arnopoulos's proposal, I would expect it to be transformed into:
struct Person: DatabaseModel {
// no default values here
private var _firstName: Field<String>
private var _lastName: Field<String>
private var _birthdate: Field<Date>
var firstName: String { get set ... }
var lastName: String { get set ... }
var birthdate: Date { get set ... }
init() {
// default values here instead
_firstName = Field(name: "first_name")
_lastName = Field(name: "last_name")
_birthdate = Field(name: "date_of_birth")
}
}
Only an empty memberwise initializer should be generated since it's not possible to provide an initial value to @Field wrapped properties.
Suppose now that also an .init(wrappedValue:name:) initializer is available:
extension Field {
init(wrappedValue: Value, name: String) {
self.name = name
self.wrappedValue = wrappedValue
}
}
In this case we can either provide an initial value using = or not, leaving its initialization in the generated memberwise initializer:
struct Person: DatabaseModel {
@Field(name: "height") var height: Double
@Field(name: "alive") var alive: Bool = true
// generated memberwise initializers:
// - case 'height' is using Field.init(name:)
init(alive: Bool = true) {
_height = Field(name: "height")
_alive = Field(wrappedValue: alive, name: "alive")
}
// - case 'height' is using Field.init(wrappedValue:name:)
init(height: Double, alive: Bool = true) {
_height = Field(wrappedValue: height, name: "height")
_alive = Field(wrappedValue: alive, name: "alive")
}
}
The property alive has an initial value, so .init(wrappedValue:name:) is the only initializer matching its declaration. On the other hand, the property height does not provide an initial value so there are exactly two cases:
height is using .init(name:), so a memberwise initializer without an height parameter should be generated;
height is using .init(wrappedValue:name:), so a memberwise initializer with an height parameter should be generated.
This has bad scalability implications, since now 2^n initializers would need to be generated, with n being the number of properties having no initial value and both .init(wrappedValue:...) and .init(...) available in their wrapper types.
Is this a reason why we cannot (or shouldn't) allow storage initialization in inits when wrappedValue is not specified?
On the other hand, having those memberwise initializers generated that way, has some advantages:
I wonder if reducing the cases to be 1^n, i.e. by forcing one of the "overloads" or by using the "pick the most specific overload" already vastly used in the language, would be beneficial to the whole property wrapper field.