4

I can’t figure out how to perform a cast which would let me eventually to introduce some sort of dynamism working with generics.

Next piece of code does not compile. It shows this error:

Cannot invoke 'createContainer' with an argument list of type '(FooProtocol)' Expected an argument list of type '(T)'

protocol FooProtocol {
    func doSomething()
}

class Foo : FooProtocol {
    func doSomething() {}
}

class Container<T : FooProtocol> {
    let someDataConformingFooProtocol : T

    init(someDataConformingFooProtocol : T) {
        self.someDataConformingFooProtocol = someDataConformingFooProtocol
    }
}

class AllTogether {

    init () {
        createContainer(Foo()) //So far, so good

        let foo2Mask : AnyObject = Foo()
        if let foo2MaskChecked = foo2Mask as? FooProtocol {
            createContainer(foo2MaskChecked)
            //ERROR: Cannot invoke 'createContainer' with an argument list of type '(FooProtocol)'
            //Expected an argument list of type '(T)'
        }

    }

    func createContainer<T : FooProtocol>(data: T){
        Container<T>(someDataConformingFooProtocol: data)
    }
}

Is this really the expected behaviour? Because personally I can’t understand what or why the compiler is complaining about it.

What would be the appropriate cast? Without referencing to the concrete class, I mean NOT like this:

if let foo2MaskChecked = foo2Mask as? Foo 

Thanks!

Víctor Albertos
  • 8,093
  • 5
  • 43
  • 71
  • 1
    It's something to do with type variance. Have a read of this: http://nomothetis.svbtle.com/type-variance-in-swift – Mike Pollard Jan 25 '16 at 13:53
  • I see. The last example of the post illustrates the same problem. It seems that there is not a solution right now. Thanks! – Víctor Albertos Jan 25 '16 at 14:39
  • @MikePollard Can you explain how variance comes into play here? We are not trying to pass off a `Container` as a `Container`; all we try is to pass some `U: FooProtocol` to a method with signature `T: FooProtocol`. Intutitively, `U` and `T` unify, so all should be good. What am I missing? – Raphael Jan 11 '17 at 17:00
  • Some days, I miss Scala. – Raphael Jan 11 '17 at 17:03
  • FWIW, I thought that class protocols should be of help here, but no. [See this follow-up question](http://stackoverflow.com/questions/41596617/why-does-type-inference-in-swift-not-deal-with-transitive-constraints). – Raphael Jan 11 '17 at 17:04
  • The problem is that [protocols don't conform to themselves](http://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself) – therefore you cannot use `FooProtocol` as a concrete type that conforms to `FooProtocol`. – Hamish Jan 11 '17 at 17:10

1 Answers1

0

What it comes down to is the difference between T: FooProtocol and FooProtocol as @Hamish mentioned in his comment.

When I have a function like this:

func foo(_ i: FooProtocol)

I'm taking in an instance of type FooProtocol, so I can pass in a value that conforms to FooProtocol or its type is FooProtocol. This is similar to subclassing where you can pass both SuperClass and SubClass into SuperClass.

But if you have a function like this:

func foo<T>(_ i: T) where T: FooProtocol

I can pass in any type that conforms to FooProtocol, but because FootProtocol does not conform to itself, you can't pass that in.

So in your question, you are trying to pass in a type FooProtocol where the types must conform to FooProtocol. You could probably fix this by doing:

class Container {
    let someDataConformingFooProtocol: FooProtocol

    init(someDataConformingFooProtocol: FooProtocol) {
        self.someDataConformingFooProtocol = someDataConformingFooProtocol
    }
}

// Later
func createContainer(data: FooProtocol){
    Container(someDataConformingFooProtocol: data)
}
Caleb Kleveter
  • 11,170
  • 8
  • 62
  • 92