> On Jan 3, 2018, at 6:45 PM, Slava Pestov <spes...@apple.com> wrote:
>> On Jan 3, 2018, at 5:33 PM, Hooman Mehr <hoo...@mac.com 
>> <mailto:hoo...@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.

Is this desirable? It seems that Swift compiler generally tries to prevent you 
from defining mutating methods on class types and this situation feels like a 
flaw/bug that needs fixing. I think this can even have security implications 
(letting a malicious coder obfuscate a code put in place to create a backdoor).

>> 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.

I don’t think a mutating default implementation makes any sense for a class 
type. I think we should handle default implementation for class type with a 
constrained/refined extension that provides a non-mutating default 
implementation for the cases when a class type conforms to a non-class bound 
protocol. This seems to be impossible at the moment, but I see it as another 
shortcoming in the compiler, not a source breaking change for the sake of 
providing an obscure new feature.

> 
> Slava
> 
>> 
>> Hooman
>> 
>>> On Dec 21, 2017, at 10:59 PM, Slava Pestov <spes...@apple.com 
>>> <mailto:spes...@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@swift.org <mailto:swift-evolution@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@swift.org <mailto:swift-evolution@swift.org>
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution 
>>>> <https://lists.swift.org/mailman/listinfo/swift-evolution>
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to