Why can we not mutate mutable members on a reference type?

I stumbled against this wall a few times, previously I simply ignored it, but now it seems like a pointless language limitation. Every object type (right now we only have classes) in Swift is implicitly a subtype of AnyObject, which in fact is like a type alias to the class keyword. That makes any instance that is known to be of type AnyObject a reference type.

However when used in a protocol extension the compiler does not allow one mutate mutable members even if Self is guaranteed to be a AnyObject sub-type.

Here the original example that got me thinking:

protocol P {
  var value: Bool { get set }
}

protocol Q {
  associatedtype Object: AnyObject
  var object: Object { get }
}

protocol R: Q where Object: P {}
extension R {
 func makeTrue() {
    object.value = true // Cannot assign to property: 'object' is a get-only property
  }
}

Making P refine AnyObject solves the issue here, but it defeats the purpose of reusability of P in non-object context.

Another workaround would be this:

var ref = object
ref.value = true

However it really feels not natural, rather messed up I would say.

@timv actually reduced the example from above to a simpler form:

protocol P {
  var value: Bool { get set }
}

extension P where Self: AnyObject {
 func makeTrue() {
    value = true // Cannot assign to property: 'self' is immutable
  }
}

One can solve the problem here by making the method mutating, but this is again non-sense as class bound types don't allow mutating and in this case Self is known to be class bound.

I refer to this error:

class A {
  mutating func foo() {} // 'mutating' isn't valid on methods in classes or class-bound protocols
}

So why exactly do we have this limitation in the language?

All you have to do is mark it as 'mutating'. If anything I would argue why don't we force all classes to mark their methods as mutating when the change self.

1 Like

See swift - Why can't I change variables in a protocol extension where self is a class? - Stack Overflow – the problem is that for non-class bound protocols, properties default to having mutating setters, which matters for protocol extensions which are able to assign completely new values to self within such a setter.

Applied to your example:

protocol P {
  init()
  var value: Bool { get set } // implicitly `{ get mutating set }`
}

extension P {
  var value: Bool {
    get { return true }

    // implicitly `mutating set`
    set { self = type(of: self).init() }
  }
}

protocol Q {
  associatedtype Object: AnyObject
  var object: Object { get }
}

protocol R: Q where Object: P {}
extension R {
  func makeTrue() {
    // What if we're calling the `value` setter from the protocol extension?
    // We'd be assigning a new value to `object`, which is get-only.
    object.value = true
  }
}
5 Likes

I was discussing this in Slack with @timv and he came up with an example that is potentially the reason for the limitation. Long story short the setter can potentially re-assign self which wouldn't work on immutable reference properties.

protocol P {
    init(value: Bool)
    var value: Bool { get set }
}

extension P {
    var value: Bool {
        get { fatalError() }
        set { self = Self.init(value: newValue) }
    }
}

extension P where Self: AnyObject {
    func makeTrue() {
        // Cannot assign to property: 'self' is immutable
        // self.value = true
        
        var s = self
        s.value = true  // doesn't actually mutate `self`
    }
}

final class C: P {
    var _value: Bool
    init(value: Bool) {
        _value = value
    }
}

let c = C(value: false)
c.makeTrue()
print(c._value)  // false

Making the method mutating is sub-optimal as it would force you to make the object property mutable as well.

protocol P {
  init(value: Bool)
  var value: Bool { get set }
}

extension P {
  var value: Bool {
    get { fatalError() }
    set { self = Self.init(value: newValue) }
  }
}

extension P where Self: AnyObject {
  mutating func makeTrue() {
    self.value = true
  }
}

final class C: P {
  var _value: Bool
  init(value: Bool) {
    _value = value
  }
}

let c = C(value: false)
c.makeTrue() // Cannot use mutating member on immutable value: 'c' is a 'let' constant

So if set does not work, can this be made possible when modify becomes available for us to use? cc @John_McCall

This would actually be a change in behavior, because then class methods could re-assign 'self' to a different instance. Eg, if classes could have mutating methods,

let obj = Foo()
var mutableObj = obj
mutableObj.someMethod()
print(mutableObj === obj) // might print false
1 Like

modify does not change anything here, because if the setter is mutating on self, so is modify.

As @hamishknight explained earlier in the thread, this is working as intended because the implementation of the setter might swap out self for a different instance, so it would not actually be valid to call the setter on an immutable reference.