29

I have several "Any" value types that I want to compare.

var any1: Any = 1
var any2: Any = 1

var any3: Any = "test"
var any4: Any = "test"

print(any1 == any2)
print(any2 == any3)
print(any3 == any4)

Using the == operator shows an error:

"Binary operator '==' cannot be applied to two 'Any' (aka 'protocol<>') operands"

What would be the way to do this ?

the Reverend
  • 12,305
  • 10
  • 66
  • 121
  • 6
    How can you compare things you know nothing about? Why are you making them `Any`? – Wain Jan 13 '16 at 23:21
  • 1
    Im just testing swift capabilities. – the Reverend Jan 13 '16 at 23:30
  • 1
    @theReverend `===` compares references, so it can only be applied to reference types - which all of them conform to `AnyObject` – Sebastian Osiński Jan 13 '16 at 23:33
  • @Wain, You can compare two things that you know nothing about by asking them to compare themselves. I suspect that the idea is that if the two things are structs then the content needs to be compared but if they are class objects then a content compare or a reference compare might be needed. This might come into play when writing a generic that can hold class objects, structs, or "native" types like Int. – David Rector Jan 28 '23 at 01:12

8 Answers8

32

The only way to do this is with a function other than == that takes a type parameter, and then compares the values if they are both of that type:

func isEqual<T: Equatable>(type: T.Type, a: Any, b: Any) -> Bool {
    guard let a = a as? T, let b = b as? T else { return false }

    return a == b
}

Now, using your variables above, you can compare them like this:

var any1: Any = 1
var any2: Any = 1

var any3: Any = "test"
var any4: Any = "test"

isEqual(type: Int.self, a: any1, b: any2)      // true
isEqual(type: Int.self, a: any2, b: any3)      // false
isEqual(type: String.self, a: any3, b: any4)   // true
Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • 1
    Shmart! But is it neccessey to pass 'type', looks like it's not used anywhere. – Michael Voline May 06 '18 at 21:08
  • i believe it is necessary despite not using it. Swift requires you to "use" the generic parameter in some part of the signature. however, couldn't you just cast `a` and `b` to `Equatable` directly? – Alex Bollbach Jan 07 '19 at 18:35
  • 2
    @MichaelVoline it is used, in the first line of the function. The inputs are casted as the given type, and if either fails then they are considered to be not equal. If the type casts succeed then equality is determined by the == function which is implemented for that type. The point is that it only makes sense to compare two values of the same type, so we have to verify first that they are the same type. But if you didn't give the type to the function then the only other possible implementation would be to type cast to _every_ type that there is until one of them succeeds, which is impossible – jeremyabannister Feb 14 '19 at 20:49
16

You can do it like this by using AnyHashable:

func equals(_ x : Any, _ y : Any) -> Bool {
    guard x is AnyHashable else { return false }
    guard y is AnyHashable else { return false }
    return (x as! AnyHashable) == (y as! AnyHashable)
}

print("\(equals(3, 4))")        // false
print("\(equals(3, equals))")   // false
print("\(equals(3, 3))")        // true

As not every Equatable has to be Hashable, this might fail under rare circumstances.

Usually there is no reason for using above hack; but sometimes you will need it, just as sometimes AnyHashable is needed.

E_net4
  • 27,810
  • 13
  • 101
  • 139
Steven Obua
  • 934
  • 1
  • 8
  • 17
  • It's fine to use. It's just not any different than Aaron Rasmussen's answer (see my answer for why), and it's not written as Swiftily. –  Apr 06 '20 at 00:59
  • 1
    It is very different from Aaron's answer, as it introduces `AnyHashable`, which is basically compiler magic. Once you know that, you can of course write this in any form you like, for example in the form you wrote it in, also using `AnyHashable`. – Steven Obua Apr 06 '20 at 04:54
  • 1
    To make the difference more clear: Aaron's answer says, you cannot do what you want to do, you need to use a type parameter for that. My answer says, you can actually do what you want, just substitute `AnyHashable` for the type parameter. I think answers cannot get much more different than that – Steven Obua Apr 06 '20 at 05:49
  • Just because `AnyHashable` is more likely to work than any other type, when the desired `==` overload is unknown, it's no so wonderful as to be worth using as an invisible default. Especially without documentation! With Aaron's, you can use `isEqual(type: AnyHashable.self, a: any1, b: any2)`. –  Apr 06 '20 at 08:24
  • 1
    `AnyHashable` is the only **interesting type** that will work. With the help of compiler magic. Otherwise, you know the type already, and then there is no problem in the first place. The problem is, you don't know anything about the type of `Any`, how do you compare two values of type `Any` for equality? The only practical solution to this problem involves `AnyHashable`. – Steven Obua Apr 06 '20 at 17:25
  • 1
    Now, you can do all sorts of additional non-relevant acrobatics as you do in your solution. I have no problem with that, just don't advertise it as better, because it isn't. In fact, your solution is a complication of my solution, not an extension of Aaron's. – Steven Obua Apr 06 '20 at 17:33
  • Seems like there is a need for an AnyEquatable which would make this work in all cases. – Ian Lovejoy May 08 '20 at 01:36
4

To use == operator, type has to conform to Equatable protocol. Any protocol does not conform to Equatable protocol, so there is no way to compare two Any values. It's logical - Any is too broad term - values can have no 'common denominator'.

What's more, Swift doesn't allow to compare two Equatable values which have different type. E.g. both Int and String conform to Equatable but 1 == "1" does not compile. The reason for that is the declaration of == in Equatable protocol: func ==(lhs: Self, rhs: Self) -> Bool. This Self basically means that both arguments have to have the same type. It it's kind of a placeholder - in implementation for specific type, Self should be replaced with the name of this type.

Sebastian Osiński
  • 2,894
  • 3
  • 22
  • 34
  • 1
    what if I don't know type of objects to be compared? Is it possilbe to declare any "common type which allows comparison"? In objective-C it was simpler - all (or most) objects have common parent `NSObject` which already supports comparison – Vyachaslav Gerchicov Oct 15 '18 at 13:37
4

Aaron Rasmussen's answer can also be used as an extension, transformed to use potentially non-any Any arguments, like so:

public extension Equatable {
  /// Equate two values of unknown types.
  static func equate(_ any0: some Any, _ any1: some Any) -> Bool {
    ((any0, any1) as? (Self, Self)).map(==) ?? false
  }
}
let int: some Any = Int.random(in: .min...(.max))
let bool: any Any = Bool.random()

XCTAssertTrue(Int.equate(int, int))
XCTAssertTrue(.equate(bool, bool))
XCTAssertFalse(.equate(int, int))

XCTAssertTrue(AnyHashable.equate(bool, bool))
XCTAssertFalse(AnyHashable.equate(bool, int))

struct Equatable: Swift.Equatable { }
XCTAssertTrue(Equatable.equate(Equatable(), Equatable()))
2

There's much more elegant way available in Swift 5.7. It doesn't require to provide a specific type and is suitable for using with generics. Credits to this blog:

extension Equatable {
    func isEqual(_ other: any Equatable) -> Bool {
        guard let other = other as? Self else {
            return other.isExactlyEqual(self)
        }
        return self == other
    }
    
    private func isExactlyEqual(_ other: any Equatable) -> Bool {
        guard let other = other as? Self else {
            return false
        }
        return self == other
    }
}

func areEqual(first: Any, second: Any) -> Bool {
    guard
        let equatableOne = first as? any Equatable,
        let equatableTwo = second as? any Equatable
    else { return false }
    
    return equatableOne.isEqual(equatableTwo)
}
Leo
  • 3,003
  • 5
  • 38
  • 61
1

We can solve it in the following way

enum SwiftDataType
{
    case String
    case Int
    case Int64
    case Double
    case Bool
    case Undefined
}

func getType( of : Any ) -> SwiftDataType
{
    if let type = of as? String
    {
        return SwiftDataType.String
    }
    else if let type = of as? Int
    {
        return SwiftDataType.Int
    }
    else if let type = of as? Int64
    {
        return SwiftDataType.Int64
    }
    else if let type = of as? Double
    {
        return SwiftDataType.Double
    }
    else if let type = of as? Bool
    {
        return SwiftDataType.Bool
    }
    else
    {
        return SwiftDataType.Undefined
    }
}

func isEqual( a : Any, b : Any ) -> Bool
{
    let aType : SwiftDataType = getType( of : a )
    let bType : SwiftDataType = getType( of : b )
    if aType != bType
    {
        print("Type is not Equal -> \(aType)")
        return false
    }
    else
    {
        switch aType  {
        case SwiftDataType.String :
            guard let aValue = a as? String, let bValue = b as? String else
            {
                return false
            }
            return aValue == bValue

        case SwiftDataType.Int :
            guard let aValue = a as? Int, let bValue = b as? Int else
            {
                return false
            }
            return aValue == bValue

        case SwiftDataType.Int64 :
            guard let aValue = a as? Int64, let bValue = b as? Int64 else
            {
                return false
            }
            return aValue == bValue

        case SwiftDataType.Double :
            guard let aValue = a as? Double, let bValue = b as? Double else
            {
                return false
            }
            return aValue == bValue

        case SwiftDataType.Bool :
            guard let aValue = a as?  Bool, let bValue = b as? Bool else
            {
                return false
            }
            return aValue == bValue

        default:
            return false
        }
    }
}
boopathy
  • 427
  • 2
  • 9
  • 20
1

You can use NSObject ...

var any1: Any = 1
var any2: Any = 1

var any3: Any = "test"
var any4: Any = "test"

var any5: Any? = nil
var any6: Any? = nil

print(any1 as? NSObject == any2 as? NSObject)
print(any2 as? NSObject == any3 as? NSObject)
print(any3 as? NSObject == any4 as? NSObject)
print(any4 as? NSObject == any5 as? NSObject)
print(any5 as? NSObject == any6 as? NSObject)

This should produce :- true false true false true

DaxaR
  • 19
  • 1
  • What if the type is a pure Swift type that does not bridge to an `NSObject` equivalent or a class that does not inherit from `NSObject`? – JAL Dec 04 '19 at 21:00
  • I should add a caveat that design your base class to derive from NSObject root object. I am new to Swift but I found that doing so made some of unit test simpler to code. Otherwise you need to know the class name. Also the above only works for Classes & not Structs – DaxaR Dec 06 '19 at 11:56
1

There is a semi-private function _openExistential, shipped no later than Swift 5.6, that makes this possible.

First, consider the following utilities:

protocol EquatablePair {
    func perform() -> Bool
}

protocol MaybeEquatablePair {
    func maybePerform() -> Bool?
}

struct Pair<T> {
    var lhs: T
    var rhs: T
}

extension Pair: MaybeEquatablePair {
    func maybePerform() -> Bool? {
        (self as? EquatablePair)?.perform()
    }
}

extension Pair: EquatablePair where T: Equatable {
    func perform() -> Bool {
        lhs == rhs
    }
}

Here, we have a conditional conformance of Pair to EquatablePair. This allows us to use self as? EquatablePair to dynamically determine if T is Equatable. The MaybeEquatablePair conformance uses this trick to produce a boolean result if T is Equatable and nil otherwise.

The next part is to get Pair<T> for some concrete type T. We really need to get Pair<T> and not Pair<Any>. This is what helps me distinguish this subtle difference: Any is nothing but a struct of two fields, one being the pointer to the wrapped value’s type and the other the pointer to the actual value. The reality is slightly more complicated but this should give you some intuition. In this way, 1 as Int gives you a plain integer, where as (1 as Int) as Ayn gives you a special struct, hence Pair<Int> is very different from Pair<Any>.

So how can we dynamically fetch some Any’s wrapped value’s type and use that to get a desired Pair<T>? Here comes _openExisential:

func genericEqual(_ lhs: Any, _ rhs: Any) -> Bool {
    func openLHS<LHS>(_ lhs: LHS) -> Bool {
        if let rhs = rhs as? LHS {
            return Pair(lhs: lhs, rhs: rhs).maybePerform() ?? false
        } else {
            return false
        }
    }
    return _openExistential(lhs, do: openLHS)
}

_openExisential takes a wrapped value and use it to call some generic function. This magic function will dynamically fetch the type for its first argument and use that to call the generic function dynamically. This is not possible using plain Swift, in which calls to generic functions must have types resolved statically.

_openExisential can do more than Any. You might have heard the term existential types. This function can, well, open existential containers. It is a very complicated topic. See the Swift Evolution proposal Implicitly Opened Exisentials if you are interested.

My code is simplified from Foundation’s implementation for AttributedString. They seem to have a set of utilities to call Equatable, Encodable, Decodable implementation for Anys. Check out AttributedString.swift and AttributedStringAttribute.swift for more. Start with struct CheckEqualityIfEquatable.

Minsheng Liu
  • 760
  • 7
  • 23
  • Getting segfault while trying to compile this (`swift-driver version: 1.62.15 Apple Swift version 5.7.2 (swiftlang-5.7.2.135.5 clang-1400.0.29.51)`) – Leo Jun 30 '23 at 00:49