J. Doe
J. Doe

Reputation: 13033

Why can't I change variables in a protocol extension where self is a class?

I am curious why this doesn't work:

public protocol MyProtocol {
    var i: Int { get set }
}

public protocol MyProtocol2: class, MyProtocol {}

public extension MyProtocol2 where Self: AnyObject {
    func a() {
        i = 0 <-- error
    }
}

Error:

Cannot assign to property: 'self' is immutable

Why? Only classes can adopt MyProtocol2. If I add : class declaration behind MyProtocol it works. I do not understand why it doesn't work on a subprotocol.

Upvotes: 13

Views: 2430

Answers (2)

Cristik
Cristik

Reputation: 32785

Property setters trigger mutation on the callee, which means that any method that operates on the property needs to be declared as mutating:

public extension MyProtocol2 where Self: AnyObject {
    mutating func a() {
        i = 0
    }
}

This will allow any writes on self within the method.

Upvotes: 0

Hamish
Hamish

Reputation: 80781

Your example doesn't compile because MyProtocol isn't class-bound, and as such can have mutating requirements and extension members. This includes property setters, which are by default mutating. Such members are free to re-assign a completely new value to self, meaning that the compiler needs to ensure they're called on mutable variables.

For example, consider:

public protocol MyProtocol {
  init()
  var i: Int { get set } // implicitly `{ get mutating set }`
}

extension MyProtocol {
  var i: Int {
    get { return 0 }
    // implicitly `mutating set`
    set { self = type(of: self).init() } // assign a completely new instance to `self`.
  }
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
  func a() {
    i = 0 // error: Cannot assign to property: 'self' is immutable
  }
}

final class C : MyProtocol2 {
  init() {}
}

let c = C()
c.a()

If this were legal, calling c.a() would re-assign a completely new instance of C to the variable c. But c is immutable, therefore the code is not well formed.

Making MyProtocol class bound (i.e protocol MyProtocol : AnyObject or the deprecated spelling protocol MyProtocol : class) works because now the compiler knows that only classes can conform to MyProtocol. Therefore it imposes reference semantics by forbidding mutating requirements and extension members and therefore prevents any mutations of self.

Another option at your disposal is to mark the setter for the requirement i as being nonmutating – therefore meaning that it can only be satisfied by a non-mutating setter. This makes your code once again well-formed:

public protocol MyProtocol {
  init()
  var i: Int { get nonmutating set }
}

public protocol MyProtocol2 : class, MyProtocol {}

public extension MyProtocol2 where Self : AnyObject {
  func a() {
    i = 0 // legal
  }
}

Upvotes: 12

Related Questions