Which function does Swift call? Part 4: Generics

This is part 4 of a series on how overloaded functions in Swift are chosen. Part 1 covered overloading by return type, part 2 was about how different functions with simple single arguments were picked on a “best match” basis, and part 3 went into a little more detail about protocols.

We started the series with the question, why do you get a Range type back from 1...5 instead of a ClosedInterval? Today we cover how generics fit into the matching hierarchy, and we’ll finally have enough information to answer that question.

Generics are Lower Priority

Generics are lower down the pecking order. Remember, Swift likes to be as “specific” as possible, and generics are less specific. Functions with non-generic arguments (even ones that are protocols) are always preferred over generic ones:

// This f will match a single argument of any type,
// but is very low priority
func f<T>(t: T) { 
    print("T") 
}

// This f takes a specific implemented type, so will be
// preferred over anything else when passed a String.
func f(s: String) {
    print("String")
}

// This f takes a specific protocol, so would be
// preferred over the generic version (though not
// over a version of f that took an actual Bool)
func f(b: BooleanType) {
    print("BooleanType")
}

// this prints "String"
f("wotcha")

// this prints "BooleanType"
f(true)

// this prints "T"
f(1)

OK that’s simple enough. But once we’re in generic territory, there’s a bunch of additional rules for picking one generic function over another.

Making Generic Placeholders More Specific with Constraints

Within a type parameter list, you can apply various different constraints to your generic parameters. You can require generic placeholders conform to protocols, and that their associated types be equal to a certain type, or conform to a protocol.

Just like before, the rule of thumb is: the more specific you make a function, the more likely it is to be picked in overload resolution.

This means a generic function with a placeholder that has a constraint (such as conforming to a protocol) will be picked over one that doesn’t:

func f<T>(t: T) {
    print("T")
}

func f<T: IntegerType>(t: T) {
    print("T: IntegerType")
}

// prints "T: IntegerType"
f(1)

If the choice is between two functions that both have constraints on their placeholder, and one is more specific than the other, the more specific one is picked. These rules follow a familiar pattern – they mirror the rules for non-generic overloading.

For example, when one protocol inherits from another, the inheriting protocol constraint is preferred:

func g<T: IntegerType>(t: T) {
    print("IntegerType")
}

func g<T: SignedIntegerType>(t: T) {
    print("SignedIntegerType")
}

// prints "SignedIntegerType"
g(1)

Or if the choice is between adhering to one protocol or two, two is preferred:

func f<B: BooleanType>(b: B) {
    print("BooleanType")
}

func f<B: protocol<BooleanType,Printable>>(b: B) {
    print("BooleanType and Printable")
}

// prints "BooleanType and Printable"
f(true)

The where clause introduces the ability to constrain placeholders by their associated types. These additional constraints make the match more specific, bumping it up the priority:

// argument must be an IntegerType
func f<T: IntegerType>(t: T) {
    print("IntegerType")
}

// argument must be an IntegerType 
// AND it's Distance must be an IntegerType
func f<T: IntegerType where T.Distance: IntegerType>(t: T) {
    print("T.Distance: IntegerType")
}

// prints "T.Distance: IntegerType"
f(1)

Here, the extra where clause is analogous to a regular argument needing to conform to two protocols.

Swift will also distinguish between two where clauses, one of which is more specific than another. For example, requiring an associated type to conform to an inheriting protocol:

func f<T: IntegerType where T.Distance: IntegerType>(t: T) {
    print("T.Distance: IntegerType")
}

// where T.Distance: SignedIntegerType is more specific than
// where just T.Distance: IntegerType
func f<T: IntegerType where T.Distance: SignedIntegerType>(t: T) {
    print("T.Distance: SignedIntegerType")
}

// so this prints "T.Distance: SignedIntegerType"
f(1)

Where clauses also allow you to check a value is equal to a specific type, not just that it conforms to a protocol:

func f<T: IntegerType where T.Distance == Int>(t: T) {
    print("Distance == Int")
}

func f<T: IntegerType where T.Distance: IntegerType>(t: T) {
    print("Distance: IntegerType")
}

// prints "Distance == Int"
f(1)

It isn’t surprising that the version that specifies Distance is equal to an exact type is more specific than the version that just requires it conforms to a protocol. This is similar to the rule for non-generic functions that a specific type as an argument is preferred over a protocol.

One interesting point about checking equality in the where clause. You can check for equality to a protocol. But it probably isn’t what you want – equality to a protocol is not the same as conforming to a protocol:

// create a protocol that requires the
// implementor defines an associated type
protocol P {
    typealias L
}

// implement P and choose Ints as the
// associated type
struct S: P {
    typealias L = Int
}
let s = S()

// Ints are printable so this will match
func f<T: P where T.L: Printable>(t: T) {
    print("T.L: Printable")
}

// == is more specific than :, so this will
// be the one that's called, right?
func f<T: P where T.L == Printable>(t: T) {
    print("T.L == Printable")
}

// nope! prints "T.L: Printable"
f(s)

// here is a struct where L really is == Printable
struct S2: P {
    typealias L = Printable
}
let s2 = S2()

// and this time, yes, it prints "T.L == Printable"
f(s2)

It’s actually pretty hard to do this by accident – you can only check for type equality if a protocol you’re equating to has no Self or associated type requirements (i.e. the protocol doesn’t require a typealias. or use Self as a function argument), same as you can’t use those protocols as non-generic arguments. This rules out most of the protocols in the standard library (Printable is one of only a handful that doesn’t).

Beware the Unexpected Overload

So, if given a choice between a generic overload and a non-generic one, Swift choses the non-generic one. If choosing between two generic functions, there’s a set of rules that are very similar to the ones for choosing between non-generic functions.

It’s not a perfect parallel, though – there are some differences. For example, if given a choice between an inheriting constraint or more constraints (“depth versus breadth”), unlike with non-generic protocols, Swift will actually chose breadth:

protocol P { }
protocol Q { }

protocol A: P { }

struct S: A, Q { }
let s = S()

// this f is for an inherited protocol
// (deeper than just P)
func f(p: A) {
    print("A")
}

// this f is for more protocols
// (broader than just P)
func f(p: protocol<P, Q>) {
    print("P and Q")
}

// error: Ambiguous use of 'f'
f(s)

// however, if we define a similar situation
// with generics instead:

func g<T: A>(t: T) {
    print("T: A")
}

func g<T: protocol<P, Q>>(t: T) {
    print("T: P and Q")
}

// the second one will be picked
// prints "T: P and Q"
g(s)

Let’s see that example again with some real-world types from the standard library:

func f<T: SignedIntegerType>(t: T) {
    print("SignedIntegerType")
}

// IntegerType is less specific than SignedIntegerType,
// but depth beats breadth so this one should be called:
func f<T: protocol<IntegerType, Printable>>(t: T) {
    print("T: P and Q")
}

// prints "SignedIntegerType"
// wait, what?
f(1)

If the example with P and Q worked one way, why the difference with SignedIntegerType and Printable?

Well, turns out IntegerType itself conforms to Printable (by way of _IntegerType). Since it already conforms to it, the Printable in protocol is redundant and can be ignored – it doesn’t make that version of f any more specific than T just conforming to IntegerType. Since SignedIntegerType inherits from IntegerType, it is more specific, so that’s the one that gets picked.

I point this out not to be fussy about the hierarchy of standard library protocols, but to point out how easy it is to get an unexpected version of an overloaded function. For this reason, it’s a good rule to never overload a function with different functionality. Overload for performance optimization (like a collection algorithm that extends), or to extend a function to cover your user-defined new type, or to provide the caller with a more powerful but basically equivalent type (like with Range and ClosedInterval). But overload to vary functionality based on the input and sooner or later you’ll be sorry.

Anyway, with that little mini-lecture out of the way, we finally have enough of the details of overloading to answer the original question – why does Range get picked? We’ll cover that in the next article.

Which function does Swift call? Part 3: Protocol Composition

Previously, we looked at how Swift functions can be overloaded just by return type, and how Swift picks between different possible overloads based on a best-match mechanism.

All this was working towards the reason why 1...5 returns a Range rather than a ClosedInterval. And to understand that, we’ll have to look at how generics fit into the matching criteria.

But before we do, a brief diversion into protocol composition.

Protocols can be composed by putting zero1 or more protocols between the angle brackets of a protocol<> statement. The Apple Swift book says:

NOTE
Protocol compositions do not define a new, permanent protocol type. Rather, they define a temporary local protocol that has the combined requirements of all protocols in the composition.

Reading that, you might think that declaring protocol<P,Q> was essentially like declaring an anonymous protocol Tmp: P, Q { }, and then using Tmp, without ever giving it a name. But that’s not quite what this means, and a way to spot the difference is by declaring functions that take arguments declared with protocol.

First, a recap of some of the behaviours of regular protocols. Suppose we define 4 protocols: first P and Q, and then two more, A and B that both just conform to P and Q:

protocol P { }
protocol Q { }

protocol A: P, Q { }
protocol B: P, Q { }

Although A and B are functionally identical, they are two different protocols. This means you can write two functions, one that takes A and one that takes B, and they’ll be two different functions with equal priority, so if you try to call them and either one would work, you’ll get an ambiguous call error:

struct S: A, B { }
let s = S()

func f(a: A) {
    print("A")
}

func f(b: B) {
    print("B")
}

// error: Ambiguous use of 'f'
f(s)

If, on the other hand, we use protocol<P,Q> instead of B, we get a different result:

func g(a: A) {
    print("A")
}

func g(p: protocol<P,Q>) {
    print("protocol<P,Q>")
}

// prints "A"
g(s)

If protocol<P,Q> were really declaring a new anonymous protocol that conformed to P and Q, we’d get the same error as before. Instead, its like saying “an argument that conforms to both P and Q”.

As we saw in the last article, an inherited protocol (in this case A) always trumps its ancestors (in this case P and Q). So the A version of g is called. This is consistent with our rule of thumb, that Swift will always pick the function with more “specific” arguments – here, inheriting protocols are more specific than inherited ones.

Another way to be more specific is to require more protocols. Here’s an example of how three protocols is more specific than two:

protocol R { }
extension S: R { }

// you can assign an alias for any
// protocol<> definition if you prefer
typealias Two = protocol<P,Q>
typealias Three = protocol<P,Q,R>

func h(p: Two) {
    print("Two")
}

func h(p: Three) {
    print("Three")
}

// prints "Three"
h(s)

// you can still force the other to be
// called if you want: this prints "Two"
h(s as Two)

Finally, if given a choice between an inheriting protocol, or more protocols (i.e. depth vs breadth), Swift won’t favour one over the other:

func o(p: protocol<A,B>) {
    print("A and B")
}

func o(p: protocol<P,Q,R>) {
    print("P, Q and R")
}

// error: Ambiguous use of 'o'
o(s)

// prints "A and B"
o(s as protocol<A,B>)
// prints "P, Q and R"
o(s as protocol<P,Q,R>)

One of the conclusions here is that there are quite a few possible ways for you to declare functions that take the same argument type, and to tweak the declarations to favour one function being called over another. When you combine these with different return values, that gives you a tool to nudge Swift into inferring a specific type by default, but allowing the user to get a different one from the same function name if they prefer, while avoiding forcing the user to deal with ambiguity errors. This is what is happening with ... when it defaults to Range.

So now, on to generics, which will take this even further.


  1. zero being an interesting case we’ll look at some other time 

Changes to the Swift Standard Library in 1.1 beta 3

The biggest deal in this latest beta is the documentation. There are 2,745 new lines of /// comments in beta 3, and even pre-existing documentation for most items has been revised.

That doesn’t mean there aren’t any functional changes to the library, though. Here’s a rundown of what I could find:

  • As mentioned in the release notes, the various literal convertible protocols are now implemented as inits rather than static functions.
  • The ArrayBoundType protocol is gone, and the various integer types that conformed to it no longer do.
  • The CharacterLiteralConvertible protocol is gone. Character never appeared to conform to it – it conformed to ExtendedGraphemeClusterLiteralConvertible which remains.
  • The StringElementType protocol is gone, and UInt8 and UInt16 no longer conform to it. The UTF16.copy method, that relied on it to do some pointer-based manipulation, is also gone.
  • Dictionary’s initializer that took a minimumCapacity argument no longer has a default for that minimum capacity. It’s interesting that this worked previously, since if you did the same thing with your own type (i.e. gave it both an init() and an init(arg: Int = 0)) you’d get a compiler error when you tried to actually use init().
  • In extending RandomAccessIndexType, integers now use their Distance typealias for Int rather than straight Int.
  • The static from() methods on integers are all gone.
  • There’s now an EnumerateSequence that returns an EnumerateGenerator, and enumerate() returns that rather than the generator.
  • The various integer types no longer have initializers from Builtin.SomeType.
  • The _CocoaArrayType protocol (as used to initialize an Array from a Cocoa array) has been renamed to _SwiftNSArrayRequiredOverridesType.
  • Numerous _CocoaXXX and _SwiftXXX protocols have acquired an @objc attribute.
  • _PrintableNSObjectType (which wasn’t explicitly implemented by anything in the std lib) is gone.
  • The AssertStringType and StaticStringType protocols are gone, as has AssertString. StaticString still exists and is clearly described as a static string that can be known at compile-time.
  • assert and precondition are much simplified with those string types removed, leaving one version each that just uses String for it’s message parameter. assertionFailure, preconditionFailure and fatalError all take a String for their message now as well, though they still take StaticString for the filename argument as described in the Swift blog’s article.
  • There are two new sort functions that operate on a ContiguousArray (one for arrays of comparable elements, and one that takes a comparison predicate).
  • But there are two fewer sorted functions. The ones that take a MutableCollectionType, and return one, are gone. Seems a shame, though it didn’t really make sense for them to take a mutable type given they didn’t need to mutate it. Maybe they’ll be replaced with versions that return ExtensibleCollectionType.
  • There’s a new unsafeDowncast that is equivalent to x as T but with the safeties removed – to be used only when using as is causing performance problems.
  • Raise a glass for the snarky “Haskell’s fmap, which was mis-named” comment, which is now replaced by a very straight-laced description of what Optional.map actually does.

In keeping with the documenting theme, there are a lot of argument name changes here as well. Continuing a trend seen in previous betas, specific, descriptive argument names are preferred over more generic ones. This is probably a good Swift style tip for your own code, especially if you’re also writing libraries.

Some examples:

  • Naming arguments in protocols instead of just using ‘_‘ e.g. func distanceTo(other: Self) instead of func distanceTo(_: Self) (there’s no compulsion to use the same names for your method arguments that the protocol does but you probably should)
  • Avoiding just naming the variable after the type (e.g. sequence: Sequence), For example, Array.extend has renamed its argument to newElements.
  • Replacing newValues with newElements in various places, presumably because of the unintended Swift implications of the term “values”.
  • Avoiding i or v such as subscript(position: Index) instead of subscript(i: Index), and init(_ other : Float) instead of init(_ v : Float).
  • Descriptive names for predicate arguments, such as isOrderedBefore rather than just pred.

Rather than me regurgitate the comments here, I’d suggest option-clicking import Swift and reading through it in full. There’s lots of informative stuff in there. Major types like Array and String have long paragraphs in front of them with some good clarifications. Many things are explained that you’d otherwise only have picked up on by watching the developer forums like a hawk.1

Here are a few items of specific interest:

The comment above GeneratorType has been revised. The suggestion that if using a sequence multiple times the algorithm “should probably require CollectionType, since CollectionType implies multi-pass” is gone. Instead it states that “any code that uses multiple generators (or for ... in loops) over a single sequence should have static knowledge that the specific sequence is multi-pass”. The most common case of having that static knowledge being that you got the sequence from a collection I guess.

Above Slice we find “Warning: Long-term storage of Slice instances is discouraged“. It makes it pretty clear Slice is mainly there to help pass around subranges of arrays, rather than being a standalone type in its own right. They give you the value type behaviour you’d get from creating a new array (i.e. if a value in the original array is changed, the value in the slice won‘t) while allowing you to get the performance benefits of copy-on-write for a sub-range of an existing array.

Several of the _SomeProto versions of protocols now have an explicit comment along the lines of the previously implied “this protocol is an implementation detail of SomeProto; do not use it directly”. I’m still not sure why these protocols are written in this way, possibly as a workaround for some compiler issues? If you have an idea, let me know.

edit: a post on the dev forum confirms it’s a temporary workaround for a compiler limitations related to generics, though doesn’t get specific about what limitation. Thanks to @anatomisation and @ivicamil for the link.

The reason for ContiguousArray’s existence is finally made clear. It’s there specifically for better performance when holding references to classes (not value types). There’s also a comment above the various collection withUnsafeBufferPointer methods suggesting you could use them when the optimizer fails to eliminate bounds checks automatically, in a performance-vs-safety trade-off.

Chris Lattner clarified on the dev forum that although this version is a GM, that’s in order to allow you to use it to submit apps to the mac store rather than because this is the final shipping version. We’ll likely see more changes to Xcode 6.1 before that, and with it maybe further changes to the standard lib.


  1. Or reading this blog, of course. Hopefully the comments don’t get too helpful, what would I write about then?