On the Quest of Finding Out Why Value Type Methods Need Mutating Keyword

This topic has been confusing me for a long time. Searching for answers online has yielded many explanations, which were often debated or disproved in subsequent posts. However, during my searches, I started to form my own reasoning. It centers around the idea of two types of identity, object identity and value identity.
For variables that are of reference type, any two variables that have the same value are considered to have the same value identity. However, this does not mean that they are the same object. It could be two distinct objects or a single object shared by both variables. So reference type variables have these two identities that are somewhat independent. For value types, of course it has value identity but it does not have object identity. At least, its object identity is trivial because, by definition, value types are always copied, ignoring COW optimization. That means any two value type variables must be two different objects. So concepts such as “object” and “object identity” are trivial to value types. Let’s consider the following case:

 W { V, R }

W is a value type that consists of a value type, V, and a reference type, R. When W has a method that changes a property of V, it changes value identity of W too. However, if W has a method that changes a property of R, does it change the identity of W? Since changing a property of R does not change the reference but only the content being referenced, the answer is NO. Now, what if W has a method that changes R as a whole? This change does indeed change the identity of W too, just like a value type! To summarize:

W.V.someProperty = newValue // DOES change W; Needs mutating keyword
W.R.someProperty = newValue // Does NOT change W; No need for mutating
W.R = newR                  // DOES change W; Needs mutating keyword

To test, you can create a W like structure and use it in @State from SwiftUI. SwiftUI does NOT react to the changes happened in the second one and DOES react to the changes in the first and third one! So changing value to value type and reference type have drastically different implications. That is, to value types, it changes them completely! But for reference type, it still has its object identity unchanged. So is this the reason why mutating keyword is needed? I do not know because it might also be due to the difference in compilation process caused by this or other differences.
Thanks for reading to this far, I just want to share what I learned and please feel free to give me any feedbacks!

Edit: Cleared up my conclusion.

Methods in Swift are essentially functions that take an implicit self parameter. You can think of these two func definitions as equivalent:

extension Int {
  func modify() {
  }
}

func modify(self: Int) {
}

Functions parameters are immutable, so you can’t mutate them inside the function unless you make them inout.

func modify(self: Int) {
  self += 1 // Error
}

func modify(self: inout Int) {
  self += 1 // OK
}

mutating is just a different spelling of inout for the implicit self parameter.

extension Int {
  func modify() {
    self += 1 // Error
  }
  
  mutating func modify() {
    self += 1 // OK
  }
}

(And as you’ve already identified, the value of a variable of reference type is the reference itself, not the object that it points to. That’s why methods on reference types don’t need mutating; even if they mutate the object, they never mutate the self parameter, i.e. the reference to the object.)

20 Likes

Great explanation!

I just wanted to add that in fact a class can conform to a protocol whose protocol extension declares a mutating method, and then it behaves as you would expect - you can re-assign the self parameter to another instance:

class C: P {}
protocol P {}
extension P {
  mutating func f() {
    self = …
  }
}

var c = C()
c.f()

Not recommending this, though ;-)

9 Likes

Hi Mister! I really want to learn more about Swift and how it works under the hood. I feel pretty limited by the quality of answers I can find on Google, and some of them are just wrong but somehow got popular. For example, this topic has many different versions of answers online, and I'm just bombarded by different explanations. So, to be more autonomous in finding the truth about Swift, I'm thinking about learning LLVM. My background is C/C++ from a system programming course and some basic assembly/hardware from a computer organization course. For self-learning, what resources would you recommend to learn about LLVM for better exploring Swift? Thanks <3

IMHO this is a hack and a flaw. And even worth fixing, ideally. Unfortunately too much code rely on this mis-feature already.

:flushed: oh that can’t be good. Can it be good?! I’m reading newer comments below that this is used, too!

I think I get what you mean now! It's the array, right? Because for a while before any write happens to a copy of an array, it's referencing the same array content on memory. So value identity and object identity do not corresponds 100% to hardware/memory representation. Part of them is also maintained by Swift runtime.

Please correct me if I am wrong, but a variable of a reference type contains a reference to an instance of the corresponding type. Therefore, if two variables of the same reference type have the same value, they reference (or point to) the same object.

@main
enum Test {
    static func main () async  {
        let u = A ()
        print (u.isEqual (to: u))          // yes
        print (u.isEqual (to: A ()))       // yes
        print (u.isEqual (to: B ()))       // no
        print (u.isEqual (to: B (v: u.u))) // maybe

        let v = u
        print (v.isEqual (to: u))  // yes
        print (u.isEqual (to: v))  // yes
        
        class C {
        }
        print (u.isEqual (to: C ())) // no
    }
}

enum Fuzzy {
    case yes, no, maybe (probability: Double)
    
    protocol Equatable: AnyObject {
        func isEqual (to: AnyObject ) -> Fuzzy
    }
}

class U: Fuzzy.Equatable {
    let u: Int
    
    init (u: Int) {
        self.u = u
    }
    
    func isEqual (to it: AnyObject) -> Fuzzy {
        guard let it = it as? U else {
            return .no
        }
        
        if it === self {
            return .yes
        }
        else {
            return is_equal (to: it)
        }
    }
    
    // Subclasses to override
    func is_equal (to it: U) -> Fuzzy {
        .no
    }
}

class A: U {
    init (v: Int = 2) {
        super.init (u: v)
    }
    
    override func is_equal (to it: U) -> Fuzzy {
        return self.u == it.u ? (((it as? Self) != nil) ? .yes : .maybe (probability: 0.5)) : .no
    }
}

class B: U {
    init (v: Int = 3) {
        super.init(u: v)
    }
    
    override func is_equal (to it: U) -> Fuzzy {
        return self.u == it.u ? ((it as? Self) != nil ? .yes : .maybe (probability: 0)) : .no
    }
}

1 Like

Sorry for the confusion. When I said "two (reference type) variables of the same value", the "value" here is the value of the instances referenced by those variables. You are definitely right in that a value of a reference type is the reference itself which, in turn, points to an instance and should NOT be the value of the instance. I mixed two things as one because I was not careful enough and not clear enough with definitions of terms.
Hope this confusion did not costs too much of your time. This is my first time explaining something online and was not expecting how different and difficult it would be. I usually do it in person with my friends and they can ask for clarifications on the spot. I guess that really allowed me to keep my bad habits. Will keep a note of that :smile: !

1 Like