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

Gwendal Roué gwendal.roue at gmail.com
Fri Dec 11 11:44:39 CST 2015


I did report this problem as https://bugs.swift.org/browse/SR-142 a couple of days ago.

Greg Titus replied:

> Looked into this briefly, and it's a lot harder than just changing the error checking, since m() has an implicit `inout P` first argument. Probably the solution is an automatic version of Gwendal's workaround: declaring a hidden `var p: P = c` and calling the method on p.

Gwendal Roué

> Le 11 déc. 2015 à 18:22, Josh Avant via swift-evolution <swift-evolution at swift.org> a écrit :
> 
> Thanks, Kevin!
> 
> Look forward to any other feedback!
> 
> On Thursday, December 10, 2015, Kevin Ballard via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
> A couple of miscellaneous points:
>  
> * This problem only occurs when the class inherits a default implementation of the method from a protocol extension. If the class declares the method itself, then it's no longer a mutating method and everything works fine.
> * The problem exists because mutating functions are allowed to assign to self, but methods on class types cannot assign to self, they can only mutate the properties of self. This is why we cannot simply allow the call to the inherited mutating method, as that method may reassign self.
> * Classes can still call the method, they just have to say something like
>  
> var this = self
> this.callMutatingMethod()
>  
> Yeah it's a little awkward, but it's not really all that bad for an edge case like this.
>  
> Another potential workaround requires more work on the protocol side but allows implementations to not care about the difference, which is to provide a non-mutating variant in an extension restricted by Self: AnyObject:
>  
> protocol P {
>     var count: Int { get set }
>     mutating func foo() -> String
> }
>  
> extension P {
>     mutating func foo() -> String {
>         return _mutatingFoo(&self)
>     }
> }
>  
> extension P where Self: AnyObject {
>     func foo() -> String {
>         var this = self
>         return _mutatingFoo(&this)
>     }
> }
>  
> private func _mutatingFoo<T: P>(inout value: T) -> String {
>     value.count += 1
>     return "foo"
> }
>  
> Ultimately, I think there's some value in this proposal, but I worry about adding a new keyword to handle an edge case like this when there's workarounds available. Assuming we do add this feature, I'd suggest using something like `mutating(ish)` instead of inventing a brand new keyword (there's precedent in `private(set)`).
>  
> -Kevin Ballard
>  
> On Thu, Dec 10, 2015, at 02:35 PM, Josh Avant via swift-evolution 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 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 <javascript:_e(%7B%7D,'cvml','swift-evolution at swift.org');>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>  
> 
>  _______________________________________________
> 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/babbad80/attachment.html>


More information about the swift-evolution mailing list