Property wrappers: 'self' used before all stored properties are initialized

I'm running into an issue using property wrappers and I'm wondering if this is by design or just a current limitation of property wrappers.

Assume the following code:

struct User {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        print(self.name)
        self.age = age
    }
}

User(name: "Swift", age: 5)

Even though User is not fully initialized when print(self.name) is called, the compiler is able to determine that name is set and allows us to use self there.

Now, assume we are using a simple property wrapper around these properties:

@propertyWrapper struct Field<T> {
    var wrappedValue: T
    init(wrappedValue: T) {
        self.wrappedValue = wrappedValue
    }
}

struct User {
    @Field var name: String
    @Field var age: Int

    init(name: String, age: Int) {
        self.name = name
        print(self.name)
        self.age = age
    }
}

The compiler no longer allows our usage of print here. It says:

error: PropertyWrapperInit.playground:15:20: error: 'self' used before all stored properties are initialized
        print(self.name)
                   ^

PropertyWrapperInit.playground:15:20: note: 'self.age' not initialized
        print(self.name)
                   ^

However, we are able to access the underlying property wrapper storage. For example, this print statement put in the same place does compile:

print(self._name.wrappedValue)

My guess is this limitation has to do with the _enclosingInstance accessor. However, I'm not using that here and I think Swift should allow the above code to compile.

3 Likes

This is intentional, and the reason is that the wrapped property self.name is not a stored property - its getter/setter/observers can access other parts of self, so this isn't safe to allow until all of self is initialized.

The property wrapper example is more analogous to this example of accessing computed properties before all of self is initialized, which will also result in an error:

struct User {
    var _name: String
    var name: String {
      get { _name }
      set { _name = newValue }
    }
    var age: Int

    init(name: String, age: Int) {
        self._name = name
        print(self.name)
        self.age = age
    }
}
error: 'self' used before all stored properties are initialized
        print(self.name)
                   ^
note: 'self.age' not initialized
    var age: Int
        ^
14 Likes

Thanks for the reply!

I'm assuming by this you mean the _enclosingInstance subscript? Because AFAIK there is no other way to access self from a property wrapper. Or is there some other (potentially hidden?) self access that I don't know about?

The argument I'm trying to make here is that this property wrapper I wrote isn't accessing self. I feel that Swift should be able to determine this and allow me to use the property before the rest of self is initialized.

I specifically meant property observers (willSet and didSet, though enclosing self is another example), such as:

struct User {
    @Field var name: String {
        willSet { print(age) }
    }
    var age: Int

    init(name: String, age: Int) { ... }
}

The argument I'm trying to make here is that this property wrapper I wrote isn't accessing self

I think the same argument could be made for regular computed properties. The compiler could probably figure out whether the computed property getter/setter you wrote actually needs the rest of self or if it truly just needs one piece that is already initialized (e.g. the backing property wrapper), but a more granular rule like this makes the language harder to reason about as a user, as opposed to a blanket rule of "no computed properties can be accessed before all of self is initialized".

Perhaps we do want to consider allowing accessing the getter for a wrapped property before all of self is initialized if it truly only depends on the backing property wrapper being initialized, but we need to think about the complexity this adds to the user model.

4 Likes

Possibly I'm not understanding the issue, but according to The Swift Programming Language:

[willSet and didSet] are not called while a class is setting its own properties, before the superclass initializer has been called.

1 Like

[ willSet and didSet ] are not called while a class is setting its own properties, before the superclass initializer has been called.

Yes, if we allow wrapped properties to be accessed during initialization where appropriate, we'd want to follow this rule for that reason. I was only trying to demonstrate another way the wrapped property could access other parts of self (I realize now that this example doesn't really apply here). Property wrappers that use the enclosing self are another example, as @tanner0101 pointed out.

Anyway, I was trying to emphasize that wrapped properties are not stored properties themselves, and that's why you can't access them the same way as other stored properties during initialization. Instead, they follow the same rules as computed properties. Nothing is preventing us from refining these semantics, but we want to carefully consider what the user needs to know in order to understand how/when they can use a wrapped property.

3 Likes

Ah okay that makes more sense now. Thank you.

Given that the compiler is the one implementing the getter and setter for the property wrapper, it seems reasonable that an exception could be made here without increasing user complexity too much. I think it might even make things a bit simpler since property wrappers look more like stored properties than computed properties where they are used.

However, I understand that improving property wrapper access to the enclosing type is still part of future direction so perhaps it makes more sense to table this issue until then.

1 Like

I think it might even make things a bit simpler since property wrappers look more like stored properties than computed properties where they are used.

I was thinking something along the same lines earlier today. I personally think about property wrappers as a backing stored property and a computed property separately, but that's probably because I think about property wrappers in terms of how they're implemented. I think a lot of users might not distinguish between the two. They might think of name and _name (from above) as one entity/property with different forms, or maybe they don't think about _name at all unless they have to. I'd be curious to hear how other folks think about property wrappers when they use them!

But you're right, after this discussion, I think the only exception to the behavior you're requesting would be property wrappers that use the enclosing self subscript. The compiler would know if that's the case, and could provide a specific error message for users who try to access the wrapped value of an enclosing self property wrapper during initialization.

1 Like
Terms of Service

Privacy Policy

Cookie Policy