let and var indicate the mutability of the storage location of the variable itself. In the case of value types, the entire value is stored at the storage location (and so let prevents mutations of the value itself), whereas for reference types, only a reference to the value is stored in that storage location, while the actual storage for the instance is elsewhere (and is implicitly mutable). Thus, you can modify the instance through a class reference declared as a let, but you cannot modify the reference itself:
let foo1 = Foo(1)
foo1 = Foo(5) // error!
You also couldn't, for instance, pass foo to a parameter of type inout Foo:
I think the confusion arise from Swift use "." to access properties for both value and reference variables. C++ use a different operator pointer->member for reference variable, making the distinction very clear.
The mutating annotation cannot even be used directly on class methods, I am sure Swift not allow this for good reason. So what's a protocol mutating method when apply to a class mean? I'm very confused now.
That is true. mutating is not supported on classes because it violates a fundamental assumption about an object having identity. There is an implicit expectation about something that we say has identity: Calling methods on an object should not turn it into a different object. We expect the same object to continue to exist as we interact with it. Otherwise we can't reliably refer to the same object throughout an application.
but this principle can be violated simply with protocol implementation on class as your example show. Would love to learn why Swift not allow mutating on class func but allow on protocol on class? Seems like a compromise?
// error: protocol 'AssignSelf' can only be used as a generic constraint because it has Self or associated type requirement
var d: AssignSelf = MyClass()
So AssignSelf cannot be used for "object polymorphism", only in generic constraint ("parametric polymorphism", yes?). Will this limitation go away when Unlock existentials for all protocols land?
Yes, you should be careful when conforming a class to a protocol that includes mutating methods. You need to make sure the class can correctly support the protocol before conforming to it. You should not (normally) use the default implementation of any mutating protocol methods and implement them as non-mutating methods on the class.
The example I showed is considered a hack and not a recommended practice.
It is perfectly valid to design protocols that can support both value and reference types and you may need to use mutating methods to cover value types.
On the other hand, note that you can constrain protocols to class types, but can't constrain protocols to value types. The reason is: It is easy to tell if a type is a reference type, but very hard to tell if a type is a true value type.
A type defined using struct and enum may or may not be a true value type and a class can represent a value type. (In that case you should not rely on its identity and not use === checks on it.) That is why we talk about value semantics. We can't constrain a protocol based on semantic distinctions.
If a protocol can't support class (and soon actor) types, you need to document it and the clients of that protocol needs to follow the documentation.
This has nothing to do directly with the topic at hand. You can simply declare a concrete existential:
Sure, I have also mentioned above that you can use classes to create types with value semantic. At the physical level that compiler directly understands, reference types are well distinguished and are specially treated (reference counts, virtual method table, etc.). Even when semantically acting as a value type, an object is still a reference and needs all of those special treatments (maintaining reference count, etc).