Comments inline
A “mutating” keyword on a protocol method is only useful if
you can reason about the mutation in generic code.protocol P {
mutating func changeMe()
func noChange() -> Int
}In other words, given x of some type conforming to P, it should be
meaningful that this is an error:func f<T: P>(x: T) {
immutable.changeMe() // can't mutate a "let" binding
}
Agreed.
which means that you shouldn't be able to get around that meaning by
writing:func g<T: P>(x: T) {
var mutable = x
mutable.changeMe() // OK
}
Now I am confused. Why should this be illegal? Doesn’t this work with structs right now? (you are working on a mutable copy)
Also, you should be able to reason that both of the following print the same
thing twice, for types whose methods have no external side-effects:func gg<T: P>(x: T) {
print(x)
x.noChange()
print(x)
}func ggg<T: P>(x: T) {
print(x)
var y = x
y.changeMe()
print(x)
}
I see the issue here. Though, it seems to me it is just a more easily discoverable version of an entire class of problem with reference types in general. X could be changed on a different thread too, for example. or in a nested function call.
You would also still have the same problem with 'let y = x'.
When T is a class type, it can easily violate *all* of these
expectations. In other words, classes naturally bypass the mutation
model.If we are going to maintain source stability after Swift 3, it seems
that we either need to address this now, or decide that it won't be
addressed, because of the “viral const” problem.One idea that Jordan and I have floated is that protocols with mutating
methods should be constrained to applying to non-class types. That
would be a step in the right direction, but, that still leaves cases
like gg able to easily violate expectations when the protocol in
question has no mutating methods.
I really *dislike* the approach of disallowing class types for protocols with mutating methods, unless an additional reference type is added. I have several protocols which have conforming classes and structs and that that lets you choose reference vs value semantics.
I would much rather have us mark class methods as mutating when they change the class’s value, and just having the concept be separate from let/var in that case.
Another possibility would be to formalize the idea of value semantics in
protocol declarations, so that non-class protocols were only allowed to
apply to values.
I would like to have a way to require value semantics in a protocol (similar to how we can require ‘class' now). I still really want/need the ability to have a protocol which can be adhered to by both value and class types though...
It's also possible that I've overestimated the seriousness of the issue
and we actually can afford to postpone thinking about it until after
Swift 4.Thoughts?
I would vote to postpone to swift 4, and have it be part of a larger discussion involving the marking of side-effects and thread safe mutability.
If you need a stop-gap for Swift 3, I would be in favor of adding the ability to mark a particular protocol as needing to be a value type (not every protocol… just those that are marked as such). That should give you the guarantees you need for particular projects.
Thanks,
Jon