Ambiguous operator ==

Hi,

i have following swift code:

import UIKit

class ViewController: UIViewController {
  let i1 = UIImage()
  let i2 = UIImage()

  override func viewDidLoad() {
    super.viewDidLoad()
    i1 == i2 // <------------------- Ambiguous use of operator '=='
  }
}

protocol ImageHolder {
  func equals(_ o2: ImageHolder) -> Bool
}

func ==(lhs: ImageHolder, rhs: ImageHolder) -> Bool {
  lhs.equals(rhs)
}

extension UIImage: ImageHolder {
  func equals(_ o2: ImageHolder) -> Bool {
    true
  }
}

Full error message

~/work/Example/Example/ViewController.swift:16:8: error: ambiguous use of operator '=='
    i1 == i2
       ^
~/work/Example/Example/ViewController.swift:24:6: note: found this candidate
func ==(lhs: ImageHolder, rhs: ImageHolder) -> Bool {
     ^
ObjectiveC.NSObject:2:24: note: found this candidate
    public static func == (lhs: NSObject, rhs: NSObject) -> Bool

And also it could be inside of the generated conformance to Equatable protocol by some Struct with an UIImage property.

Why compiler couldn't infer that i want use ==(lhs: UIImage, rhs: UIImage) operator of UIImages conformance to Equatable instead of global operator ==(lhs: ImageHolder, rhs: ImageHolder)? Is there any way to fix such errors?

Based on what the compiler tells you, there is no such function. Instead, the two options are:

  1. == (lhs: NSObject, rhs: NSObject) -> Bool
  2. == (lhs: ImageHolder, rhs: ImageHolder) -> Bool

As you allude to, you can create a function which takes arguments of concrete type, which would be preferred.

Well, the error the compiler tells you basically means that you did not specify what's supposed to happen exactly in an unambiguous way, so the "fix" is to be exact.
As is written, the code really gives no clue for the compiler what to infer at all.

You could do something like

  let myEqualOp = (==) as (ImageHolder, ImageHolder) -> Bool
  myEqualOp(i1, i2)

inside your viewDidLoad but that kind of defeats the purpose of having an operator and not a regular function, I guess.

As @xwu points out, the compiler has two options, but that is not the only cause of the problem, imo.
Both of these operators are global in the sense that when you write them out, syntactically they look like they only belong to a module (at best), not a specific type. Of course the underlying function == (lhs: NSObject, rhs: NSObject) -> Bool is implemented on NSObject, but with the way Equatable and the == operator work, it "looks like" a function that's defined on the module only. You wouldn't write NSObject.==(lhs: img1, rhs: img2) (as you would for other static functions).
Your own == is then truly a global function on your module.

Without specifying which module to refer to now, the compiler cannot know which one to use, as both have basically equal "priority" (for lack of a better word). Or rather I should say nobody can know that, we can only guess. There is no inherent "hierarchy" between your ImageHolder protocol and NSObject (well, in this case the UIImage subclass), so how can we say which one makes more sense?

For normal functions that are not operators, you prefix the function name with the module name in these cases, but I don't know how to do that for operators. I played around a bit with it, but didn't find a way, and I guess it might not be possible? At least the pseudo-syntax I provided above does not work, neither for NSObject's version nor for the really global function/operator you defined.

That being said, I'd advise against defining a module-wide (i.e. global) function that is also an operator at all. Especially the equality operator is strongly tied to the Equatable protocol, so even if you could specify which version to use it can cause all kinds of confusion. The Equatable protocol is exactly meant to ensure the operator is well defined, why "overload" it in such a way?

You don't need UIKit or NSObject for this example. Let's distill it:

protocol ImageHolder {
    func equals(_ rhs: ImageHolder) -> Bool
}
class Base {
}
class C: Base, ImageHolder {
    func equals(_ rhs: ImageHolder) -> Bool {
        fatalError("TODO")
    }
}
func ==(lhs: ImageHolder, rhs: ImageHolder) -> Bool {
    fatalError("TODO")
}
let a = C(), b = C()
a == b // ✅

So far so good. Now add == to the base class:

class Base {
    static func ==(lhs: Base, rhs: Base) -> Bool {
        fatalError("TODO")
    }
}
...
a == b // 🛑 Ambiguous use of operator '=='

Same error as you are getting as now Swift doesn't know which one to use: the one in the base class or the one in the protocol.

You could attempt to "fix" the issue by doing:

let a: ImageHolder = C(), b: ImageHolder = C()

It's no longer ambiguous, but now you'd need to implement some logic inside protocol's ==:

class Apple: ImageHolder {...}
class Orange: ImageHolder {...}

func ==(lhs: ImageHolder, rhs: ImageHolder) -> Bool {
    // hmm, `lhs` could be Apple and `rhs` could be Orange, 🤔
    // let's return false in such cases
    // ...
}

i.e. it's "doable" that way although not something Swift encourages you to do.


A rant about Swift operators

As a side note, personally I think the way operators (including ==) implemented in Swift (as static functions) is inferior to how they are implemented in C++ (instance methods). This leads to particularly awkward and surprising results when both subclass and base class defines ==.

1 Like
  1. Because if i conform ImageHolder to Equatable i would need to put any ImageHolder everywhere.
  2. It would lead to awkward code
func ==(lhs: ImageHolderImpl, rhs: ImageHolderImpl) -> {
  (lhs as ImageHolder) == (rhs as ImageHolder)
}

in every conformant type of ImageHolder

Yes, I understand that you probably had a reason for not having your protocol adopt Equatable, but under the circumstances I would then not recommend to define a separate ==, i.e. give it at least a different name (I know that it loses its "operator-ness" then).

There's probably also a way to have a type structure in your app that plays nicely with Equatable, but that might be a) overkill and b) not easy to come up with without extensively looking at what your app does and how, etc.