29
struct MyStruct {
    var count = 0

    mutating func add(amount: Int) {
        count += amount
    }
}

var myStruct = MyStruct()

[1, 2, 3, 4].forEach(myStruct.add)
// Partial application of 'mutating' method is not allowed

Why isn't using forEach like this allowed for mutating methods? I'm aware that I could do

for number in [1, 2, 3, 4] {
    myStruct.add(number)
}

or

[1, 2, 3, 4].forEach { myStruct.add($0) }

instead, but neither are as clean as

[1, 2, 3, 4].forEach(myStruct.add)
Tim Vermeulen
  • 12,352
  • 9
  • 44
  • 63

3 Answers3

34

The key to a value type is that assignment creates a copy. This contract also affects how people can reason about their code. For example if you're passed an Int into a method, you can be confident that the value won't change out from under you, even if the original int passed in gets sent off to another thread and has calculations done elsewhere.

Same is true for structs. This is why in swift when defining methods that may alter 'self', if it's a value type you have to define it as 'mutating'. What this says is that it will simultaneously reassign to your variable with the new value. So for example When you call your add method with '3', you can think of it performing something similar to:

var myStruct = MyStruct()
var tmp = myStruct
tmp.count = tmp.count + 3
myStruct = tmp

Now the reason that you are hitting an error is because partially applying a mutating function would break that contract. If you are able to save a closure like let myStructAdd = myStruct.add, then at some later point you could call myStructAdd(3) and it would try to change myStruct. This would give reference semantics to a value type since now you have the power to alter myStruct at a later point, even from a different method.

In short, swift is giving you a convenience by providing 'mutating' methods for code readability, with the caveat that it has to happen all at once so as not to break the value type contract.

bobDevil
  • 27,758
  • 3
  • 32
  • 30
  • 2
    `let myStructAdd = myStruct.add` indeed doesn't work, but `let myStructAdd = { myStruct.add($0) }` does. How is this any different? It still allows me to call `myStructAdd(3)` to mutate the struct's value. – Tim Vermeulen May 27 '16 at 17:59
  • 1
    In that situation, swift can capture the entire struct in the closure. Swift will try to be nice and attempt to reassign to the same value if it doesn't lose scope, however if the original 'myStruct' does fall out of scope that's ok, the closure now just has a new variable which is a copy of it. With the partial application you still run into the problem that the closure is intrinsically tied to the original struct so having it fall out of scope would be an issue – bobDevil May 27 '16 at 18:09
  • 3
    It's a shame Swift isn't clever enough to notice that the closure parameter of `forEach` is an `@noescape` one, and therefore the closure won't be able to escape the scope of the method call, therefore meaning there shouldn't be any issues passing in a `mutating` function. – Hamish May 27 '16 at 18:23
  • 1
    I agree. Though swift is still changing. Previously they didn't allow partial application of *any* struct methods, and changed it to just mutating methods. They may add an exception to `@noescape` closures as well. It's similar in concept to the change they're making with regards to capture of 'inout' parameters only being implicitly allowed for `@noescape`. I like to keep an eye on https://github.com/apple/swift-evolution for these kinds of things. – bobDevil May 27 '16 at 18:39
  • Technically, couldn't they simply give `myStruct.add` the same behaviour as `{ myStruct.add($0) }`? i.e. that simply nothing would happen to the struct if it's out of scope. – Tim Vermeulen May 27 '16 at 19:09
0

This answer is only related to the error message, but not the code in the question.

I was appending to an array like this:

array.append[newElement]

And that was throwing me two errors:

Partial application of 'mutating' method is not allowed

Value of type '(__owned Int) -> ()' has no subscripts

I was getting the error, because I had to use () instead of []. Basically the first error was misleading to follow. Fixing the 2nd error message, resolved everything

The error went away after changing it to:

array.append(newElement)
mfaani
  • 33,269
  • 19
  • 164
  • 293
-1
    struct MyStruct {
    static var count = 0
    var amount: Int

    mutating func add() {
        MyStruct.count = MyStruct.count + amount
    }
}
var array = [1,2,3,4,5,6]
for i in array {
var callfunc = MyStruct(amount: i)
callfunc.add()
}
print(MyStruct.count) // count of list

you can also do it using static var in struct