About reassigning self in classes

This is a spin-off from this thread.

As far as I can see, Swift seems to actively prevent us from reassigning self in classes:

class C {
    func reassignSelf(to other: C) {
        self = other // ERROR: Cannot assign to value: 'self' is immutable
    }
}

and

class C {
    mutating func reassignSelf(to other: C) { // ERROR: 'mutating' isn't valid on methods in classes or class-bound protocols
        self = other // ERROR: Cannot assign to value: 'self' is immutable
    }
}

So I'm assuming that we are not allowed to reassign self in classes (please correct me if I'm wrong).


But:

It is possible to reassign self in a class via a protocol's default implementation:

protocol P {
    mutating func reassignSelf(to other: Self)
}

extension P {
    mutating func reassignSelf(to other: Self) { self = other }
}

class C : P {
    var v: Int
    var addressStr: String { "\(Unmanaged.passUnretained(self).toOpaque())" }
    init(v: Int) { self.v = v }
}

func test() {
    let a = C(v: 1)
    var b = C(v: 2)
    let c = b

    print(a.v, a.addressStr) // 1 0x0000000100703c70
    print(b.v, b.addressStr) // 2 0x0000000100703c90
    print(c.v, c.addressStr) // 2 0x0000000100703c90
    print(a === b) // false
    print(a === c) // false
    print(b === c) // true

    b.reassignSelf(to: a)

    print(a.v, a.addressStr) // 1 0x0000000100703c70
    print(b.v, b.addressStr) // 1 0x0000000100703c70
    print(c.v, c.addressStr) // 2 0x0000000100703c90
    print(a === b) // true <-- These are now referencing the same instance.
    print(a === c) // false
    print(b === c) // false
}
test()

So, while Swift disallows reassigning self in classes, it allows us to do just that (I guess even accidentally) via a default implementation of some protocol ...

It would be interesting to learn more about the rationale behind this seemingly inconsistent behavior, that is:

  • Why is it important to prevent reassigning self in classes?

  • Why is it not prevented to (even accidentally) do so via a protocol's default implementation?

3 Likes

This is discussed at length here and here and here and here.

1 Like

Thanks for providing those links! I've read them through and they are mostly about the problem as formulated in the forked thread, and while they provide answers to the problem from that perspective, they don't quite answer the specific questions of this thread, namely:

Given the program in the OP (which demonstrates that it is possible to reassign self in classes, and that the way it is done is such that it might happen accidentally):


I've quoted the parts of the linked discussions that comes closest to answering these questions here:


So what I'm saying is that after reading and understanding the above (and the other parts of the linked discussions) I've yet to see the rationale for the seemingly inconsistent behavior demonstrated by the program in the OP. That is:
Why does Swift allow a default implementation of a protocol to reassign self of a class from under our feet (while actively preventing us from doing it explicitly within the class)?

2 Likes

I feels like this might be part of the reason.

Reassigning self in class may mean 2 things:

  • Change only the reference calling the function
  • Change all references referring to the class

Which ever we choose is gonna cause confusion to the other half (not to mention complexity to do the latter).

Reassigning self in protocol doesn’t seem to have as many interpretation.

Yes, this:

answers the first question:

But not the second:

If the following is your answer to that second question:

Then I can't see what you mean by that.

To me

  • reassigning in class may mean this reference or all reference, but
  • reassigning protocol may only mean this reference (since it may apply to value type).

But the effect is the same (see demonstration program in OP) no matter if the reassignment is done via a default protocol implementation of a method, or if it had been allowed in a method on the class.

And unless I'm misunderstanding the following:

The answer from @jrose seems to suggest that assigning self of a class (no matter how) is something that is meant to be left out of the language.

But it's not left out of the language (as shown by the demonstration program). And I'd like to know if it is

  1. Expected behavior, working as designed (and if so, why allowing this indirect form that can happen accidentally via a default implementation of a protocol, while disallowing the explicit form in the class)?
  2. An unfortunate by-product that can't be helped
  3. A bug (ie it shouldn't be allowed, because it's meant to be "left out of the language to avoid introducing a point of confusion").

Not really, what if mutating in class reassign all references pointing to it (which frankly, is more or less the same as non-mutating function, but there will be people thinking like that for sure).

Alternatives would be:

  • Disallow P.reassignSelf specifically for class types.
  • Disallow any class types to conform to P.

None seems to be obviously better choice.

Probably need the core team to answer that. Anyhow, it’s a good thought exercise. :hugs:

1 Like