Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 300
J. Doe Avatar asked Aug 03 '18 19:08

J. Doe


1 Answers

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
  }
}
like image 178
Hamish Avatar answered Oct 19 '22 23:10

Hamish