Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 917
aeubanks Avatar asked Sep 03 '14 13:09

aeubanks


8 Answers

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.

like image 66
Rob Napier Avatar answered Oct 05 '22 02:10

Rob Napier


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)
    }
}
like image 41
Tolga Okur Avatar answered Oct 05 '22 01:10

Tolga Okur


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()
like image 39
Matt Mendrala Avatar answered Oct 05 '22 00:10

Matt Mendrala


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
like image 39
werediver Avatar answered Oct 05 '22 01:10

werediver


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
like image 38
johnlinvc Avatar answered Oct 05 '22 00:10

johnlinvc


Following on Rob's suggestion, this could be made more generic with associated types. I've changed the example a bit to demonstrate the benefits of the approach.

protocol Copyable: NSCopying {
    associatedtype Prototype
    init(copy: Prototype)
    init(deepCopy: Prototype)
}
class C : Copyable {
    typealias Prototype = C // <-- requires adding this line to classes
    required init(copy: Prototype) {
        // Perform your copying here.
    }
    required init(deepCopy: Prototype) {
        // Perform your deep copying here.
    }
    @objc func copyWithZone(zone: NSZone) -> AnyObject {
        return Prototype(copy: self)
    }
}
like image 32
David James Avatar answered Oct 05 '22 00:10

David James


I had a similar problem and came up with something that may be useful so I though i'd share it for future reference because this is one of the first places I found when searching for a solution.

As stated above, the problem is the ambiguity of the return type for the copy() function. This can be illustrated very clearly by separating the copy() -> C and copy() -> P functions:

So, assuming you define the protocol and class as follows:

protocol P
{
   func copy() -> P
}

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

This compiles and produces the expected results when the type of the return value is explicit. Any time the compiler has to decide what the return type should be (on its own), it will find the situation ambiguous and fail for all concrete classes that implement the P protocol.

For example:

var aC:C = C()   // aC is of type C
var aP:P = aC    // aP is of type P (contains an instance of C)

var bC:C         // this to test assignment to a C type variable
var bP:P         //     "       "         "      P     "    "

bC = aC.copy()         // OK copy()->C is used

bP = aC.copy()         // Ambiguous. 
                       // compiler could use either functions
bP = (aC as P).copy()  // but this resolves the ambiguity.

bC = aP.copy()         // Fails, obvious type incompatibility
bP = aP.copy()         // OK copy()->P is used

In conclusion, this would work in situations where you're either, not using the base class's copy() function or you always have explicit type context.

I found that using the same function name as the concrete class made for unwieldy code everywhere, so I ended up using a different name for the protocol's copy() function.

The end result is more like:

protocol P
{
   func copyAsP() -> P
}

class C:P  
{
   func copy() -> C 
   { 
      // there usually is a lot more code around here... 
      return C() 
   }
   func copyAsP() -> P { return copy() }       
}

Of course my context and functions are completely different but in spirit of the question, I tried to stay as close to the example given as possible.

like image 35
Alain T. Avatar answered Oct 05 '22 02:10

Alain T.


Just throwing my hat into the ring here. We needed a protocol that returned an optional of the type the protocol was applied on. We also wanted the override to explicitly return the type, not just Self.

The trick is rather than using 'Self' as the return type, you instead define an associated type which you set equal to Self, then use that associated type.

Here's the old way, using Self...

protocol Mappable{
    static func map() -> Self?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> Self? {
        ...
    }
}

Here's the new way using the associated type. Note the return type is explicit now, not 'Self'.

protocol Mappable{
    associatedtype ExplicitSelf = Self
    static func map() -> ExplicitSelf?
}

// Generated from Fix-it
extension SomeSpecificClass : Mappable{
    static func map() -> SomeSpecificClass? {
        ...
    }
}
like image 36
Mark A. Donohoe Avatar answered Oct 05 '22 02:10

Mark A. Donohoe