Search code examples
swiftprotocolsassociated-types

Can't create an Array of types conforming to a Protocol in Swift


I have the following protocol and a class that conforms to it:

protocol Foo{
    typealias BazType

    func bar(x:BazType) ->BazType
}


class Thing: Foo {
    func bar(x: Int) -> Int {
        return x.successor()
    }
}

When I try to create an Array of foos, I get an odd error:

var foos: Array<Foo> = [Thing()]

Protocol Foo can only be used as a generic constraint because it has Self or associated type requirements.

OK, so it can only be used if it has an associated type requirement (which it does), but for some reason this is an error?? WTF?!

I'm not sure I fully understand what the compiler is trying to tell me...


Solution

  • Let's say, if we could put an instance of Thing into array foos, what will happen?

    protocol Foo {
        associatedtype BazType
        func bar(x:BazType) -> BazType
    }
    
    class Thing: Foo {
        func bar(x: Int) -> Int {
            return x.successor()
        }
    }
    
    class AnotherThing: Foo {
        func bar(x: String) -> String {
            return x
        }
    }
    
    var foos: [Foo] = [Thing()]
    

    Because AnotherThing conforms to Foo too, so we can put it into foos also.

    foos.append(AnotherThing())
    

    Now we grab a foo from foos randomly.

    let foo = foos[Int(arc4random_uniform(UInt32(foos.count - 1)))]
    

    and I'm going to call method bar, can you tell me that I should send a string or an integer to bar?

    foo.bar("foo") or foo.bar(1)

    Swift can't.

    So it can only be used as a generic constraint.

    What scenario requires a protocol like this?

    Example:

    class MyClass<T: Foo> {
            let fooThing: T?
    
            init(fooThing: T? = nil) {
                    self.fooThing = fooThing
            }
            
            func myMethod() {
                    let thing = fooThing as? Thing // ok
                    thing?.bar(1) // fine
                    
                    let anotherThing = fooThing as? AnotherThing // no problem
                    anotherThing?.bar("foo") // you can do it
                    
                    // but you can't downcast it to types which doesn't conform to Foo
                    let string = fooThing as? String // this is an error
            }
    }