Swift Protocol With "Where Self" Clause

Swift protocol with where Self clause

The ability to put superclass constraints on protocols declarations (that is, being able to define protocol P where Self : C where C is the type of a class) was a premature consequence of
SE-0156, and the syntax should have been rejected in Swift 4.x until the feature was implemented. Attempting to use this feature in Swift 4.x can cause miscompilation and crashes, so I would avoid using it until Swift 5.

In Swift 5 (Xcode 10.2) the feature has now been implemented. From the release notes:

Protocols can now constrain their conforming types to those that
subclass a given class. Two equivalent forms are supported:

protocol MyView: UIView { /*...*/ }
protocol MyView where Self: UIView { /*...*/ }

Swift 4.2 accepted the second form, but it wasn’t fully implemented
and could sometimes crash at compile time or runtime. (SR-5581)
(38077232)

This syntax places a superclass constraint on MyView which restricts conforming types to those inheriting from (or being) UIView. In addition, the usage of MyView is semantically equivalent to a class existential (e.g UIView & MyView) in that you can access both members of the class and requirements of the protocol on the value.

For example, expanding upon the release notes' example:

protocol MyView : UIView {
var foo: Int { get }
}

class C : MyView {} // error: 'P' requires that 'C' inherit from 'UIView'

class CustomView : UIView, MyView {
var foo: Int = 0
}

// ...

let myView: MyView = CustomView(frame: .zero)

// We can access both `UIView` members on a `MyView` value
print(myView.backgroundColor as Any)

// ... and `MyView` members as usual.
print(myView.foo)

what is 'where self' in protocol extension

That syntax is: https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID521

Consider:

protocol Meh {
func doSomething()
}

// Extend protocol Meh, where `Self` is of type `UIViewController`
// func blah() will only exist for classes that inherit `UIViewController`.
// In fact, this entire extension only exists for `UIViewController` subclasses.

extension Meh where Self: UIViewController {
func blah() {
print("Blah")
}

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

class Foo : UIViewController, Meh { //This compiles and since Foo is a `UIViewController` subclass, it has access to all of `Meh` extension functions and `Meh` itself. IE: `doSomething, blah, foo`.
func doSomething() {
print("Do Something")
}
}

class Obj : NSObject, Meh { //While this compiles, it won't have access to any of `Meh` extension functions. It only has access to `Meh.doSomething()`.
func doSomething() {
print("Do Something")
}
}

The below will give a compiler error because Obj doesn't have access to Meh extension functions.

let i = Obj()
i.blah()

But the below will work.

let j = Foo()
j.blah()

In other words, Meh.blah() is only available to classes that are of type UIViewController.

Protocols with where clause don't work when referring them as protocols

Unlike e.g. Objective-C protocols, a Swift protocol doesn't conform to itself. As a result, protocol MyModel: Naming doesn’t satisfy the Self.Model: Naming constraint of your protocol extension.

Is it possible to add condition where protocol is confirmed by either of class giving multiple options in where clause Self

So you want to define an extension to protocol XYZ when Self is A or B right?

The answer is: you can’t.

However

You can achieve a similar result doing something like this

protocol XYZ { }
protocol AOrB: XYZ { }

class A: AOrB { }
class B: AOrB { }
class C: XYZ { }

extension XYZ where Self: AOrB {
func foo() { }
}

A().foo()
B().foo()

Error using Self in generic function where clause

Before discussing this, I highly advice you to rethink your problem and simplify your types. Once you wander down the road of mixing generics and protocols in Swift, you will be fighting the type system nonstop. Part of this is that complex types are complex and it is difficult to get them correct even with a very strong type system. Part of it is that Swift does not have a very strong type system. Compared to Objective-C or Ruby, sure, it's incredibly powerful, but it's still fairly weak around generic types and there are many concepts you can't express (there are no higher-kinded types, there is no way to express covariance or contravariance, no dependent types, and there are weird quirks like protocols don't always conform to themselves). In nearly every case where I've worked with a developer on their complex types, it turned out that their actual program didn't need so much complexity. Protocols with associated types should be considered an advanced tool; don't reach for them unless you really need them. See Beyond Crusty for more on that.

This doesn't work because it violates your where clause:

func feed<T:Animal>(to:T) where Self == T.FoodSource

So Animal.FoodSource has to match Self. Let's look at how you use it:

SteakSalad().feed(to: Lion())

So Self is SteakSalad and Lion.FoodSource is Meat. Those are not equal, so this doesn't apply. What you really mean is this:

func feed<T:Animal>(to animal:T) where Self: T.FoodSource

But that isn't legal Swift ("error: first type 'T.FoodSource' in conformance requirement does not refer to a generic parameter or associated type"). The problem is that T.FoodSource could be anything; it doesn't have to be a protocol. "Self conforms to an arbitrary type" is not meaningful Swift.

We could try to improve this by making FoodSource at least conform to Food, but it gets even worse:

protocol Food {}
protocol Meat: Food {}

protocol Animal {
associatedtype FoodSource: Food
}

And then make Lion eat Meat:

class Lion : Animal {
typealias FoodSource = Meat

MyPlayground.playground:1:15: note: possibly intended match 'Lion.FoodSource' (aka 'Meat') does not conform to 'Food'

typealias FoodSource = Meat

Doesn't Meat conform to Food? Hah, no. This is part of the greater "protocols don't conform to themselves" limitation in Swift. You can't just treat protocols like they have inheritance. Sometimes they do, and sometimes they don't.

What you can do is that that meat can be fed to meat-eaters:

protocol Meat {}

extension Meat {
func feed<T:Animal>(to animal:T) where T.FoodSource == Meat {
animal.eat(self)
}
}

And Veg can be fed to veg-eaters:

protocol Vegetable {}

extension Vegetable {
func feed<T:Animal>(to animal:T) where T.FoodSource == Vegetable {
animal.eat(self)
}
}

But there's no way I know of to make this generic over protocols-with-associated-types (PATs). It's just too much for the Swift type system. My recommendation is to get rid of the PAT and just use generics. Most of these problems will go away. Even in a language like Scala that has a more powerful type system and also has associated types, the right answer is usually simpler generics (and often not even that; we often make things generic when there's no need).

Objective C syntax corresponding to Swift extension where self: some protocol

There is no Objective-C syntax for that. (Not sure if there’s even a Swift syntax like this; we have a generic where clause, but not on self. And Objective-C doesn’t offer full generic implementation, only offering lightweight implementation.)

Can not conform to protocol by creating extension with Where Clauses

This is pretty interesting. Long story short (okay maybe not that short) – it's an intentional side effect of #12174, which allows for protocol extension methods that return Self to satisfy protocol requirements for non-final classes, meaning that you can now say this in 4.1:

protocol P {
init()
static func f() -> Self
}

extension P {
static func f() -> Self {
return self.init()
}
}

class C : P {
required init() {}
}

In Swift 4.0.3, you would get a confusing error on the extension implementation of f() saying:

Method 'f()' in non-final class 'C' must return Self to conform to protocol 'P'

How does this apply to your example? Well, consider this somewhat similar example:

class C {}
class D : C {}

protocol P {
func copy() -> Self
}

extension P where Self == C {
func copy() -> C {
return C()
}
}

extension C : P {}

let d: P = D()
print(d.copy()) // C (assuming we could actually compile it)

If Swift allowed the protocol extension's implementation of copy() to satisfy the requirement, we'd construct C instances even when called on a D instance, breaking the protocol contract. Therefore Swift 4.1 makes the conformance illegal (in order to make the conformance in the first example legal), and it does this whether or not there are Self returns in play.

What we actually want to express with the extension is that Self must be, or inherit from C, which forces us to consider the case when a subclass is using the conformance.

In your example, that would look like this:

protocol Typographable {
func setTypography(_ typography: Typography)
}

extension UILabel: Typographable {}

extension Typographable where Self : UILabel {

func setTypography(_ typography: Typography) {

self.font = typography.font
self.textColor = typography.textColor
self.textAlignment = typography.textAlignment
self.numberOfLines = typography.numberOfLines
}
}

which, as Martin says, compiles just fine in Swift 4.1. Although as Martin also says, this can be re-written in a much more straightforward manner:

protocol Typographable {
func setTypography(_ typography: Typography)
}

extension UILabel : Typographable {

func setTypography(_ typography: Typography) {

self.font = typography.font
self.textColor = typography.textColor
self.textAlignment = typography.textAlignment
self.numberOfLines = typography.numberOfLines
}
}

In slightly more technical detail, what #12174 does is allow the propagation of the implicit Self parameter through witness (conforming implementation) thunks. It does this by adding a generic placeholder to that thunk constrained to the conforming class.

So for a conformance like this:

class C {}

protocol P {
func foo()
}

extension P {
func foo() {}
}

extension C : P {}

In Swift 4.0.3, C's protocol witness table (I have a little ramble on PWTs here that might be useful in understanding them) contains an entry to a thunk that has the signature:

(C) -> Void

(note that in the ramble I link to, I skip over the detail of there being thunks and just say the PWT contains an entry to the implementation used to satisfy the requirement. The semantics are, for the most part, the same though)

However in Swift 4.1, the thunk's signature now looks like this:

<Self : C>(Self) -> Void

Why? Because this allows us to propagate type information for Self, allowing us to preserve the dynamic type of the instance to construct in the first example (and so make it legal).

Now, for an extension that looks like this:

extension P where Self == C {
func foo() {}
}

there's a mismatch with the signature of the extension implementation, (C) -> Void, and the signature of the thunk, <Self : C>(Self) -> Void. So the compiler rejects the conformance (arguably this is too stringent as Self is a subtype of C and we could apply contravariance here, but it's the current behaviour).

If however, we have the extension:

extension P where Self : C {
func foo() {}
}

everything's fine again, as both signatures are now <Self : C>(Self) -> Void.

One interesting thing to note about #12174 though is that is preserves the old thunk signatures when the requirements contain associated types. So this works:

class C {}

protocol P {
associatedtype T
func foo() -> T
}

extension P where Self == C {
func foo() {} // T is inferred to be Void for C.
}

extension C : P {}

But you probably shouldn't resort to such horrific workarounds. Just change the protocol extension constraint to where Self : C.



Related Topics



Leave a reply



Submit