Why doesn't an array match Equatable when its elements are Equableable in Swift?

UPDATE: with Xcode 9.3, which includes Swift 4.1, array equality works as expected, and the source question code compiles without errors. However, please see the accepted answer because it provides a better, more modern solution.


When I try to declare an instance of a shared enumeration of type [Post] , I get an error

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

which is absurd because Post matches Equatable , and can I really compare two instances of [Post] without compilation errors?


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

  • Check that I can compare two types of Post : OK
  • Verify that I can compare two types of [Post] : OK
  • Verify that I can compare the two types of Result<Post> : OK
  • Verify that I can compare the two types of Result<[Post]> : 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") } } 
+5
source share
3 answers

Swift 4.1 update:

With the introduction of conditional matching in Swift 4.1, Array now Equatable , so the problem should be solved without the need for any workarounds.

In addition, Swift now allows a type to automatically synthesize an Equatable match if all its members are Equatable by simply declaring Equatable match as part of the original type definition (rather than an extension), but without implementing any of its requirements. This works for enumerations, provided that the associated values, if any, are Equatable .

The code from this question can now be written much more briefly, as shown 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 indicated 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 result:

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

This problem completely sucks and is still not fixed in Swift 4.

I worked on the problem, having a different type of ArrayResult specifically for result arrays, in addition to Result

 public enum ArrayResult<T:Equatable> { case success(result: [T]) case failure(error: Error) } extension ArrayResult: Equatable { public static func ==(lhs: ArrayResult<T>, rhs: ArrayResult<T>) -> Bool { switch (lhs) { case .success(let lhsResult): if case .success(let rhsResult) = rhs, lhsResult == rhsResult { return true } case .failure(let lhsError): // We cast associated Error to a NSError so we get Equatable behaviour // (Apple guarantee that Error can always be bridged to an NSError) if case .failure(let rhsError) = rhs, lhsError as NSError == rhsError as NSError { return true } } return false } } func test() { // Test 4: Check Result<[Post]> type for equality: NOW OK let arrayResult1: ArrayResult<Post> = ArrayResult<Post>.success(result: arrayOfPosts1) let arrayResult2: ArrayResult<Post> = ArrayResult<Post>.success(result: arrayOfPosts2) if arrayResult1 == arrayResult2 { print("equal results of array of posts") } } 
+1
source
 public 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 } 

without extension

-1
source

Source: https://habr.com/ru/post/1269636/


All Articles