Protocol func returning Self

31,733

Solution 1

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.

Solution 2

With Swift 2, we can use protocol extensions for this.

protocol Copyable {
    init(copy:Self)
}

extension Copyable {
    func copy() -> Self {
        return Self.init(copy: self)
    }
}

Solution 3

There is another way to do what you want that involves taking advantage of Swift's associated type. Here's a simple example:

public protocol Creatable {

    associatedtype ObjectType = Self

    static func create() -> ObjectType
}

class MyClass {

    // Your class stuff here
}

extension MyClass: Creatable {

    // Define the protocol function to return class type
    static func create() -> MyClass {

         // Create an instance of your class however you want
        return MyClass()
    }
}

let obj = MyClass.create()

Solution 4

Actually, there is a trick that allows to easily return Self when required by a protocol (gist):

/// Cast the argument to the infered function return type.
func autocast<T>(some: Any) -> T? {
    return some as? T
}

protocol Foo {
    static func foo() -> Self
}

class Vehicle: Foo {
    class func foo() -> Self {
        return autocast(Vehicle())!
    }
}

class Tractor: Vehicle {
    override class func foo() -> Self {
        return autocast(Tractor())!
    }
}

func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

let vehicle = Vehicle.foo()
let tractor = Tractor.foo()

print(typeName(vehicle)) // Vehicle
print(typeName(tractor)) // Tractor

Solution 5

Swift 5.1 now allow a forced cast to Self, as! Self

  1> protocol P { 
  2.     func id() -> Self 
  3. } 
  9> class D : P { 
 10.     func id() -> Self { 
 11.         return D()
 12.     } 
 13. } 
error: repl.swift:11:16: error: cannot convert return expression of type 'D' to return type 'Self'
        return D()
               ^~~
                   as! Self


  9> class D : P { 
 10.     func id() -> Self { 
 11.         return D() as! Self
 12.     } 
 13. } //works
Share:
31,733

Related videos on Youtube

aeubanks
Author by

aeubanks

Updated on November 06, 2020

Comments

  • aeubanks
    aeubanks over 3 years

    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?

    • Rob Napier
      Rob Napier over 9 years
      What do you mean by "nothing works?"
    • aeubanks
      aeubanks over 9 years
      The compiler complains when putting either C or Self as the return value unless the class is a final class
    • Rob Napier
      Rob Napier over 9 years
      OK, I've reproduced the errors, but when asking questions, you need to include the actual error that is returned. Not just "it gives errors" or "it doesn't work."
    • Rob Napier
      Rob Napier over 9 years
      The compiler is completely correct in its errors here, BTW. I'm just thinking about whether you can get the thing you're trying to do at all.
    • aeubanks
      aeubanks over 9 years
      So there's nothing like instancetype in Objective-C?
    • Rob Napier
      Rob Napier over 9 years
      That's not the problem. You can't call [[instancetype alloc] init] either.
    • aeubanks
      aeubanks over 9 years
      But you can call [[[self class] alloc] init]. So I guess the question is that is there a type-safe way to call the current class and call an init method?
    • newacct
      newacct over 9 years
      @aeubanks: Yes, you can do self.dynamicType(). The initializer you call must be declared required, because initializers are not always inherited in Swift.
  • newacct
    newacct over 9 years
    Hmm, they must have changed this. I could have swore that func copy() -> C worked in previous betas, and it was consistent because protocol conformance was not inherited. (Now it seems protocol conformance is inherited, and func copy() -> C does not work.)
  • fluidsonic
    fluidsonic over 9 years
    The last pure-Swift solution does not work with subclasses as they are required to implement init(copy: C) instead init(copy: Self) :(
  • chakrit
    chakrit about 9 years
    The last solution guarantees the return value to be Self but the initializer then all have to accept a variable statically typed to C which is to say, it's not much improvement to just returning AnyObject in the first place.
  • Rob Napier
    Rob Napier about 9 years
    I'd say it's still a big improvement over AnyObject, but it is true that it means that you have to be able to copy your superclasses (so there have to be default values for your subclass properties, or you have to use fatalError() or the like, which is ugly). The problem does go away if you get rid of the Copyable protocol. It all behaves as expected as long as you just implement init(copy:) without promising to so (but it also won't force you to implement init(copy:) at each layer. See stackoverflow.com/a/28795620/97337 and also devforums.apple.com/message/1086442
  • pronebird
    pronebird over 8 years
    In swift 2.0 you'd have to call init explicitly: self.dynamicType.init( ... )
  • gkaimakas
    gkaimakas over 8 years
    This is a great answer and that type of approach was discussed extensively on WWDC 2015.
  • Jeehut
    Jeehut over 8 years
    @RobNapier Thanks for the great answer but I don't understand the beginning yet. You say 'And I return a C, not a fully-initialized Self (which I promised)' but I thought a Self within the class C is the same as writing C, isn't it? So a C should be a Self in that context respectively, no? Doesn't seem so but I don't really understand why.
  • Rob Napier
    Rob Napier over 8 years
    @Dschee inside of C, Self could be C or a subclass of C. Those are different types.
  • jhrmnn
    jhrmnn about 8 years
    This should be the accepted answer. It can be simplified with return Self(copy: self) (in Swift 2.2 at least).
  • SimplGy
    SimplGy about 8 years
    Wow. compiles. That's tricksy, because the compiler won't let you just return Vehicle() as! Self
  • Fattie
    Fattie about 7 years
    Fascinating. I wonder if that relates to stackoverflow.com/q/42041150/294884
  • Fattie
    Fattie about 7 years
    that is mindboggling. Wow. Is what I'm asking here actually a variation on this?? stackoverflow.com/q/42041150/294884
  • werediver
    werediver about 7 years
    @JoeBlow I'm afraid it isn't. I'd say that to keep our minds safe we should know the return type exactly (i.e. not "A or B", but just "A"; otherwise we must think about polymorphism + inheritance + function overloading (at least).
  • freennnn
    freennnn about 7 years
    that's compiler tricking. Since overriding of foo() is not enforced, every Vehicle descendant without foo() custom implementation will produce obvious crash in autocast(). For example: class SuperCar: Vehicle { } let superCar = SuperCar.foo() . Instance of Vehicle can't be downcasted to SuperCar - so force unwrapping of nil in 'autocast()' leads to crash.
  • shawnynicole
    shawnynicole almost 7 years
    @freennnn Changing the code to the following does not crash when a subclass does not override foo(). The only requirement is that class Foo must have a required initializer for this to work as shown below. class Vehicle: Foo { public required init() { // Some init code here } class func foo() -> Self { return autocast(self.init())! // return autocast(Vehicle())! } } class Tractor: Vehicle { //Override is not necessary /*override class func foo() -> Self { return autocast(Tractor())! }*/ }
  • Josh at The Nerdery
    Josh at The Nerdery about 6 years
    This one does what I'm interested in. Thanks!
  • mfaani
    mfaani over 4 years
    FWIW for an enum it works. I wonder why: protocol Selfer { func back() -> Self } enum Letter: Selfer { case a case b func back() -> Letter { return Letter.a } } print(Letter.a.back()) // b
  • Mark A. Donohoe
    Mark A. Donohoe over 2 years
    Update: I think Swift properly lets you use Self as the return type now. There was a change in the compiler. I haven't had to use such a workaround in a while.