Rob N
Rob N

Reputation: 16399

How to use Self for method return types in Swift?

This code produces Xcode error messages that lead you in a circle. Let's say I have a protocol called Marker and I'd like Markers to copy themselves. Here's a first guess...

protocol Marker {
    func copy() -> Self
}
class Marker1 : Marker {
    func copy() -> Self {
        return Marker1()   // error here
    }
}

(I'm not sure how to use Self correctly, because I can't find it in the The Swift Programming Language document. If you know where it's documented, please include that in answer.)

That code gives an error on the marked line: Cannot convert return expression of type 'Marker1' to return type 'Self' and it suggests a fix: Insert ' as! Self'.

I accept the fix:

...
    return Marker1() as! Self
...

This leads to another compiler error: 'Self' is only available in a protocol or as the result of a method in a class; did you mean 'Marker1'?

If I accept that "fix" it goes back to the original error. I'd call that a bug in Xcode. Let's try something else:

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

Another error: Method 'copy()' in non-final class 'Marker1' must return `Self` to conform to protocol 'Marker'

Making the class final does fix the error. But is there a way to do this without making a class final? And where is Self documented?

Upvotes: 4

Views: 2609

Answers (2)

Sulthan
Sulthan

Reputation: 130072

With such a hierarchy, you would have to make the class conforming to the protocol final:

protocol Marker {
    func copy() -> Self
}
final class Marker1 : Marker {
    func copy() -> Marker1 {
        return Marker1()
    }
}

The final is needed because when you don't apply final and you create a subclass Marker2: Marker1 then copy would not return the correct class Self any more.

You can workaround that by creating a required initializer and always create the correct instance:

protocol Marker {
    init()
    func copy() -> Self
}
class Marker1 : Marker {
    required init() {
    }
    func copy() -> Self {
        let copy = type(of: self).init()
        return copy
    }
}

(original code removed because does not work)

Related: Implementing copy() in Swift

Upvotes: 5

David Pasztor
David Pasztor

Reputation: 54706

The issue with your current implementation is that Self is an abstract type, so you cannot simply return a concrete type, such as Marker1 from a function with a return type of Self. If you changed the return type to Marker1, you'd need to make your class final to ensure that no subclass can override the method, since in a subclass, Self would correspond to the subclass type. However, this is still not good since in this case the subclass won't conform to the protocol, since in a subclass of Marker1, the return type of Marker1 is not the same as self.

You can solve this problem by getting the metatype of your current class using type(of: self), then calling a designated initializer on the metatype, which will indeed return an instance of type Self. The final step is to create a required initializer for your class to ensure that all subclasses need to implement the same initializer that's called in your copy() method in order to make copy work for subclasses as well.

protocol Marker {
    func copy() -> Self
}

class Marker1 : Marker {
    func copy() -> Self {
        let result = type(of: self).init()
        return result
    }

    required init() {}
}

Upvotes: 1

Related Questions