Inout variables in for-in loops

Hello Swift community! I'm here to discuss a new idea. I'm sorry if it was already discussed but if this was the case I wasn't able to find it.

I'm just suggesting the possibility to mutate the elements of an array inside for-in loops (and other flows maybe).
Basically my idea is to change this:

var dogs: [Dog] = //...
for (offset, dog) in dogs.enumerated() {
    if dog.age > 1 {
        dogs[offset].description = "grown"
    }
}

To this:

var dogs: [Dog] = //...
for inout dog in dogs {
    if dog.age > 1 {
        dog.description = "grown"
    }
}
18 Likes

Neat! I can see this applying to forEach too:

var dogs: [Dog] = //...
dogs.forEach { dog in
    if dog.age > 1 {
        dog.description = "grown"
    }
}

Another method using current language features:

var dogs: [Dog] = //...
for i in dogs.indices {
    { dog in
        if dog.age > 1 {
            dog.description = "grown"
        }
    }(&dogs[i])
}

Hi @lorenzofiamingo, this is a good idea that's been percolating as part of the Ownership Manifesto:

Parts of that vision for ownership are being reviewed even right now, with recent proposals on take and borrow. It's certainly exciting to contemplate how we might realize the remainder of that vision, including the mutating iteration feature that would very logically be spelled for inout.

10 Likes

For what it's worth, you can implement something like this now. Probably doesn't have everything you'd want out of a feature like this, but a good start IMO.

[update: Don't use this implementation! Use Nevin's version below.]

Don't use this
extension MutableCollection {
  func modifyEach(_ modify: (inout Element) throws -> Void) rethrows {
    for index in indices {
      modify(&self[index])
    }
  }
}

var ints = Array(1...10)
ints.modifyEach { $0 += 1 } 
1 Like

…and of course, since somebody has to say it, “You shouldn’t iterate over indices while modifying a collection, because that can cause an unwanted copy-on-write if indices holds a reference to the collection (as is the case with Dictionary.Values, for example).”

The right way to do this is:

extension MutableCollection {
  mutating func modifyEach(_ modify: (inout Element) throws -> Void) rethrows {
    var i = startIndex
    while i != endIndex {
      try modify(&self[i])
      formIndex(after: &i)
    }
  }
}

(Personally I lean toward naming this function transform, but that’s matter of taste.)

12 Likes

Ahhh, good call, thanks for pointing that out!