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