1

While designing APIs, I run into this at every corner:

Protocol can only be used as a generic constraint because it has Self or associatedType requirements

What that means is that you basically can not use Self or associatedtype on protocol level if you ever plan to use the protocol type on its own -- otherwise the need for types to be generic themselves cascades out of control.

This is rather annoying (read: makes it next to impossible to design reasonable APIs, e.g. such that expose protocols but hide implementations). For instance, you can not have a simple data container protocol:

protocol DataContainer: Equatable {
    var id: Int { get }
    var content: Data { get }
}

The issue is that Equatable requires static function == which is, of course, expressed using Self. Therefore, you can not have a type like [DataContainer], or any number of other natural things. I first ran into it with groups (arrays) of users (which could be of one of three different types).

The "official" recommended "solution" is to have delegating/wrapper structs that eliminate the type variable, like AnyDataContainer. This feels like a rather silly, hacky workaround, not proper API design.

I honestly don't get why the type system has this restriction. What are the reason? Why can't the compiler implicitly create AnyP for every protocol P to work around it?

Raphael
  • 9,779
  • 5
  • 63
  • 94
  • 2
    I believe [my answer to a previous question](https://stackoverflow.com/a/41698579/2976878) of yours basically answers this – there's no real reason why this isn't possible, Swift just doesn't support it yet. Quoting from the generics manifesto that I quoted in my answer "*The restrictions on existential types came from an implementation limitation, but it is reasonable to allow a value of protocol type even when the protocol has Self constraints or associated types.*" – Hamish Jun 06 '17 at 12:30
  • @Hamish I had forgotten about that, thanks. I wonder if there aren't other issues, though. For instance, can all the things "we" would like to be able to do resolved statically? Swift doesn't seem to have many facilities for working with types *dynamically* so there may be some real (or at least secondary) restrictions here. For instance, I don't see how the compiler can always work correctly with calls to a function using `Self` without knowing what `Self` resolves to, given that it basically wants to dispatch statically all the time. – Raphael Jun 06 '17 at 12:47
  • 1
    In cases with requirements that have associated types or `Self` requirements (such as `==` in your example), in order to use them on instances typed as the protocol (when this is supported), you'll indeed need to give the compiler some concrete type information. One way in which this could be handled is detailed in the ["Opening Existentials" section](https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#opening-existentials) of the generics manifesto. – Hamish Jun 06 '17 at 14:08

2 Answers2

0

Here is one reason I can think of, which actually does not need an arcane setup either. Building on the example from the question, say we could have code like this:

func contained(element: DataContainer, in list: [DataContainer]) -> Bool {
    for elem in list {
        if elem == element {
            return true
        }
    }
    return false
}

The Swift compiler wants to statically dispatch ==; but it can't! There is no single implementation it can pick up; every array element may have its own type and therewith implementation of ==, and element may have a completely different one. The compiler therefore forbids the code at hand.


Two additional thoughts.

  1. I'd argue that the compiler to too defensive. The following code could be compiled easily, for instance:

    func contained(id: Int, in list: [DataContainer]) -> Bool {
        for elem in list {
            if elem.id == id {
                return true
            }
        }
        return false
    }
    

    Here, we don't need to know the exact types of the array elements; the promise that they each have an Int property id is enough!

    So it could allow the use of types like [DataContainer] as long as all accessed properties can be resolved statically.

  2. The compiler could allow Self references if there is no ambiguity where to dispatch and/or generic implementations exist. For instance (pseudo code):

    extension DataContainer {
        static func ==<T: DataCointainer, U: DataContainer>(lhs: T, rhs:U) -> Bool {
            guard T == U else {
                return false
            }
    
            return T.==(lhs, rhs as! T)
        }
    }
    

    Such a default implementation could be used in usage contexts as the one above, i.e. if not enough type information is available to select an implementation statically.

I don't know if all problems with generic protocols can be done away with similar ideas, and if we would run into further restrictions along the road.

Raphael
  • 9,779
  • 5
  • 63
  • 94
0

Think of it like the documentation says A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.

So the protocol only says what is required to conform to it not how that is to be done. The job of 'how' is left up to whatever conforms to the protocol itself.

This means:

1) You can't use a protocol on it's own as it has no implementation itself.

2) You cannot design an API that uses protocols to hide implementation.

Basically the protocol is saying to anything that implements it 'here is what you need to do but it's up to you how to do it'.

For an API maybe you would be better off using subclassing.

(Of course there may be ways to bend a protocol to do these kind of things but then you should really think if that's the best way to do it)

Edit:

You can use self within a protocol extension but you can't use it in the way you want. I could add this to the protocol:

func doubleID() -> Int

and then create an extension like this:

extension DataContainer {
    func doubleID() -> Int {
        return self.id * 2
    }
}

So you can hide some implementation but then that is a default as whatever conforms to the protocol is free to define it's own method for doubleID.

Edit:

This will achieve what you are after though:

protocol TestProtocol: Equatable {
    var id: Int { get }
    var data: Data { get }
}

func ==<T: TestProtocol>(lhs: T, rhs: T) -> Bool {
     return lhs.id == rhs.id
}

func ==<T: TestProtocol, U: TestProtocol>(lhs: T, rhs: U) -> Bool {
    return lhs.id == rhs.id
}

func !=<T: TestProtocol, U: TestProtocol>(lhs: T, rhs: U) -> Bool {
    return lhs.id != rhs.id
}

This will provide a default equality implementation for anything that conforms to the TestProtocol however it will not stop anything overriding with its own implementation.

Upholder Of Truth
  • 4,643
  • 2
  • 13
  • 23
  • Thanks for your time, but I don't think this very broad-strokes answer, well, answers my question. I'm aware of the high-level idea of protocols, and this is about some "what"s Swift does not allow us to declare (in effect). – Raphael Jun 06 '17 at 14:11
  • Regarding subclassing, since they call Swift "protocol-oriented" I'm sure that's not the answer. Especially so since the use of structs is explicitly encouraged, and there's no inheriting from structs. – Raphael Jun 06 '17 at 14:12
  • Ad edit 1: This is about `Self`, not `self`. Ad edit 2: You still can't declare an `[TestProtocol]`; even if the compiler were smarter about the issue at hand, it is completely unclear which implementation it should pick statically. – Raphael Jun 06 '17 at 16:33
  • Good point about Self vs self, my bad there. Yeah you're right you can't declare that. I guess we will just have to wait a version of the Swift compiler that is smarter than the current version. I guess it has a lot to do with the purpose of protocols as they stand at the moment where they don't really provide implementation themselves. – Upholder Of Truth Jun 06 '17 at 17:40
  • Also there is nothing wrong with using inheritance and subclassing even if Swift is protocol-oriented and the use of structs is encouraged. We should use the right tool for the job and there are times when a subclass is the correct (or only) option. – Upholder Of Truth Jun 06 '17 at 17:47