Compile error: partial application of 'mutating' method is not allowed: what is "partial application"?

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:

func bar(_ baz: inout Foo) {
    baz = Foo(100)
}
bar(&foo1) // error!

But if we have a class reference declared as var, then it works as expected:

var foo2 = Foo(2)
bar(&foo2)
print(foo2.x) // '100'
2 Likes

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.

Mutating methods enable assigning to self. Maybe a shorter example helps?

class MyClass: CustomStringConvertible {
    var description = "Something"
}

protocol AssignSelf {
    mutating func assign(_ newSelf: Self)
}

extension AssignSelf {
    mutating func assign(_ newSelf: Self) { self = newSelf }
}

extension MyClass: AssignSelf {}

let c1 = MyClass()
var c2 = c1
var c3 = MyClass() // Other instance
c3.description = "Something else"

//c1.assign(c3) // Error Cannot use mutating member on immutable value: 'c1' is a 'let' constant
c2.assign(c3)

print("c1:\(c1), c2:\(c2), c3:\(c3)")
2 Likes

I would say that mutating annotation allows modifying self.

In structs all stored properties are a part of self, so if you modify one of them, your method must be annotated with mutating

In classes stored properties are stored on a heap, in some other part of the memory, so the only way to modify self is to reassign another reference to it.

I have no idea why swift doesn't allow you to write mutating func f() {} on a class, but it cannot be anything fundamental, because you can achieve the same thing by writing more code.

There seems to be no way to implement assign(_:) directly on a class. Only possible going through protocol implementation. Why so?

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:

protocol AssignMyClass {
        mutating func assign(_ newSelf: MyClass)
}

The proposal for expanding protocol existential may not be able to support this particular case.

1 Like

It's not even that easy to tell if a type really has reference semantics.


protocol Movable {
    func moved(by vec: (x: Int, y: Int)) -> Self
    mutating func move(by vec: (x: Int, y: Int))
}

extension Movable {
    mutating func move(by vec: (x: Int, y: Int)) {
        self = self.moved(by: vec)
    }
}

final class Point: Equatable {
    let x, y: Int
    public init(_ x: Int, _ y: Int) {
        self.x = x
        self.y = y
    }
    public static func == (lhs: Point, rhs: Point) -> Bool {
        lhs.x == rhs.x && lhs.y == rhs.y
    }
}

extension Point: Movable {
    func moved(by vec: (x: Int, y: Int)) -> Self {
        Self(x + vec.x, y + vec.y)
    }
}

This class does not have reference semantics.

1 Like

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).

1 Like
Terms of Service

Privacy Policy

Cookie Policy