Mutating for loop error, "Left side of mutating operator isn't mutable"

Consider the following examples.

var alphabet = ["a", "b"]
for letter in alphabet {
  letter += " is a letter"
}
var alphabet = ["a", "b"]
alphabet.forEach { letter in
  letter += " is a letter"
}

Both snippets produce the same error, Left side of mutating operator isn't mutable: 'letter' is a 'let' constant.

Re-assigning the let constant to a var variable resolves the error but does not persist changes to the original array.

Is there a construction for a mutating for loop, or are we left with iterating over indices, ie.

for i in 0..<alphabet.count {
  alphabet[i] += " is a letter"
}

?

There is none as of yet. Though a few designs came up in the past.

1 Like

Previous related topics in Swift Evolution:

In-place map for MutableCollection (March 2018)
Idea: mutatingForEach for Collections (November 2018)

1 Like

This strikes me as something that is more or less by design. Especially in an instance like this, but really in most instances, iterating over a set of value types with the intent to mutate them imperatively is a recipe for somewhat opaque, difficult to debug, and difficult to optimize code (both from the programmer's perspective and the compiler's). There are all kinds of scenarios where you could pull the metaphorical rug out from under yourself or have values that don't make sense because of the way your iteration was handled.

Instead of coming at this situation from an imperative perspective, are you able to come at it instead from a more functional one by using map? That strikes me as the Swifty approach here.

2 Likes

Currently, iterating over indices is the correct solution. What you wrote will work for Array. However, there are some important reasons that it will not work for other collections.

Most collections do not have 0-based indices, so iterating over 0..<count is wrong. For example, if you are working with an ArraySlice, such as alphabet[5..<10], then the indices are actually 5..<10 (slices share indices with their base collection).

It would be tempting to iterate over the indices property, but you should not do that either, because some collections such as Dictionary.Values are retained by their indices, and thus mutating the collection while iterating over indices would make an extra copy of the entire collection.

The correct, general solution looks like this:

var i = alphabet.startIndex
while i != alphabet.endIndex {
  alphabet[i] += " is a letter"
  alphabet.formIndex(after: &i)
}

If you want to encapsulate this functionality into a method available on all mutable collections, it would look like this:

extension MutableCollection {
  mutating func mutateEach(transform: (inout Element)->Element) {
    var i = startIndex
    while i != endIndex {
      transform(&self[i])
      formIndex(after: &i)
    }
  }
}

Then you call it like this:

alphabet.mutateEach{ $0 += " is a letter" }
4 Likes