0

Here's a simple playground example:

class Foobar {
    var name = ""

    init(name:String) {
        self.name = name
    }

    func modifyName(modification:String) {
        self.name += modification
    }
}

let list = [Foobar(name:"I"), Foobar(name:"Me"), Foobar(name:"Myself")]

for each in list {
    each.modifyName(" rules")
}

A class with name variable, and a single function that can modify that variable. Make a list of them, now use a for in to exercise the modifying function.

BUT. If I refactor the modifyName variable into a protocol extension:

class Foobar {
    var name = ""

    init(name:String) {
        self.name = name
    }
}

protocol Modifiable {
    var name:String { get set }
}

extension Modifiable {
    mutating func modifyName(modification:String) {
        self.name += modification
    }
}

extension Foobar:Modifiable { }

Then the for in will no longer work because the Modifiable extension needed to annotate the modifyName as mutating. The error emitted is error: cannot use mutating member on immutable value: 'each' is a 'let' constant. There's a simple workaround, but it feels like I'm abusing something:

for each in list {
    var each = each
    each.modifyName(" rules")
}

I've had the same problem with functions that have an argument that has been tagged with annotating. It used to be the case that you could mark those parameters with var in the arg list, but that is deprecated in the latest versions of swift. The same single line workaround works there. Is there a better more idiomatic way to do this? What originally started this, was simply extracting some common behavior into protocol extensions, rather than an inheritance tree. It feels like I'm paying a penalty for that choice.

Travis Griggs
  • 21,522
  • 19
  • 91
  • 167

1 Answers1

4

No, your single-line workaround doesn't necessarily work. Your protocol does not require that conforming types are reference types (classes). If a struct conformed to your protocol, and were in the array, var each = each would make a copy, and modifying the copy would not modify the value in the array.

You could require your objects to be classes, protocol Modifiable: class, in which case I believe mutating will no longer be allowed (or, at least your code will work properly).

But presuming you want to keep mutating, you can do this to modify the elements in place:

for i in list.indices {
    list[i].modifyName()
}

Of course, your array will also have to be var, because it's also a struct with value semantics.

jtbandes
  • 115,675
  • 35
  • 233
  • 266