Completing Property Wrappers

Hello, Swift community!

I’d like to start a discussion about the complete roadmap of property wrappers and lay down the list of missing features in some sort of a manifesto to make it easier for the community to understand the plans and goals regarding property wrappers and help push forward the proposals and implementations.

Personally, I’ve been dearly missing the ability to declare a property wrapper as a protocol requirement. I’ve also very need the ability to access the enclosing self, although, I’ve been using the private feature as a temporary fix (yea, I know that it can break at any moment without notice).

What do you guys think? What is missing? What is being done right now? What is already planned?

1 Like

There has been this recent proposal for requiring a property wrapper in a protocol:


which I still hope get some traction.

1 Like

And the rest of the thread.

Such design would also allow us to create property wrappers for computed properties.

One use case I have in mind:

extension EnvironmentValues {
  enum ValueKey: EnvironmentKey {
    static var defaultValue: Value { ... }
  }
​
  // before
  var value: Value {
    get {
      self[ValueKey.self]
    }
    set {
      self[ValueKey.self] = newValue
    }
  }

  // `Wrapper` is a static property wrapper which does not require any storage.
​
  // after
  @Wrapper(ValueKey.self)
  var value: Value
}

I would be against rushing to "complete" property wrappers any time soon, as this will dramatically change the design and property wrappers potential. I would like to encourage the community to push other language features that would make PW's even more powerful first.

Would you mind listing some examples of supporting language features that could come first, and how they might inform/aid future directions of property wrappers?

As I mentioned in the linked thread, we would need 'generalized coroutines' to avoid the clunkiness of static subscripts (as currently done with the private feature) and it would allow us to have property wrappers that would work great both with value and object types. The issue here however is that it would probably take a while until we get that. I'm not even sure we'll get everything in Swift 6, would be cool though.

Two features I would love to see:

Local Property Wrappers

func foo() {
  @MyPropertyWrapper var foo: Int = 3
}

This is already planned according to the diagnostic that is shown if you type this.

Immutable Property Wrappers

Right now, the synthesized code for property wrappers looks something like this:

struct MyStruct {
  private var _myWrappedProperty: PropertyWrapper<Int>
  var myWrappedProperty: Int { _myWrappedProperty.wrappedValue }
  var $myWrappedProperty: PropertyWrapper <Int>.ProjectedValue { _myWrappedProperty.projectedValue }
}

Unfortunately, this allows you to do the following:

mutating func doEvil() {
  _myWrappedProperty = MyWrappedProperty(3)
}

It would be great if we supported @PropertyWrapper let myWrappedProperty: Int = 3 to disallow this behavior.

1 Like

The first feature is reasonable in the near term I think. The latter however does imply that the wrapped property itself gets immutable, so what‘s the point of it?

I would love to be able to pass values to a property wrapper through an init.

What do you mean? What is it that‘s not already possible?

Looks like the ownership model and the concurrency model are the major enablers for this. Since both of them are, IIRC, on the Swift 6 roadmap, I’d love to be able to get some news on the implementation progress.

1 Like

Borrowing an example from NSHipster

@propertyWrapper
struct Clamping<Value: Comparable> {
    var value: Value
    let range: ClosedRange<Value>

    init(initialValue value: Value, _ range: ClosedRange<Value>) {
        precondition(range.contains(value))
        self.value = value
        self.range = range
    }

    var wrappedValue: Value {
        get { value }
        set { value = min(max(range.lowerBound, newValue), range.upperBound) }
    }
}

struct Solution {
    @Clamping(0...14) var pH: Double = 7.0

    init(withRange range: ClosedRange<Double>) {
         // No way to pass range to the init of the wrapper
    }
}

The example is outdated and there is a way to do that.

@propertyWrapper
struct Clamping<Value: Comparable> {
  var value: Value
  let range: ClosedRange<Value>

  init(wrappedValue: Value, _ range: ClosedRange<Value>) {
    precondition(range.contains(wrappedValue))
    self.value = wrappedValue
    self.range = range
  }

  var wrappedValue: Value {
     get { value }
     set { value = min(max(range.lowerBound, newValue), range.upperBound) }
  }
}

struct Solution {
  @Clamping(0...14) var pH: Double = 7.0

  init(withRange range: ClosedRange<Double>) {
    self._ph = Clamping(wrappedValue: 7.0, range)
  }
}

If you need to catch up with the functionality of property wrappers, please read the proposal. ;)

That works but it ends up being a ton of duplication, especially in situations where you have many wrappers that all need the same value. It would be far nicer if property wrappers could refer to a stored property.

I don't understand what you actually want from property wrappers. There is no magic here, it's just synthesized code, if you need to opt out, you can always write the synthesized part manually.

Oh yes, yes, yes! Please. Not all property wrappers require instance storage. This not only wastes space. This also plays very badly with Codable synthesis, because such property wrappers are usually instantiated with non-codable static values (key paths, static members, etc.)

I recently hit this wall.

But this is not "completing" property wrappers, I think. A dedicated pitch would be needed for this.

1 Like

From my perspective this would require static property wrappers which is already kinda blocked. :thinking:

I was talking about instance property wrappers, but without storage.

My testbed is GRDB, an everlasting opportunity for experiments (but only successful ones ship, of course).

The sample code below won't compile, because Codable synthesis fails when it is not the wrapped property which complains being declared in an extension:

// Future Swift?
struct Team: Codable {
    var id: Int64
    var name: String
}

extension Team: TableRecord {
    // Declare a database association
    static let players = hasMany(Player.self)
    
    @Associated(players) var players: Request<Player>
}

let team: Team = ...
let players = try team.players.fetchAll(db) // Fetch associated players
let playerCount = try team.players.fetchCount(db) // Fetch players count

It could compile if we had storage-less property wrappers.

Swift 5.2 equivalent code

This one works perfectly today, but without property wrappers:

// Swift 5.2
struct Team: Codable {
    var id: Int64
    var name: String
}

extension Team: TableRecord {
    // Declare a database association
    static let players = hasMany(Player.self)
    
    var players: Request<Player> { request(for: Self.players) }
}

let team: Team = ...
let players = try team.players.fetchAll(db) // Fetch associated players
let playerCount = try team.players.fetchCount(db) // Fetch players count
1 Like

Having a keypath to the wrapped property always at hand in the wrapper would be great. It was pitched before. Definitely in favor of this.

Sorry it took a bit for me to reply, but I thought some code would help illustrate the second point. I open-sourced a module here which uses a property wrapper to affect Decodable decoding. In this case, I don't need the property wrapper to be a var, since it never changes. I suspect property wrappers which are class types might find this useful as well.

1 Like

Well, perhaps there does not have to be a single, "final" way to access self. Maybe we can have multiple approaches.

I'm working on a project where this currently-hidden API is a hugely useful game-changer. The use case is for a very simplified kind of binding that relies on keypaths.

At first I was passing in the keypath like @Wrap(\Foo.bar) var bar: Bar however this no longer compiles due to a bug in Swift 5.2, and there is no guarantee that you passed in the right keypath—@Wrap(\Foo.baz) var bar would compile but then fail at runtime since \Foo.baz != \Foo.bar.

To me, the main problem with the current hidden feature is that the wrapper does not know the enclosing instance's type at init-time, so I'm having to resort to passing an extra argument into the wrapper just to make sure our wrapper is specialized on the correct root type. That could easily be remedied if the compiler synthesized this extra param.

Speaking of synthesizing params, maybe there is a way to add a general ability to add auto-synthesized params for wrappers based on keypaths and protocols, something like:

protocol AFoo {
   var bar: Bar
}

@propertyWrapper 
struct Wrap<Root: AFoo, Value> {
    // normal stuff
    // ...
    
    /// Inits the wrapper with a Bar, 
    /// uses Root instance's bar as default
    init(bar: Bar = \Root.bar) {
        self.bar = bar
    }

Especially with wrappers in protocols this would be super awesome since you'd be able to:

protocol AFoo {
   @Bind var bar: Bar
}

@propertyWrapper 
struct Wrap<Root: AFoo, Value> {
    // normal stuff
    // ...
    
    /// Inits the wrapper with a Bar, 
    /// uses Root instance's bar binder as default
    init(bar: Bar = \Root.bar) {
        self.bar = $bar
    }
Terms of Service

Privacy Policy

Cookie Policy