Can't assign to self in initializer in protocol extension when protocol inherits from NSCoding

Why am I not able to assign to self in this context? Is there a way around it?

protocol Test: NSCoding {
    
}

extension Test {
    init (equalTo other: Self) throws {
        self = other // Error: Cannot assign to value: 'self' is immutable
    }
}

Swift, in general, does not allow self-assignment for object types in initializers — in any regular class, you cannot write

class C {
    init(_ other: C) {
        self = other // error: Cannot assign to value: 'self' is immutable
    }
}

I don't have a citation handy, but I believe this is related to to Swift's initialization model for objects: unlike Objective-C, Swift couples allocation and initialization of objects so that you can't work with allocated-but-uninitialized objects; and IIRC, this has consequences for not wanting to pass in self as inout to allow self-assignment over an already-allocated object. I don't remember the specifics, but maybe someone has a reference handy.

There is a notable exception to this: self-assignment is allowed in implementations of protocol initializers, since structs can conform to protocols, and self-assignment is allowed for structs:

protocol Test {}

extension Test {
    init(with other: Self) {
        self = other // ✅ The concrete type could be a struct.
    }
}

To support this, Swift does have to do extra work to allow classes to conform to such protocols, by allowing effective inout passing of an object type into the initializer.

From what I remember, this is considered an undesirable hole in the language, that's supported by necessity. That changes, though, if the protocol is known statically to be constrained to object types:

protocol Test: AnyObject {}

extension Test {
    init(with other: Self) {
        self = other // ❌ The concrete type _cannot_ be a struct; error: Cannot assign to value: 'self' is immutable
    }
}

In your specific case, because NSCoding is an Objective-C protocol, and Objective-C protocols are inherently class-constrained, the compiler won't allow you to assign to self in the protocol.


In terms of workarounds: I hesitate to recommend this, but you might be able to add a parent protocol which isn't class-constrained, add an initializer to that, and inherit the NSCoding conformance in a concrete child protocol:

protocol TestBase {}
extension TestBase {
    init(with other: Self) {
        self = other
    }
}

protocol TestConcrete: TestBase, NSCoding {}