swiftprotocolssubclassingswift-protocols

Protocol func returning Self


I have a protocol P that returns a copy of the object:

protocol P {
    func copy() -> Self
}

and a class C that implements P:

class C : P {
    func copy() -> Self {
        return C()
    }
}

However, whether I put the return value as Self I get the following error:

Cannot convert return expression of type 'C' to return type 'Self'

I also tried returning C.

class C : P {
    func copy() -> C  {
        return C()
    }
}

That resulted in the following error:

Method 'copy()' in non-final class 'C' must return Self to conform to protocol 'P'

Nothing works except for the case where I prefix class C with final ie do:

final class C : P {
    func copy() -> C  {
        return C()
    }
}

However if I want to subclass C then nothing would work. Is there any way around this?


Solution

  • The problem is that you're making a promise that the compiler can't prove you'll keep.

    So you created this promise: Calling copy() will return its own type, fully initialized.

    But then you implemented copy() this way:

    func copy() -> Self {
        return C()
    }
    

    Now I'm a subclass that doesn't override copy(). And I return a C, not a fully-initialized Self (which I promised). So that's no good. How about:

    func copy() -> Self {
        return Self()
    }
    

    Well, that won't compile, but even if it did, it'd be no good. The subclass may have no trivial constructor, so D() might not even be legal. (Though see below.)

    OK, well how about:

    func copy() -> C {
        return C()
    }
    

    Yes, but that doesn't return Self. It returns C. You're still not keeping your promise.

    "But ObjC can do it!" Well, sort of. Mostly because it doesn't care if you keep your promise the way Swift does. If you fail to implement copyWithZone: in the subclass, you may fail to fully initialize your object. The compiler won't even warn you that you've done that.

    "But most everything in ObjC can be translated to Swift, and ObjC has NSCopying." Yes it does, and here's how it's defined:

    func copy() -> AnyObject!
    

    So you can do the same (there's no reason for the ! here):

    protocol Copyable {
      func copy() -> AnyObject
    }
    

    That says "I'm not promising anything about what you get back." You could also say:

    protocol Copyable {
      func copy() -> Copyable
    }
    

    That's a promise you can make.

    But we can think about C++ for a little while and remember that there's a promise we can make. We can promise that we and all our subclasses will implement specific kinds of initializers, and Swift will enforce that (and so can prove we're telling the truth):

    protocol Copyable {
      init(copy: Self)
    }
    
    class C : Copyable {
      required init(copy: C) {
        // Perform your copying here.
      }
    }
    

    And that is how you should perform copies.

    We can take this one step further, but it uses dynamicType, and I haven't tested it extensively to make sure that is always what we want, but it should be correct:

    protocol Copyable {
      func copy() -> Self
      init(copy: Self)
    }
    
    class C : Copyable {
      func copy() -> Self {
        return self.dynamicType(copy: self)
      }
    
      required init(copy: C) {
        // Perform your copying here.
      }
    }
    

    Here we promise that there is an initializer that performs copies for us, and then we can at runtime determine which one to call, giving us the method syntax you were looking for.