9

I've defined two generic functions

func job<T: Comparable>(x: T) {
  println("1")
}

func job<T: Hashable>(x: T) {
  println("2")
}

and when I try to call one of them, for example with:

let myInt: Int = 1 // Explicit Int just for clarity of the example
job(myInt)

of course Swift complains and throws an error
Ambiguous use of 'job'
which is understandable, because it is not clear whether I want to use the Comparable one or Hashable (Int conforms to both of them)

Is there a way I can hint the compiler which one I want to use?

Bartek Chlebek
  • 1,665
  • 1
  • 16
  • 23
  • 1
    Does `job(myInt as Hashable)` work? – Paul Manta Sep 29 '14 at 20:36
  • 1
    Nope :( I get 2 errors: `Protocol 'Hashable' can only be used as a generic constraint because it has Self or assosiated type requirements` AND `Type 'Hashable' does not conform to protocol 'Comparable'` (this one sounds weird :) ) – Bartek Chlebek Sep 29 '14 at 20:41
  • FYI for the latest swift note these two great answers ... http://stackoverflow.com/a/39836054/294884 ... http://stackoverflow.com/a/39835658/294884 – Fattie Oct 18 '16 at 20:07

1 Answers1

12

This is ambiguous because Int is both Hashable and Comparable, and neither of those two protocols are in the same hierarchy. (You can view the Int protocol hierarchy on Swifter.)

func f<T: Hashable>(t: T) {
    println("Hashable: \(t)")
}
func f<T: Comparable>(t: T) {
    println("Comparable: \(t)")
}

let number = 5
f(number)
// error: ambiguous use of 'f'

You can't explicitly tell it which function to call, because of the associated type requirements of each protocol, but what you can do is define a third function:

func f<T: Comparable where T: Hashable>(t: T) {
    println("Both Hashable & Comparable: \(t)")
}
f(number)
// Both Hashable & Comparable: 5

This is how Swift implements the ..< operator, which otherwise would be ambiguous for types that implement both Comparable and ForwardIndexType.


To expand a little further, here's a look at what I meant by "you can't explicitly tell it which function to call, because of the associated type requirements of each protocol." Protocols can be used as types, as described in the Swift book chapter on Protocols:

protocol RandomNumberGenerator {
    func random() -> Double
}

class Dice {
    let generator: RandomNumberGenerator
    // ...
}

In this example, the generator property can be any type that conforms to RandomNumberGenerator - similar to how id<ProtocolName> is used in Objective-C. However, protocols can only be used as types when they do not include an associated type or reference Self in their declaration. This unfortunately excludes nearly every built-in type in Swift, including Hashable and Comparable.

Hashable inherits from Equatable, which references Self when defining the == operator:

func ==(lhs: Self, rhs: Self) -> Bool

and Comparable does the same with its operators:

func <=(lhs: Self, rhs: Self) -> Bool
// similar definitions for <, >, and >=

These protocols can only be used as generic constraints, and not used as a type when declaring a variable. (As far as I can tell this is undocumented, but discoverable through error messages.)

Two protocols that don't have that restriction are Printable and BooleanType, so we can look at how they work. Bool is the only built-in type that conforms to BooleanType, and it is also Printable, so that will be our test type. Here are our generic functions p() and the variable t - note that, as before, we can't just call the function with t:

func p<T: Printable>(t: T) {
    println("Printable: \(t)")
}
func p<T: BooleanType>(t: T) {
    println("BooleanType: \(t)")
}

let t: Bool = true
p(t)
// error: Ambiguous use of 'p'

Instead, we need to cast (upcast?) t to a particular protocol using the as keyword, and call a particular generic function that way:

p(t as Printable)
// Printable: true

p(t as BooleanType)
// BooleanType: true

So as long as we have a qualifying protocol, we can choose which variant of the generic method to call.

Community
  • 1
  • 1
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • 1
    To clarify, `Comparable` and `Hashable` methods were just to demonstrate the problem. I do not expect to need them, but I wanted to understand how limited I may be in the future. Now, I'm not sure what you mean by "You can't explicitly tell it which function to call, **because of the associated type requirements of each protocol**". Is there some fundamental issue here with being able to provide the compiler with a hint about which method to use? Do you think we can expect such a feature in the future? – Bartek Chlebek Sep 29 '14 at 21:19
  • Isn't telling the compiler which function to use kind of in opposition to the design of generic programming (in general, not just in Swift)? Regardless, if there's a feature you want to see (or at least be told not to expect) in the future, it's probably best to [file a bug](http://bugreport.apple.com). – rickster Sep 29 '14 at 23:22
  • @BartekChlebek: Added a little (okay, a lot) of explanation on that point. Swift is certainly in flux, but I don't necessarily see that it needs to change. This kind of conflict seems relatively rare, no? – Nate Cook Sep 30 '14 at 01:54
  • @NateCook wow, I appreciate your answer, now it's clear. While I agree that such conflicts should be incredibly rare, I still think that it's a limitation that could be resolved in the future versions of Swift. Your example with `Printable` and `BooleanType` demonstrates that some of such functionality already exists. – Bartek Chlebek Sep 30 '14 at 21:31