I would like to create a function that returns an object that conforms to a protocol, but the protocol uses a typealias
. Given the following toy example:
I think the key to understanding what is going on here is distinguishing between things that are determined dynamically at runtime, and things that are determined statically at compile time. It doesn't help that, in most languages like Java, protocols (or interfaces) are all about getting polymorphic behavior at run time, whereas in Swift, protocols with associated types are also used to get polymorphic behavior at compile time.
Whenever you see a generic placeholder, like T
in your example, what type is filled in for this T
is determined at compile time. So, in your example:
func returnsSomethingWithAwesomeness<T: HasAwesomeness>(key: String) -> T
is saying: returnsSomethingWithAwesomeness
is a function that can operate on any type T
, just so long as T
conforms to HasAwesomeness
.
But what is filled in for T
is determined at the point returnsSomethingWithAwesomeness
is called – Swift will look at all the information at the call site and decide what type T
is, and replace all T
placeholders with that type.*
So suppose at the call site the choice is that T
is a String
, you can think of returnsSomethingWithAwesomeness
as being rewritten with all occurrences of the placeholder T
replaced with String
:
// giving the type of s here fixes T as a String
let s: String = returnsSomethingWithAwesomeness("bar")
func returnsSomethingWithAwesomeness(key: String) -> String {
if key == "foo" {
return "Amazing Foo"
}
else {
return 42
}
}
Note, T
is replaced with String
and not with a type of HasAwesomeness
. HasAwesomeness
is only being used as a constraint – that is, restricting what possible types T
can be.
When you look at it like this, you can see that that return 42
in the else
makes no sense – how could you return 42 from a function that returns a string?
To make sure returnsSomethingWithAwesomeness
can work with whatever T
ends up being, Swift restricts you to only use those functions that are guaranteed to be available from the given constraints. In this case, all we know about T
is that it conforms to HasAwesomeness
. This means you can call the returnsSomethingWithAwesomeness
method on any T
, or use it with another function that constrains a type to HasAwesomeness
, or assign one variable of type T
to another one (all types support assignment), and that is it.
You can’t compare it to other Ts (no guarantee it supports ==
). You can't construct new ones (who knows if T
will have an appropriate initialize method?). And you can’t create it from a string or integer literal (doing that would require T
to conform to either StringLiteralConvertible
or IntegerLiteralConvertible
, which it doesn’t necessarily – hence those two errors when you try and create the type using one of these kinds of literals).
It is possible to write generic functions that return a generic type that all conforms to a protocol. But what would be returned would be a specific type, not the protocol so which type would not be determined dynamically. For example:
func returnCollectionContainingOne<C: ExtensibleCollectionType where C.Generator.Element == Int>() -> C {
// this is allowed because the ExtensibleCollectionType procol
// requires the type implement an init() that takes no parameters
var result = C()
// and it also defines an `append` function that allows you to do this:
result.append(1)
// note, the reason it was possible to give a "1" as the argument to
// append was because of the "where C.Generator.Element == Int" part
// of the generic placeholder constraint
return result
}
// now you can use returnCollectionContainingOne with arrays:
let a: [Int] = returnCollectionContainingOne()
// or with ContiguousArrays:
let b: ContiguousArray = returnCollectionContainingOne()
Think of returnCollectionContainingOne
in this code as really being two functions, one implemented for ContiguousArray
, and one for Array
, written automatically by the compiler at the point you call them (and therefore where it can fix C
to be a specific type). Not one function that returns a protocol, but two functions that return two different types. So in the same way returnsSomethingWithAwesomeness
can’t return either a String
or an Int
at runtime based on some dynamic argument, you couldn’t write a version of returnCollectionContainingOne
that returned either an array or a contiguous array. All it can return is a T
, and at compile time whatever T
actually is can be filled in by the compiler.
* this is a slight oversimplification of what the compiler actually does but it’ll do for this explanation.