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:
Post
types: OK
[Post]
types: OK
Result<Post>
types: OK
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")
}
}
method without including a closure in each call, extend the StreetAddress type to conform to Equatable . The StreetAddress type now conforms to Equatable . You can use == to check for equality between any two instances or call the Equatable -constrained contains(_:)
When a value is changed from old value, this notifies to subscribers, so this generics type should conform to Equatable protocol. But when a type is String? , Xcode raise Type 'String?' does not conform to protocol 'Equatable' error.
In Swift, an Equatable is a protocol that allows two objects to be compared using the == operator. The hashValue is used to compare two instances. To use the hashValue , we first have to conform (associate) the type (struct, class, etc) to Hashable property.
When should your Swift types be equatable or comparable? “Equatable” relates to being equal, and “comparable” relates to the comparison between objects. This is important, because how can we be certain that two complex objects are the same? In many circumstances, this is something that you should decide.
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
*/
This issue totally sucks and still isn't fixed in Swift 4.
I worked around the issue by having a different type ArrayResult
specifically for arrays of results, 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")
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With