Limit read-write property wrapper to read-only

Let’s see a demo involving property wrapper (not real code, for proof-of-concept purpose only, the wrappedValue part can not accomplish such task actually):

@propertyWrapper
struct DatabaseProperty<Value> {
    let field: String
    init(_ field: String) { self.field = field }

    var wrappedValue: Value {
        get { ... }
        set { ... }
    }
}

class People {
    var id: Int
    init(id: Int) { self.id = id }

    @DatabaseProperty("name")
    var name: String
}

So far so good! Database access is simplified:

var person = People(id: 1)
person.name // getter
person.name = "John Appleseed" // setter

For another property, age, we want to make it read-only. With minimal effort, let’s introduce another property wrapper, @ReadOnly:

@propertyWrapper
struct ReadOnly<Value> {
    private var _value: Value
    var wrappedValue: Value { _value }

    init(wrappedValue: Value) { _value = wrappedValue }
}

class People {
    var id: Int
    init(id: Int) { self.id = id }

    @DatabaseProperty("name")
    var name: String

    @ReadOnly 
    @DatabaseProperty("age")
    var age: Int
}

Unfortunately, according to SR-0258:

When there are multiple, composed property wrappers, only the first (outermost) wrapper may have initializer arguments.

For implicit initialization, when there are multiple, composed property wrappers, only the first (outermost) wrapper needs to have an init().

Both limitations / design decisions make above proof-of-concept code invalid.

Is there an elegant solution for the exact use case above (limit read-write property wrapper to read-only)? Two separate property wrappers (ReadWriteDatabaseProperty and ReadOnlyDatabaseProperty) looks less orthogonal to my eyes.

Thanks.

You can work around it by explicitly initializing the backing storage of age in People.init(id:), for example:

self._age = .init(wrappedValue: .init("age"))

It's not ideal but it should work.

Which means give up all the automatic initialization? Turning an otherwise pure declarative model into a less-magical manual mode?

Not ideal, as you said. :thinking:

Yeah. Another option might be to reverse the order of attributes but that may too not be ideal.

// generic argument explicitly specified otherwise compiler fails to infer it.
@DatabaseProperty<ReadOnly<Int>>("age")
@ReadOnly
var age: Int

I think the best thing you can do is to simply create a DatabaseProperty (read-only) and a MutableDatabaseProperty (read-write).

The reversed order requires a much more complex design for ReadOnly, is it correct?