Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Forced to cast, even if protocol requires given type

Tags:

swift

swift4

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?

like image 950
zgorawski Avatar asked Sep 15 '17 09:09

zgorawski


People also ask

What is Google Cast protocol?

Google Castis the name of the protocol that is used to communicate between the sender and the receiver application. By consequence it means that a google-cast application is distributed between a sender part and a receiver part.

Why we can't perform implicit type casting on the data types?

We cannot perform implicit type casting on the data types which are not compatible with each other such as: Converting float to an int will truncate the fraction part hence losing the meaning of the value. Converting double to float will round up the digits.

Why does the type cast operator have two different forms?

Because downcasting can fail, the type cast operator comes in two different forms. The conditional form, as?, returns an optional value of the type you are trying to downcast to.

What is the general syntax for type casting operations?

The general syntax for type casting operations is as follows: The type name is the standard ‘C’ language data type. An expression can be a constant, a variable or an actual expression.


1 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)
like image 82
Hamish Avatar answered Oct 02 '22 15:10

Hamish