5

Let's assume we have a pretty big struct in Swift:

struct SuperStruct {
    var field1: Int = 0
    var field2: String = ""
    // lots of lines...
    var field512: Float = 0.0
}

.. and then we need to implement Equatable protocol:

extension SuperStruct: Equatable {
}

func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {
    return
        lhs.field1 == rhs.field1 &&
        lhs.field2 == rhs.field2 &&
        // lots of lines...
        lhs.field512 == rhs.field512
}

... and we need to write lots of lines of stupid code. Is there a way "to ask" compiler "to do" it for us?

Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
  • You should take a look at protocol extensions. WWDC 2015 Session 408. http://devstreaming.apple.com/videos/wwdc/2015/408509vyudbqvts/408/408_hd_protocoloriented_programming_in_swift.mp4?dl=1 – Leo Dabus Jan 26 '16 at 21:22
  • How hard do you thing it will be to write a small helper that will parse a piece of your code and generate the required method ? Add versions for beans on top of that to track the code that might need to be updated and you are good. – A-Live Jan 26 '16 at 21:25
  • Leo Dabus, Thanks! I am aware of how protocol extension works, but I have no idea how can I use it with my issue. – Valentin Shergin Jan 26 '16 at 21:27
  • A-Live, Yes, i can do something like this but I am looking for compiler native feature. I just believe it should exist. – Valentin Shergin Jan 26 '16 at 21:29

4 Answers4

5

The following answer shows one possible solution; possibly not a recommended one (however possibly of interest for future readers of this question).


If you have a large number of properties which all belong to a somewhat limited of number different types, you could use a Mirror of your structure instances and iterate over over the structures' properties; for each attempting conversion to the different types that you know your properties to be.

I've edited the previous answer (to something I believe is quite much neater), after watching the following WWDC 2015 session (thanks Leo Dabus!):

I'll leave the initial answer in the bottom of this answer as well, as it shows an alternative, less protocol-oriented approach, to make use of this Mirror solution.

Mirror & protocol-oriented solution:

/* Let a heterogeneous protocol act as "pseudo-generic" type
   for the different (property) types in 'SuperStruct'         */
protocol MyGenericType {
    func isEqualTo(other: MyGenericType) -> Bool
}
extension MyGenericType where Self : Equatable {
    func isEqualTo(other: MyGenericType) -> Bool {
        if let o = other as? Self { return self == o }
        return false
    }
}

/* Extend types that appear in 'SuperStruct' to MyGenericType  */
extension Int : MyGenericType {}
extension String : MyGenericType {}
extension Float : MyGenericType {}
    // ...

/* Finally, 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {

    let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
    let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }

    for i in 0..<mLhs.count {
        guard let valLhs = mLhs[i].value as? MyGenericType, valRhs = mRhs[i].value as? MyGenericType else {
            print("Invalid: Properties 'lhs.\(mLhs[i].label!)' and/or 'rhs.\(mRhs[i].label!)' are not of 'MyGenericType' types.")
            return false
        }
        if !valLhs.isEqualTo(valRhs) {
            return false
        }
    }
    return true
}

Example usage:

/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true

Previous Mirror solution:

/* 'SuperStruct' conformance to Equatable */
func ==(lhs: SuperStruct, rhs: SuperStruct) -> Bool {

    let mLhs = Mirror(reflecting: lhs).children.filter { $0.label != nil }
    let mRhs = Mirror(reflecting: rhs).children.filter { $0.label != nil }

    for i in 0..<mLhs.count {
        switch mLhs[i].value {
        case let valLhs as Int:
            guard let valRhs = mRhs[i].value as? Int where valRhs == valLhs else {
                return false
            }
        case let valLhs as String:
            guard let valRhs = mRhs[i].value as? String where valRhs == valLhs else {
                return false
            }
        case let valLhs as Float:
            guard let valRhs = mRhs[i].value as? Float where valRhs == valLhs else {
                return false
            }
            /* ... extend with one case for each type
            that appear in 'SuperStruct'  */
        case _ : return false
        }
    }
    return true
}

Example usage:

/* Example */
var a = SuperStruct()
var b = SuperStruct()
a == b // true
a.field1 = 2
a == b // false
b.field1 = 2
b.field2 = "Foo"
a.field2 = "Foo"
a == b // true
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 1
    Wow, nice hack. +1. Scary, and probably proof that one probably *shouldn't* try to avoid writing `Equatable` conformance more directly. – rickster Jan 27 '16 at 01:18
  • 1
    That's awesome! Probably it is not production solution (it should be slow, I believe) but I definitely can use it in development while a set of properties in SuperStruct unstable. – Valentin Shergin Jan 28 '16 at 17:14
  • How would you make Array conform to MyGenericType? – tungsten Nov 22 '16 at 21:03
4

In Swift 4.1, Equatable/Hashable types now synthesize conformance to Equatable/Hashable if all of the types' members are Equatable/Hashable

SE-0185

Synthesizing Equatable and Hashable conformance

Developers have to write large amounts of boilerplate code to support equatability and hashability of complex types. This proposal offers a way for the compiler to automatically synthesize conformance to Equatable and Hashable to reduce this boilerplate, in a subset of scenarios where generating the correct implementation is known to be possible.

https://github.com/apple/swift-evolution/blob/master/proposals/0185-synthesize-equatable-hashable.md

pkamb
  • 33,281
  • 23
  • 160
  • 191
2

You could make the struct Codable and compare the JSON encoded Data. Not efficient, but could be useful for some applications (e.g. unit tests).

struct SuperStruct: Encodable {
    var field1: Int = 0 
    // ....
    var field512: Float = 0.0
}

let s1 = SuperStruct()
let s2 = SuperStruct()

let encoder = JSONEncoder()
let data1 = try! encoder.encode(s1)
let data2 = try! encoder.encode(s2)
let result = (data1 == data2)

If you like this you could tidy it up into a protocol extension of Encodable.

Robert
  • 37,670
  • 37
  • 171
  • 213
1

No, it doesn't. At least not in any way that's not excessively complicated and based on use (abuse?) of runtime introspection. See dfri's answer for something that technically works, but that is way more complicated than just writing an == implementation that directly compares all fields.

As for your opinions on what "should" be available in Swift, you're more likely to see some effect if you share them with Apple or with the Swift open source community.

Community
  • 1
  • 1
rickster
  • 124,678
  • 26
  • 272
  • 326