Search code examples
arraysswiftgenericsswift3enums

Why doesn't array conform to Equatable, when its items are Equatable in Swift?


UPDATE:

As of Xcode 9.3, which includes Swift 4.1, the array equality works as expected, and the code in the original question compiles without errors.

However, please see the accepted answer, because it provides a better, more modern solution.

The original question is below:


When I try to declare an instance of a generic enum with type [Post], I get an error saying

Type '[Post]' does not conform to protocol 'Equatable'

which is nonsense, because Post conforms to Equatable and I can actually compare two [Post] instances with no compilation errors?


In the following example, I extend Post and Result<T> types with Equatable and then I do a few tests:

  1. Test that I can compare two Post types: OK
  2. Test that I can compare two [Post] types: OK
  3. Test that I can compare two Result<Post> types: OK
  4. Test that I can compare two Result<[Post]> types: ERROR
import Foundation

struct Post {
    let text: String
}
extension Post: Equatable {}
func ==(lhs: Post, rhs: Post) -> Bool {
    return lhs.text == rhs.text
}


enum Result<T: Equatable> {
    case success(result: T)
    case error
}
extension Result: Equatable {
    static func ==(lhs: Result<T>, rhs: Result<T>) -> Bool {
        switch (lhs, rhs) {
        case let (.success(lhsVal), .success(rhsVal)):
            return lhsVal == rhsVal
        case (.error, .error):
            return true
        default:
            return false
    }
}

func test() {

    // Test 1: Check Post type for equality: OK
    let post1: Post = Post(text: "post 1")
    let post2: Post = Post(text: "post 2")

    if post1 == post2 {
        print("equal posts")
    }

    // Test 2: Check [Post] type for equality: OK
    let arrayOfPosts1: [Post] = [ post1, post2 ]
    let arrayOfPosts2: [Post] = [ post1, post2 ]

    if arrayOfPosts1 == arrayOfPosts2 {
        print("equal arrays of post")
    }

    // Test 3: Check Result<Post> type for equality: OK
    let result1: Result<Post> = Result<Post>.success(result: post1)
    let result2: Result<Post> = Result<Post>.success(result: post2)

    if result1 == result2 {
        print("equal results of post")
    }

    // Test 4: Check Result<[Post]> type for equality: ERROR
    // Compiler error: "Type '[Post]' does not conform to protocol 'Equatable'"
    let arrayResult1: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts1)
    let arrayResult2: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts2)

    if arrayResult1 == arrayResult2 {
        print("equal results of array of posts")
    }
}

Solution

  • Swift 4.1 update:

    With the introduction of conditional conformance in Swift 4.1, Array now conforms to Equatable, so the issue should be resolved without the need to resort to any workarounds.

    Also, Swift now allows a type to automatically synthesize Equatable conformance, provided all its members are Equatable, simply by declaring Equatable conformance as part of the original type definition (not an extension) but without implementing any of its requirements. This works for enums provided associated values, if any, are Equatable.

    The code from this question can now be written much more concisely as below:

    import Foundation
    
    struct Post: Equatable {
        let text: String
    }
    
    enum Result<T>: Equatable where T: Equatable {
        case success(result: T)
        case error
    }
    

    This code will pass all the tests specified in the question:

    func test() {
    
        // Test 1: Check Post type for equality: OK
        let post1 = Post(text: "post")
        let post2 = Post(text: "post")
    
        if post1 == post2 {
            print("equal posts")
        }
    
        // Test 2: Check [Post] type for equality: OK
        let arrayOfPosts1 = [post1, post2]
        let arrayOfPosts2 = [post1, post2]
    
        if arrayOfPosts1 == arrayOfPosts2 {
            print("equal arrays of post")
        }
    
        // Test 3: Check Result<Post> type for equality: OK
        let result1 = Result<Post>.success(result: post1)
        let result2 = Result<Post>.success(result: post2)
    
        if result1 == result2 {
            print("equal results of post")
        }
    
        // Test 4: Check Result<[Post]> type for equality: OK
        let arrayResult1: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts1)
        let arrayResult2: Result<[Post]> = Result<[Post]>.success(result: arrayOfPosts2)
    
        if arrayResult1 == arrayResult2 {
            print("equal results of array of posts")
        }
    }
    

    Here is the output:

    test()
    /*
     prints:
     equal posts
     equal arrays of post
     equal results of post
     equal results of array of posts
    */