Mutability of Property Wrappers

Is it possible to somehow define a wrapped property where the wrapper is itself settable in addition to just the wrapped value?

Using the example from The Swift Programming Language...

struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

...and its “unfolded” form...

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

...the necessary functionality is both of these:

var rectangle = SmallRectangle()
rectangle.height = 10 // ← Assigned a new value to the same wrapper.
rectangle._height = TwelveOrLess() // ← Overwrote the wrapper itself.

Is there some way of making the same functionality available while still using the property wrapper form? (In the actual use case, the wrapper must be a class, so assigning self is problematic.)

1 Like

I believe this can already be done.

@propertyWrapper
class TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }

}
struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int

    mutating func update() {
        height = 10
        _height = TwelveOrLess()
    }
}

Is there something else you're trying to do?

Interesting. I didn’t realize the low‐lined variants actually existed. Do they carry a guarantee of stability, or are they just a leaked implementation detail?

Regardless, when I try that I get:

'_property' is inaccessible due to 'private' protection level

It needs to be possible at the public level.

I haven’t tried it yet, but I have a hunch I can make it work by nesting things differently. If it works, I’ll post the code.

The "underscored" variables are part of SE-0258, so it should be here to stay.

1 Like

It worked. I rearranged the code by adding an additional type for nesting:

public class TwelveOrLess {
    // Note that we are dealing with a class, not a structure.
    // This type is no longer technically a property wrapper itself,
    // but it still does all the work and handles the storage.
    private var number = 0
    public var value: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }
}
@propertyWrapper public struct TwelveOrLessProperty {
    public var projectedValue: TwelveOrLess
    public var wrappedValue: Int {
        get { return projectedValue.value }
        set { projectedValue.value = newValue }
    }
}
public struct SmallRectangle {
    @TwelveOrLessProperty public var height: Int
    @TwelveOrLessProperty public var width: Int
}
var rectangle = SmallRectangle()
rectangle.height = 10 // ← Assigned a new value to the same pseudo‐wrapper.
rectangle.$height = TwelveOrLess() // ← Overwrote the pseudo‐wrapper itself.

It's worth noting for anyone else who happens across this thread that if the wrapper were not required to be a class, you'd be able to do this without the extra indirection:

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, 12) }
    }

    var projectedValue: Self {
        get { return self }
        set { self = newValue }
    }
}
struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
rectangle.height = 10 // ← Assigned a new value to the same wrapper.
rectangle.$height = TwelveOrLess() // ← Overwrote the wrapper itself.

But Jeremy set the requirement that it be a class up front, so the extra level of nesting is probably the best answer.

5 Likes