Hello all,

If you’ve been (attempting) protocol-oriented development in your own projects, 
I’m sure you’ve come across a particular build error at one point:

> Protocol ‘MyProtocol' can only be used as a generic constraint because it has 
> Self or associated type requirements

To be frank, this restriction in the current Swift model sucks, a lot. In 
*many* cases, this prevents me from using protocols, and instead I have to fall 
back to using concrete types.

Here are a couple examples of using protocols with collections that should work 
fine, but simply don’t:

A Set of Types Conforming to Protocol

protocol MyProtocol: Hashable {}

let set = Set<MyProtocol>() // ERROR: Protocol ‘MyProtocol' can only be used as 
a generic constraint because it has Self or associated type requirements

When declaring a Set, the generic type of the Set’s contents must conform to 
Hashable. Following this, it would appear that you should be able to declare a 
Set containing types conforming to a given protocol which in turn conforms to 
Hashable. Nope! This also means you can’t have a Set<Hashable> (so no 
type-erased Sets for you!). One potential workaround is to use a box type, but 
if exposing the set to a user, this is essentially a leaky abstraction.

Finding a Protocol Type Instance in an Array

protocol MyProtocol {}
struct MyStruct: MyProtocol {}

var array = [MyProtocol]()
array.append(MyStruct())

let index = array.index(of: MyStruct()) // ERROR: Cannot invoke 'index' with an 
argument list of type '(of: MyStruct)'

So, we can’t use Set as a collection for our protocol types, let’s use Array 
instead! Not so fast: because MyProtocol doesn’t conform to Equatable, we can’t 
use the Array.index(of:) function to find it. Easy fix though, just make 
MyProtocol conform to Equatable, right?

protocol MyProtocol: Equatable {}
struct MyStruct: MyProtocol {}

var array = [MyProtocol]() // ERROR: Protocol ‘MyProtocol' can only be used as 
a generic constraint because it has Self or associated type requirements

Nope! Now that it conforms to Equatable, it can no longer be used in Array’s 
type declaration. However, there is a (somewhat) workaround for this problem:

protocol MyProtocol {}
func ==(lhs: MyProtocol, rhs: MyProtocol) -> Bool { return true }

struct MyStruct: MyProtocol {}

var array = [MyProtocol]()
array.append(MyStruct())

let index = array.index(where: { $0 == MyStruct() })

Basically, we can define the == function for MyProtocol, and then instead of 
using Array.index(of:), we use Array.index(where:) to manually compare each 
item to see if it matches, aka what Array.index(of:) would do for us normally 
if we simply could declare MyProtocol as conforming to equatable.

TL;DR
Swift really pushes the idea of protocol-oriented programming, and for the most 
part this works well. However, due to some underlying restrictions in the 
current Swift model, you can’t use protocols in all the same places you can use 
concrete types, which sucks. This is especially confusing for beginners who are 
trying to use protocols, but get frustrated when it doesn’t work where they 
want it to (and don’t understand why), so they fall back to using concrete 
types (usually implemented with class inheritance). For this reason, I think 
these restrictions need to be fixed ASAP, or else the Swift language is 
essentially pushing people away from protocol-oriented programming.

Riley Testut

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to