Mutating Function on Class Type (Possible bug?)

I'm building a path library that has the following protocol:

public protocol Ownable {
    var owner: uid_t { get set }
    var group: gid_t { get set }
    var ownerName: String? { get set }
    var groupName: String? { get set }
    mutating func change(owner uid: uid_t, group gid: gid_t) throws
}

(The default protocol implementation of all the setters basically just calls the change function)

Then I have another protocol which inherits from Ownable:

public protocol Path: Creatable, Deletable, Movable, Ownable /*, etc...*/ {
    public mutating func change(owner uid: uid_t, group gid: gid_t) throws {
        guard chown(string, uid, gid) == 0 else {
            // Just reads the `errno` and generates the corresponding error
            throw ChangeOwnershipError.getError()
        }
    }
}

Then I have this:

open class DirectoryPath: Path {
    /*
    lots of implementation
    */

    public func changeRecursive(owner uid: uid_t, group gid: gid_t) throws {
        try change(owner: uid, group: gid)

        // Get directory contents and change all of their ownerships recursively...
    }
}

The line try change(owner: uid, group: gid) fails to compile with the error:
cannot use mutating member on immutable value: 'self' is immutable
Since this is a class I obviously can't mark the changeRecursive function mutating.

I found a number of posts that seem to describe the exact error I'm running into:

It seems this is caused by having a default implementation marked mutating and so I can't directly call it in a reference type that conforms to the protocol.

Unfortunately, those threads haven't been touched in 2-3 years and A LOT has changed in swift in the past few years. The second link has a few workarounds, unfortunately they all feel sorta hack-ish and I'd rather not do them if I can avoid it. I want it to just work the way I have it, duh ;)

That being said, does anyone know if this is a bug that should be filed (again?) or something that can be re-looked at to see if it can be fixed in the compiler? Or even if there's just a better workaround.

It's "correct" behavior because the default implementation is allowed to go self = someCompletelyDifferentObjectOfTheSameType, and that would reassign the reference at the call site. See SR-142 for some more discussion.

Even beyond this, mutating and classes don't play so well together. It may not be the best model for what you're trying to do.

This is my other more recent thread that contains some useful information from @Slava_Pestov:

1 Like

Thanks y'all for the information! I think I will do some slight redesigning to get it working the way I want. Probably just by removing the mutating keyword on the protocol requirements since I'm working with all classes. I started with structs originally, but because I'm working with file handles and some of the ways I'm doing things I don't think I could switch back to structs (at least not easily).

Why not simply treat mutating differently and specially—as if it is not there—in reference types? We know it is only specified for value type adopters since reference types are always mutable. As to self being reassigned in the default implementation, I feel we should have to a way to specify that the protocol is only for value types and disallow such reassignments in not-value-type-only implementations.

Until we have the "this protocol is only for value types" part, we can't do the "treat mutating as if it is not there in reference types" part. It's also not obviously a good idea since an immutable class type is pretty much indistinguishable from a value type except for ObjectIdentifier (and ===).

(A long time ago @dabrahams and I talked about disallowing classes from conforming to protocols with mutating requirements, which is similar. But at this point it'd be massively source-breaking to do so.)

1 Like

One thing I have wondered about related to this issue is why Swift does not allow writing:

class Foo {
  mutating func bar() {
    self = Foo()
  }
}
1 Like

We could allow it, sure. But it would only change this reference, not any other references pointing to the same object, and not having mutating doesn't prevent you from changing stored properties (see Avoiding unbreakable reference cycle with value types and closures? - #17 by jrose). So it's left out of the language to avoid introducing a point of confusion.

Have you come across a situation where you would have wanted this?

1 Like

No, I just noted it would be a generally consistent and unambiguous extension of the current meaning.