Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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...

like image 405
cfischer Avatar asked Nov 11 '14 13:11

cfischer


People also ask

Can array have different data types in Swift?

Here, [Int]() specifies that the empty array can only store integer data elements. Note: In Swift, we can create arrays of any data type like Int , String , etc.

Can you create instance of protocol in Swift?

You can not create an instance of protocol. But however you can refer an object in your code using Protocol as the sole type.

What is conform in Swift?

A class “conforms to” a protocol if it adopts the protocol or inherits from another class that adopts it. Classes adopt protocols by listing them within angle brackets after the interface declaration.

What is type erasure in Swift?

Type-erasure simply means "erasing" a specific type to a more abstract type in order to do something with the abstract type (like having an array of that abstract type). And this happens in Swift all the time, pretty much whenever you see the word "Any."


2 Answers

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

protocol Foo {
    typealias 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
        }
}
like image 189
ylin0x81 Avatar answered Sep 20 '22 02:09

ylin0x81


I have been playing with your code trying to understand how to implement the protocol. I found that you can't use Typealias as a generic type because it is just an alias not a type by itself. So if you declare the Typealias outside your protocol and your class you can effectively use it in your code without any problem.

Note: the Typealias has the Int type in its declaration, that way you can always use the alias instead of the Int type and use all of its associated methods and functions.

Here's how I make it work:

typealias BazType = Int

protocol Foo{
  func bar(x:BazType) -> BazType
}

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

let elements: Array<Foo> = [Thing(), Thing()]
like image 20
David Gomez Avatar answered Sep 21 '22 02:09

David Gomez