Convenience init cannot set a var that is defined in a protocol extension

The following code fails to compile, using Xcode 12.2, specifying the error in the comment.

protocol Something {}
extension Something {
    var thing: String {
        get { return "" }
        set { }
    }
}

class Object: Something {
    
    init() {}
    
    convenience init(nothing: String) {
        self.init()
        self.thing = nothing // 'let' property 'thing' may not be initialized directly; use "self.init(...)" or "self = ..." instead
    }
    
}

The error seems to be incorrect considering thing is delcared as a var not a let and has a set method. And if the line setting the Object's thing were anywhere other than in the convenience init, then it compiles just fine.

Am I correct in assuming that this should work and the compiler is incorrectly throwing this error?

The error message is incorrect, and to me, it seems like an error shouldn't be generated anyway, but I may not be thinking of something. (Maybe it's an attempt at avoiding self-reassignment for reference types?)

It's possible that one of these changes will solve your use case though:

extension Something where Self: AnyObject {
nonmutating set { }
1 Like

Interesting.

protocol Something: class {}

or

protocol Something: AnyObject {}

also work.

And upon further investigation, without using any of the aforementioned workarounds, the following instance method also fails.

    func setThing(_ thing: String) {
        self.thing = thing // Cannot assign to property: 'self' is immutable
    }

As well as this

let object = Object()
object.thing = "" // Cannot assign to property: 'object' is a 'let' constant

Unless object is declared as a var.

It definitely feels like these errors are incorrect, as there shouldn't be any issue setting a var on a reference object. The issue is probably that the compiler is forwarding the resolution of this to the protocol extension which doesn't know that the owner is a class, hence why specifying that the protocol is only for class types fixes it.

The error message shown in the initial post is incorrect (and I’m not entirely sure the error is correct, but it might be). However, the errors (and their messages) that you show are correct.

It is exactly as @anon9791410 explains: Because thing has a mutating setter, that setter could reassign self. It is not permitted to call something that could reassign self on a let constant. There have been a number of threads on these forums to explain this concept.

(If you add the AnyObject constraint to the protocol, then the errors shown go away because the setter can’t then reassign self. Of course, this can come about only because the compiler then enforces the same restrictions against reassigning self inside the body of the getter’s implementation.)

1 Like