Assigning to 'self' in protocol extensions


#1

Hi all,

I just want to raise an issue.
I currently don't have any concrete ideas about it, but I believe we should
discuss this before ABI stabilization.
Default implementation for mutating methods

Swift protocol extension allows assigning to self in default
implementations for mutating methods:

protocol Resettable {
    mutating func reset()
    init()
}

extension Resettable {
    mutating func reset() {
        self = Self() // Assigning to 'self'.
    }
}

And you can:

class Foo : Resettable {
    var value: Int
    required init() {
        value = 0
    }
}

In this example, Foo class conforms to Resettable, by the default
implementation of mutating func reset(). However, this can cause an
unexpected behavior for classes:

var ref1 = Foo()
let ref2 = ref1
assert(ObjectIdentifier(ref1) == ObjectIdentifier(ref2))

ref1.value = 42
assert(ObjectIdentifier(ref1) == ObjectIdentifier(ref2))

ref1.reset()
assert(ObjectIdentifier(ref1) != ObjectIdentifier(ref2))
assert(ref1.value == 0)
assert(ref2.value == 42)

From the perspective of the caller, I think, this behavior is

counterintuitive because we use "reference types" with an expectation: the
referencing address would never be changed *unless* we explicitly replace
the object by re-assigning to the variable in call sites, e.g.,

var ref: Foo = Foo()
ref = Foo()

<https://gist.github.com/rintaro/e9d606e2a6d833a043cf43a9a3e14670#default-implementation-for-initializers>Default
implementation for initializers

Similar to methods, initializers also have this issue:

protocol HasDefault {
    static var _default: Self { get }
    init()
}

extension HasDefault {
    init() {
        self = Self._default // Here it is.
    }
}

final class Foo : HasDefault {
    let value: Int
    init(value: Int) {
        self.value = value
    }

    static var _default = Foo(value: 0)
}

let obj = Foo()
assert(obj.value == 0)

This behavior allows us to implement a kind of "factory initializer".

protocol Factory {
    init(factory: () -> Self)
}
extension Factory {
    init(factory: () -> Self) {
        self = factory()
    }
}

class Animal {
    var emoji: Character { return ":question:" }
}

class Cat : Animal {
    override var emoji: Character { return ":cat:" }
}

class Dog : Animal {
    override var emoji: Character { return ":dog:" }
}

extension Animal : Factory {
    convenience init(type: String) {
        self.init(factory: {
            switch type {
            case "dog": return Dog()
            case "cat": return Cat()
            default: return Animal()
            }
        })
    }
}

assert(Animal(type: "dog").emoji == ":dog:")

I believe this is *NOT* a right way of implementing factory initializers.
We should introduce a proper "factory initializer" syntax, as discussed in
this ML before.

Any thought?
Do we want to leave them AS IS in Swift4?


(Slava Pestov) #2

From the perspective of the caller, I think, this behavior is counterintuitive because we use "reference types" with an expectation: the referencing address would never be changed unless we explicitly replace the object by re-assigning to the variable in call sites, e.g.,

Well, there’s no real difficulty here, other than potential user confusion. The ‘self’ parameter for a mutating method is passed inout, so this behaves as if you called a global function with an inout argument. The difference is of course when you pass a non-self inout argument, the compiler requires you to use the explicit & syntax at the call site.

Is your proposal to ban calls to such mutating methods on a type that is known to be a reference type at compile time altogether? This will create an inconsistency between code that operates on concrete types and code that operates on generic parameters (in the latter case the compiler of course has no way to statically guarantee that the value is not a reference type).

The last time this quirk came up in internal discussions, the thought some of us had was that it might be worthwhile to prohibit classes from conforming to protocols with mutating requirements altogether. If you think about it, this makes some amount of sense — it seems like it would be quite hard to write code that can operate on both mutable values and mutable references generically, since the latter do not have value semantics:

var x = y
x.mutatingProtocolRequirement()
// did y change too?

However the discussion sort of fizzled out.

Perhaps we can resurrect it as a proposal, but the bar is pretty high for removing features at this point, since there’s no actual type soundness issue, just possible confusion.

var ref: Foo = Foo()
ref = Foo()
<https://gist.github.com/rintaro/e9d606e2a6d833a043cf43a9a3e14670#default-implementation-for-initializers>Default implementation for initializers

Similar to methods, initializers also have this issue:

In the specific case of initializers, my opinion here is the opposite in fact — I think assigning to ‘self’ should be permitted in all convenience initializers, even initializers defined directly classes, without the protocol extension trick. Also, we should lower this more efficiently than we do today, without creating a self ‘carcass’ that is allocated and immediately freed, to be replaced by the ‘real’ self.

We already have something like this in fact, it’s called ‘factory initializers', but it’s not directly exposed through the language. It is possible to import a static method or C function as a convenience initializer on a type. The Dispatch overlay uses this for example — DispatchQueue.init actually calls dispatch_queue_create(), which returns a new instance of the type, and not [[OS_dispatch_queue alloc] init] as you would expect if this was a vanilla Objective-C class. The code that gets generated here is similar to the protocol extension initializer example you show that assigns to ‘self’.

Slava

···

On Jan 19, 2017, at 10:52 PM, rintaro ishizaki via swift-evolution <swift-evolution@swift.org> wrote:


#3

Is your proposal to ban calls to such mutating methods on a type that is
known to be a reference type at compile time altogether? This will create
an inconsistency between code that operates on concrete types and code that
operates on generic parameters (in the latter case the compiler of course
has no way to statically guarantee that the value is not a reference type).

The last time this quirk came up in internal discussions, the thought some

of us had was that it might be worthwhile to prohibit classes from
conforming to protocols with mutating requirements altogether. If you think
about it, this makes some amount of sense — it seems like it would be quite
hard to write code that can operate on both mutable values and mutable
references generically, since the latter do not have value semantics:

var x = y
x.mutatingProtocolRequirement()
// did y change too?

However the discussion sort of fizzled out.

Perhaps we can resurrect it as a proposal, but the bar is pretty high for
removing features at this point, since there’s no actual type soundness
issue, just possible confusion.

Well, I'm not proposing anything for now.
I'm totally OK to leave it as is if the community think so.
If so, I just want to confirm that the language feature truly support this.
At least, I don't want to propose something that breaks source
compatibility.

Default implementation for initializers

Similar to methods, initializers also have this issue:

In the specific case of initializers, my opinion here is the opposite in
fact — I think assigning to ‘self’ should be permitted in all convenience
initializers, even initializers defined directly classes, without the
protocol extension trick. Also, we should lower this more efficiently than
we do today, without creating a self ‘carcass’ that is allocated and
immediately freed, to be replaced by the ‘real’ self.

I totally agree. And I think that is the least impact way on the language
specification.

···

2017-01-20 16:11 GMT+09:00 Slava Pestov <spestov@apple.com>:


(Russ Bishop) #4

Right now you could consider mutable vs non-mutable functions as “advisory” for reference types since it is up to you to fulfill that contract.

I suppose you could say that reference type members can’t do any stores to self unless the function is decorated in some way to advise the compiler that the mutation is intended but that’s a big change and not terribly useful. It would still be on the programmer to use the special decoration only in appropriate places that obey the mutability invariant (think a LRU cache doing a read() where mutation is not observable by the caller).

The alternate world where I can’t have value and reference types conform to the same protocol if the protocol has any mutating methods is a bit stifling.

IMHO in this case the consistency is more important than the quirk.

Russ

···

On Jan 19, 2017, at 11:11 PM, Slava Pestov via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 19, 2017, at 10:52 PM, rintaro ishizaki via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

From the perspective of the caller, I think, this behavior is counterintuitive because we use "reference types" with an expectation: the referencing address would never be changed unless we explicitly replace the object by re-assigning to the variable in call sites, e.g.,

Well, there’s no real difficulty here, other than potential user confusion. The ‘self’ parameter for a mutating method is passed inout, so this behaves as if you called a global function with an inout argument. The difference is of course when you pass a non-self inout argument, the compiler requires you to use the explicit & syntax at the call site.

Is your proposal to ban calls to such mutating methods on a type that is known to be a reference type at compile time altogether? This will create an inconsistency between code that operates on concrete types and code that operates on generic parameters (in the latter case the compiler of course has no way to statically guarantee that the value is not a reference type).

The last time this quirk came up in internal discussions, the thought some of us had was that it might be worthwhile to prohibit classes from conforming to protocols with mutating requirements altogether. If you think about it, this makes some amount of sense — it seems like it would be quite hard to write code that can operate on both mutable values and mutable references generically, since the latter do not have value semantics:


#5

In the specific case of initializers, my opinion here is the opposite in

fact — I think assigning to ‘self’ should be permitted in all convenience
initializers, even initializers defined directly classes, without the
protocol extension trick. Also, we should lower this more efficiently than
we do today, without creating a self ‘carcass’ that is allocated and
immediately freed, to be replaced by the ‘real’ self.

I totally agree. And I think that is the least impact way on the language
specification.

Oops, It seems I have to take back my words.
With my "factory initializer" example:

  let dog = Dog(type: "cat")
  type(of: dog) == Cat.self

results "true".