[swift-evolution] Proposal: Intermediate mutation qualifier for protocol functions on reference-types

Slava Pestov spestov at apple.com
Fri Dec 11 13:34:41 CST 2015


Hi Josh,

> On Dec 10, 2015, at 2:35 PM, Josh Avant via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Currently, when a reference-type adopts a protocol with a function declared as `mutating`, the reference-type's implementation cannot call that function internally. This is because the compiler enforces an immutable `self` pointer value, and the `mutating` qualifier implies that the function implementation may mutate that `self` pointer value.
> 
> However, there seems to be a number of fairly reasonable situations where a reference-type implementation of these `mutating` functions may only want to mutate properties owned by `self`, but not the actual `self` pointer value.
> 

Consider this usage of the code you show below:

var s: RandomDataTransformable = NumberSource_Struct()
var ss = s

s.addRandomData()
// ss.data unchanged

var c: RandomDataTransformable = NumberSource_ClassDeclaration()
var cc = c

c.addRandomData()
// cc.data changed!

It seems it would be difficult to write fully generic code with a protocol that has this behavior, because you cannot rely on value semantics at all. To implement ‘mutating’ class methods correctly, you would in fact have to create a new class instance and re-assign to self every time, to preserve old references, since mutating the old value might break users of the protocol that are written as if the witness was a value type. This seems to defeat the whole purpose of using a reference type in fact.

It seems that if you really know what you’re doing, you can define a one-element struct that conforms to the protocol and contains the reference. This is in fact how Array, Dictionary and Set implement copy-on-write under the hood.

For this reason I’m in favor of going in the opposite direction, and prohibiting classes from conforming to protocols with mutating requirements.

Now that I think about it, setters in protocols have the same issue with confusing behavior from code that expects value semantics. Hmm...

Slava

> Consider this toy example:
> 
> ```
> import Foundation
> 
> protocol RandomDataTransformable {
>     typealias TransformableType
>     var data: [TransformableType] { get set }
>     
>     mutating func addRandomData()
> }
> 
> extension RandomDataTransformable where TransformableType == Int {
>     mutating func addRandomData() {
>         let random = Int(arc4random_uniform(6) + 1)
>         data.append(random)
>     }
> }
> 
> 
> /////
> 
> // VALID
> struct NumberSource_Struct : RandomDataTransformable {
>     typealias TransformableType = Int
>     var data: [Int] = []
>     
>     mutating func addData() {
>         addRandomData()
>     }
> }
> 
> 
> // VALID
> class NumberSource_ClassDeclaration: NSObject, RandomDataTransformable {
>     typealias TransformableType = Int
>     var data: [Int] = []
> }
> 
> var numberSource = NumberSource_ClassDeclaration()
> numberSource.addRandomData()
> 
> 
> // INVALID
> class NumberSource_ClassImplementation: NSObject, RandomDataTransformable {
>     typealias TransformableType = Int
>     var data: [Int] = []
>     
>     func addData() {
>         self.addRandomData() // Compiler Error: Cannot use mutating member on immutable value: 'self' is immutable
>     }
> }
> ```
> 
> Even despite the fact that the default implementation for `addRandomData` does not mutate the `self` pointer value, reference-type implementations are unable to call that function internally, since it is marked as `mutating`.
> 
> Perhaps confusingly, `addRandomData` may be called by externally, by objects which own instances of the reference-type (even though, again, it may not called internally by the implementation, itself).
> 
> Currently, the only solution to allow reference-type implementations to call the sample `addRandomData` implementation internally is to qualify the whole `RandomDataTransformable` protocol as `class`. The downside here is that this takes an otherwise perfectly reference- and struct-compatible protocol + extension implementation and restricts it to only apply to classes, decreasing overall code reusability.
> 
> My proposal would be to introduce an intermediate mutation qualifier that applies when protocols are adopted by reference-types. The qualifier would specify that the `self` pointer value itself may not be mutated, but `self`'s properties may be, as appropriate.
> 
> Thoughts, feedback on this?
>  _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151211/c559e7c7/attachment.html>


More information about the swift-evolution mailing list