Forced to cast, even if protocol requires given type

Viewed 1050

I have following code:

import UIKit

protocol Fooable: class where Self: UIViewController {
    func foo()
}

class SampleViewController: UIViewController, Fooable {

    func foo() {
        print("foo")
    }
}

let vc1: Fooable = SampleViewController()
let vc2: Fooable = SampleViewController()


// vc1.show(vc2, sender: nil) - error: Value of type 'Fooable' has no member 'show'

// (vc1 as! UIViewController).show(vc2, sender: nil) - error: Cannot convert value of type 'Fooable' to expected argument type 'UIViewController'

(vc1 as! UIViewController).show((vc2 as! UIViewController), sender: nil)

commented lines doesn't compile.

Why am I forced to cast protocol type object to UIViewController even if Fooable protocol requires, that types that conform to it inherit from UIViewController?

5 Answers

Swift 5 update

In Swift 5 (Xcode 10.2), your code now works as expected without having to perform a force cast.


In Swift 4.x, Swift doesn't fully support superclass constraints on protocols, that is, being able to define protocol P where Self : C where C is the type of a class.

The fact that the compiler doesn't prevent you from doing this until the feature is actually implemented was an oversight, as said by Swift compiler engineer Slava Pestov:

Slava Pestov added a comment - 31 May 2018 1:19 PM

[...] "protocol P : Foo where Self : Class" was discovered on accident by users, and it doesn't really work completely. It was an oversight that it wasn't banned.

However this is a feature that is intended to be fully implemented in a future version of the language as a part of SE-0156.

Slava Pestov added a comment - 31 May 2018 1:19 PM

Both are supposed to work, but we haven't fully implemented the proposal yet.

(Edit: Slava has now implemented this in #17611, #17651, #17816 & #17851, so you'll get them in Swift 5, available from Xcode 10.2)

Once implemented, you'll be able to treat such a protocol type as the class type that it requires conforming types to inherit from (e.g allowing you to treat your Fooable as a UIViewController without having to cast), in the same way that you can treat a class existential such as Fooable & UIViewController as a UIViewController.

Not only that, but you'll also be able to state the superclass requirement directly on the protocol rather than in a where clause, for example:

protocol Fooable : UIViewController {
    func foo()
}

However, until Swift 5, I would recommend steering well clear of superclass constrained protocols – they currently have some nasty rough edges around them.

For example, this will miscompile and crash at runtime in Swift 4.1:

class C : P {
  func speak() {}
}

protocol P where Self : C {
  func speak()
}

let c: P = C()
c.speak()

and it'll crash the compiler in later versions of the language (SR-6816).

As a workaround, you could use an underscored protocol with a class existential typealias in order to enforce the class constraint instead. For example:

import UIKit

protocol _Fooable : class {
  func foo()
}

typealias Fooable = _Fooable & UIViewController

class SampleViewController : Fooable /* implicitly : UIViewController */ {
  func foo() {
    print("foo")
  }
}

// ...

let vc1: Fooable = SampleViewController()
let vc2: Fooable = SampleViewController()
vc1.show(vc2, sender: nil)
Related