[GSoC 2021] Referencing enclosing self in a property wrapper type

Hi, my name is Siddharth Nair and I am a CS student at VIT University, India. I have been working with Swift and SwiftUI for a while now and it has turned into the programming language I use most often. GranHealth is the project I have been working on recently. It is an application which caters to the health and safety of the elderly.

My work with property wrappers is limited to just the basics as I have primarily used them to eradicate the use of extension for types. But I am extremely interested and motivated to learn the necessary concepts and more, to work on this project.

@hborla I request you to guide me as to how I could move forward and gain a deeper understanding of the project. I would love to be a part of the Swift community.

1 Like

Hi Siddharth, welcome to the Swift Community!

As a first step, I recommend reading the Swift evolution proposal for property wrappers. Everything through the Detailed Design section will give you a much more in-depth understanding of property wrappers and how they work.

After that, focus on the future direction for referencing enclosing self instance in a property wrapper. Once you've done that, continue reading the rest of this post!


I recommend getting familiar with the memory exclusivity issue outlined in the proposal by playing around with the "obvious" way to reference enclosing self that the proposal talks about, which is registering self with the property wrapper after self has been fully initialized. Here's a "working" version of the problematic code shown in the proposal:

protocol Observed {
  func broadcastValueWillChange<T>(newValue: T)
}

@propertyWrapper
struct Observable<Value> {
  var stored: Value
  var observed: Observed?

  init(wrappedValue: Value) {
    self.stored = wrappedValue
  }

  mutating func register(_ observed: Observed) {
    self.observed = observed
  }

  public var wrappedValue: Value {
    get { return stored }
    set {
      observed?.broadcastValueWillChange(newValue: newValue)
      stored = newValue
    }
  }
}

class MyClass: Observed {
  @Observable public var myVar: Int = 17

  init() {
    // self._myVar gets initialized with Observable(wrappedValue: 17) here
    self._myVar.register(self)    // register as an Observable
  }

  func broadcastValueWillChange<T>(newValue: T) {
    print("oldValue: \(myVar)") // CRASH: simultaneous access to myVar!
    print("newValue: \(newValue)")
  }
}

let c = MyClass()
c.myVar = 10

The issue is that the compiler guards access to _myVar for modification in the synthesized myVar setter, which looks something like this:

  private var _myVar: Observable = Observable(wrappedValue: 17)

  public var myVar: Int {
    get {
      return _myVar.wrappedValue
    }
    set {
     // Here, the compiler will guard access to _myVar and make
     // sure nothing else accesses this memory until the set operation
     // is done
      _myVar.wrappedValue = newValue
    }
  }

The setter for _myVar.wrappedValue in the property wrapper type calls broadcastValueWillChange , which accesses _myVar through the myVar getter shown above, which triggers the exclusivity violation. The reason why the static subscript works around the issue is that it moves the access to _myVar to the same scope where broadcastValueWillChange is called, so the accesses are sequential rather than nested. It's important to keep this in mind when thinking about better designs for the feature, because the final design will likely still need to involve abstracting the wrappedValue accessors as a static subscript, or on an object separate from the property wrapper instance. A few folks in the community came up with an idea to allow property wrappers opt into having static storage, so this could be one option to consider. I wrote up a description of this idea here. I recommend thinking about how that idea could be extended to support access to the enclosing self instance.

Please let me know if you have any questions!

2 Likes

Thank you so much. I'll get started on this right away