1

I've encountered a problem with a method which I cannot explain. Here is some test code which shows the problem:

protocol Base {}
protocol Extended: Base {}

struct Instance:Extended {}

let anInstance = Instance()
let instanceOfBase = anInstance as Base
let instanceOfExtended = anInstance as Extended

func aMethod<T:Base>(_ instance:T) {}

aMethod(anInstance)
aMethod(instanceOfBase) // Error - Cannot invoke 'aMethod' with an argument list of type '(Base)'
aMethod(instanceOfExtended) // Error - Cannot invoke 'aMethod' with an argument list of type '(Extended)'

According to the Apple doco I've read on protocols, generics, etc. aMethod() should accept any object that conforms to the Base protocol. Yet it rejects both instances where I have cast them to either Base or Extended.

Can anyone explain this?

Also:

func aMethod2(_ instance:Base) {}
aMethod2(anInstance)
aMethod2(instanceOfBase)
aMethod2(instanceOfExtended)

Works fine so the difference seems to be whether the instance argument is based (excuse the pun) on Base or <T:Base>.

For anyone questioning why I would declare a generic here. The original code looked like this:

func addViewController<T:ModelObject>(_ stack:inout [UIViewController],
                               object:T?,
                               controller:DetailsViewController<T>?,
                               storyboardId:String) {...

As you can see, I want to constrain several arguments to the same type. Hence the use of a generic rather than just specifying base.

drekka
  • 20,957
  • 14
  • 79
  • 135
  • This is because [protocols don't conform to themselves](http://stackoverflow.com/q/33112559/2976878). – Hamish Feb 16 '17 at 08:14

2 Answers2

2

T is a constraint on a concrete type

<T:Base> is a constraint on the placeholder type T. T must be a concrete type (a class, enum or struct such as Instance) that conforms to the Base protocol. The placeholder type T cannot be the Base protocol.

aMethod<T:Base>(:) must be called with a variable that is of a type that conforms to the Base protocol at compile time. aMethod<T:Base>(:) cannot be called with a variable that is only known to be of type Base.

The following line of code instantiates a variable named anInstance that is of struct type Instance.

let anInstance = Instance()

aMethod(anInstance) compiles because anInstance is of a concrete type Instance that conforms to the Base protocol.

The following line of code instantiates a variable named instanceOfBase that is of protocol type Base.

let instanceOfBase: Instance = anInstance as Base

aMethod(instanceOfBase) does not compile because instanceOfBase is not of a concrete type that conforms to the Base protocol. It is of the protocol type Base.

Here is another snippet that fails to illustrate the issue. In this case the base argument is only known to be of the protocol type Base.

func aMethod(base: Base) {
    aMethod(base) // Cannot invoke 'aMethod' with an argument list of type '(Base)'
}
Mobile Dan
  • 6,444
  • 1
  • 44
  • 44
  • Yet both `instanceOfBase is Base` and `instanceOfExtended is Base` are true. So if `instanceOfBase` is a `Base`, why can I not pass it to a method that accepts an argument that confirms to `Base` according to the generics documentation? – drekka Feb 16 '17 at 04:07
  • But shouldn't the compiler see `instanceOfBase` as 'an instance of something that conforms to Base' and therefore pass it because the method accepts things that 'conform to Base'? This sounds like it's deciding to treat `Base` as a concrete type, which it isn't. When it should be treating it effectively as `Any` or something like that. – drekka Feb 16 '17 at 04:11
  • I interpret `` to be a constraint on the Type `T`. `T` must be a concrete type that conforms to the `Base` protocol. `T` cannot be any type that conforms to the `Base` protocol. – Mobile Dan Feb 16 '17 at 04:11
  • This is limitation of protocols and generics in Swift currently. – Mobile Dan Feb 16 '17 at 04:12
  • Nuts. Java is way better in this area. – drekka Feb 16 '17 at 04:12
  • I come from a C# background and what you are doing seems like it should work to the C# developer in me. However, I've been wrestling with this lately and am slowly learning how it works. – Mobile Dan Feb 16 '17 at 04:13
  • Agreed. I think this will improve in future Swift versions. – Mobile Dan Feb 16 '17 at 04:13
  • Swift is compiled to machine instructions so you get great performance at the cost of flexibility. – Mobile Dan Feb 16 '17 at 04:15
1

Hmm, I don't really get why you're doing this.

Let's say:

  • you want aMethod to accept any instance that conforms to Base protocol, then you can just make it func aMethod(_ instance: Base) {}
  • You want preserve type information inside the function then you use the generic method but pass in an instance of a concrete implementation of the protocol.

Please note that func aMethod<T:Base>(_ instance:T) {} means it's expecting an instance of type T that conforms Base. Neither Base or Extended is a valid type.

What you're doing here is using generics without its power...Which, IMHO, doesn't make sense. Unless you can provide a more realistic scenario?

J.Wang
  • 1,136
  • 6
  • 12
  • The more realistic example was `func aMethod(_ instance:T, anotherThing:SomeType)`. Essentially in my original code I had several method arguments which where either of type 'T' or where of a generic type. I wanted to ensure that the compiler would validate that all arguments used the same type. – drekka Feb 16 '17 at 03:53
  • For example, if I had a type `MyT:Base` then I wanted the function to look like `aMethod(_ instance:MyT, anotherThing:SomeType)` but as I have a number of classes that implement `Base`, generics seemed like the answer. – drekka Feb 16 '17 at 03:55
  • The reason why I'm using protocols like this is that there can be a number of implementations and I want to declare things based on the protocols to code that doesn't care about which implementation it has been passed. It'a also a good idea from the point of view of testing. – drekka Feb 16 '17 at 03:57
  • @drekka Right. My suggestion would be if you can't write `aMethod(_ instance: Base, anotherThing: SomeType)` or `aMethod(_ instance: T, anotherThing: SomeType)` then you've reached the point where swift generics can't provide at this moment. Check https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md for further development. Also, **type erase** may help with a workaround. – J.Wang Feb 16 '17 at 04:06
  • Thanks. Have already tried type erasure. Worked in a playground, but in the larger context of my app it failed to solve the problems I was trying to solve. It's really good for effectively wrapping an associated type in a generic so you can use it as a type, but doesn't solve the basic problem of wanting to declare a variable to be an generic with an unknown type. ie. Type>, or Type. – drekka Feb 16 '17 at 04:21