[swift-evolution] Preserving non-mutability of methods of an existential or generic object

Slava Pestov spestov at apple.com
Wed Jan 3 20:45:40 CST 2018



> On Jan 3, 2018, at 5:33 PM, Hooman Mehr <hooman at mac.com> wrote:
> 
> Thank you Slava, it is a very insightful answer. 
> 
> It also reveals a potential source for hard to track bugs. To make it easier to see, lets add state to class C:
> 
> class D: C { var state = 0 }
> 
> var d = D() // Bad but common habit of declaring objects as var
> for i in 1...5 { d.f(i); d.state += 1 }
> print(d.state) // Prints 1
> 
> The result is surprising because a typical programmer does not expect an object to have mutating methods (that replace the object itself with a different one).
> 
> I think this is a bug in Swift compiler. It should not let a class “inherit” a mutating method in this manner. Compiler should require a non-mutating declaration of `f()` to satisfy `P` conformance for a class type.

Perhaps, but it’s also possible to define a mutating method g() in a protocol extension of P, where g() is not a protocol requirement. Calling g() on a class instance can “replace” self in this manner also.

> If we fix the above, it should be possible to do what I asked in my post: When compiler knows an existential is an object, it should know all methods are non-mutating and accept `let` declaration despite calls to nominally mutating methods. Also, if a protocol refines another protocol to be class-bound, compiler should automatically refine all of its inherited mutating methods to be non-mutating and allow `let` declaration of an existential of that protocol even if there are calls to those originally mutating methods. 

This will prevent you from defining default implementations of protocol requirements in protocol extensions, which would be a source-breaking change at this point. I don’t think we can make such a change now.

Slava

> 
> Hooman
> 
>> On Dec 21, 2017, at 10:59 PM, Slava Pestov <spestov at apple.com <mailto:spestov at apple.com>> wrote:
>> 
>> Hi Hooman,
>> 
>> Since the protocol P is not class-bounded, the requirement can be witnessed by a protocol extension method which re-assigns ‘self’:
>> 
>> protocol Initable {
>>   init()
>> }
>> 
>> extension P where Self : Initable {
>>   mutating func f(_ x: Int) -> Int {
>>     self = Self()
>>     return x
>>   }
>> }
>> 
>> class C : P, Initable {
>>   required init() {}
>> }
>> 
>> Now imagine you could do this,
>> 
>> let x: P & AnyObject
>> 
>> x.f(12)
>> 
>> This would be invalid because ‘x’ is a let binding but the requirement ‘f’ is witnessed by the protocol extension method, which performs a mutating access of ‘self’.
>> 
>> Slava
>> 
>>> On Dec 21, 2017, at 6:01 PM, Hooman Mehr via swift-evolution <swift-evolution at swift.org <mailto:swift-evolution at swift.org>> wrote:
>>> 
>>> The title is confusing, let me clarify by example:
>>> 
>>> We have this protocol with a mutating method:
>>> 
>>> protocol P { mutating func f(_ x: Int) -> Int }
>>> 
>>> And a conforming class (which has to conform with a non-mutating method):
>>> 
>>> class C: P { func f(_ x: Int) -> Int { return x } }
>>> 
>>> An instance of this class can be used with a let constant:
>>> 
>>> let c = C()
>>> c.f(1) // OK
>>>  
>>> If we make it an existential object conforming to P, the immutability of the method will be erased:
>>> 
>>> let c: AnyObject & P = C()
>>> c.f(1) // Cannot use mutating member on immutable value: 'c' is a 'let' constant
>>> 
>>> A generic context has the same issue:
>>> 
>>> func f<T: AnyObject & P>(_ arg: T)-> Int { return arg.f(1) } // Cannot use mutating member on immutable value: ‘arg' is a 'let' constant
>>> 
>>> My question: 
>>> 
>>> Is it too much work to preserve method non-mutability in in these cases?
>>> 
>>> The workaround I am using is this:
>>> 
>>> protocol Q: class, P { func f(_ x: Int) -> Int } // 'Refine' it to be non-mutating.
>>> extension C: Q {}
>>> 
>>> // Now these work:
>>> let c: Q = C()
>>> c.f(1) // OK
>>> func f<T: Q>(_ arg: T)-> Int { return arg.f(1) } // OK
>>> 
>>> This workaround creates a lot of duplication and is hard to maintain. It is not something that I do often, but when I do, it is pretty annoying. 
>>> 
>>> Supplemental questions:
>>> 
>>> Have you guys ever ran into this?
>>> Is there already a bug tracking this? 
>>> 
>>> 
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
> 

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


More information about the swift-evolution mailing list