MightyNicM February 2016

Swift: Overriding Self-requirement is allowed, but causes runtime error. Why?

I just started to learn Swift (v. 2.x) because I'm curious how the new features play out, especially the protocols with Self-requirements.

The following example is going to compile just fine, but causes arbitrary runtime effects to happen:

// The protocol with Self requirement
protocol Narcissistic {
    func getFriend() -> Self
}


// Base class that adopts the protocol
class Mario : Narcissistic  {
    func getFriend() -> Self {
        print("Mario.getFriend()")
        return self;
    }
}


// Intermediate class that eliminates the
// Self requirement by specifying an explicit type
// (Why does the compiler allow this?)
class SuperMario : Mario {
    override func getFriend() -> SuperMario {
        print("SuperMario.getFriend()")
        return SuperMario();
    }
}

// Most specific class that defines a field whose
// (polymorphic) access will cause the world to explode
class FireFlowerMario : SuperMario {
    let fireballCount = 42

    func throwFireballs() {
        print("Throwing " + String(fireballCount) + " fireballs!")
    }
}


// Global generic function restricted to the protocol
func queryFriend<T : Narcissistic>(narcissistic: T) -> T {
    return narcissistic.getFriend()
}


// Sample client code

// Instantiate the most specific class
let m = FireFlowerMario()

// The call to the generic function is verified to return
// the same type that went in -- 'FireFlowerMario' in this case.
// But in reality, the method returns a 'SuperMario' and the
// call to 'throwFireballs' will cause arbitrary
// things to happen at runtime.
queryFriend(m).throwFireballs()

You can see the example in action on the IBM Swift Sandbox here. In my browser, the output is as follows:

SuperMario.getFriend()
Throwing 32 fireballs!

(instead of 42! Or rat

Answers


DarkDust February 2016

The issue here seems to be a violation in contract:

You define getFriend() to return an instance of receiver (Self). The problem here is that SuperMario does not return self but it returns a new instance of type SuperMario.

Now, when FireFlowerMario inherits that method the contract says that the method should return a FireFlowerMario but instead, the inherited method returns a SuperMario! This instance is then treated as if it were a FireFlowerMario, specifically: Swift tries to access the instance variable fireballCount which does not exist on SuperMario and you get garbage instead.

You can fix it like this:

class SuperMario : Mario {
    required override init() {
        super.init()
    }

    override func getFriend() -> SuperMario {
        print("SuperMario.getFriend()")

        // Dynamically create new instance of the same type as the receiver.
        let myClass = self.dynamicType
        return myClass.init()
    }
}

Why does the compiler allow it? It has a hard time catching something like this, I guess. For SuperMario, the contract is still valid: the method getFriend does return an instance of the same class. The contract breaks when you create the subclass FireFlowerMario: should the compiler notice that a superclass might violate the contract? This would be an expensive check and probably more suited for a static analyzer, IMHO (Also, what happens if the compiler doesn't have access to SuperMario's source? What happens if that class is from a library?)

So it's actually SuperMario's duty to ensure that the contract is still valid when subclassing.


Alberto Doda February 2016

Yes, there seems to be a contradiction. The Self keyword, when used as a return type, apparently means 'self as an instance of Self'. For example, given this protocol

protocol ReturnsReceived {

    /// Returns other.
    func doReturn(other: Self) -> Self
}

we can't implement it as follows

class Return: ReturnsReceived {

    func doReturn(other: Return) -> Self {
        return other    // Error
    }
}

because we get a compiler error ("Cannot convert return expression of type 'Return' to return type 'Self'"), which disappears if we violate doReturn()'s contract and return self instead of other. And we can't write

class Return: ReturnsReceived {

    func doReturn(other: Return) -> Return {    // Error
        return other
    }
}

because this is only allowed in a final class, even if Swift supports covariant return types. (The following actually compiles.)

final class Return: ReturnsReceived {

    func doReturn(other: Return) -> Return {
        return other
    }
}

On the other hand, as you pointed out, a subclass of Return can 'override' the Self requirement and merrily honor the contract of ReturnsReceived, as if Self were a simple placeholder for the conforming class' name.

class SubReturn: Return {

    override func doReturn(other: Return) -> SubReturn {
        // Of course this crashes if other is not a
        // SubReturn instance, but let's ignore this
        // problem for now.
        return other as! SubReturn
    }
}

I could be wrong, but I think that:

  • if Self as a return type really means 'self as an instance of Self', the compiler should not accept this kind of Self requirement overriding, because it makes it possible to return instances which are not self; otherwise,

  • if Self as a return type must be simply a placeholder with n

Post Status

Asked in February 2016
Viewed 2,558 times
Voted 13
Answered 2 times

Search




Leave an answer