Not possible to conditionally conform something to a protocol multiple times?

My favourite feature in Swift 4.1 is conditional conformance, but I noticed that it doesn't seem to be possible to conditionally conform something to the same protocol multiple times:

protocol TestProtocol { }

struct TestStruct<T> { }

extension TestStruct: TestProtocol where T == String { }

extension TestStruct: TestProtocol where T == Int { }

Redundant conformance of 'TestStruct' to protocol 'TestProtocol'

The reason I came across this was because I was trying to make an Array conform to Equatable when its element was a specific custom protocol. That protocol defined its own way of comparing any two conforming objects for equality, even if they weren't the same concrete types:

protocol AnyDataType {
	func isEqual(to otherDataType: AnyDataType) -> Bool
}

My attempt to conform Array (which would allow me to compare two [AnyDataType], and even two [[AnyDataType]]):

extension Array: Equatable where Element == AnyDataType {
	static func == (lhs: [Element], rhs: [Element]) -> Bool {
		guard lhs.count == rhs.count else {
			return false
		}
		
		return !zip(lhs, rhs).map({ $0.isEqual(to: $1) }).contains(false)
	}
}

This compiles, but it generates the following warning, and such arrays don't appear to actually be Equatable anyway:

Conformance of 'Array' to protocol 'Equatable' was already stated in the type's module 'Swift'

I'm guessing this is just a limitation in Swift, and it can't (yet) do what I'm trying to get it do to. But I wanted to confirm if that was the case, and if there was a reason for this limitation.

Reading the conditional conformances section of the Generics Manifesto may clarify everything for you. There is an example practically equal to yours within.

In short, it's an intended restriction. Banning disjoint multiple conformances is, however, a consistency matter, which can be addressed and proposed later, when conditional conformances are fully implemented. Right now they would most likely be out of priority.

1 Like

Thanks, I wasn't sure if it was overlooked or not (or if I was doing something wrong); I'm glad to know at least it's something that could be addressed in the future!

Is there a specific reason your protocol AnyDataType cannot inherit Equatable?

protocol AnyDataType : Equatable {}

Yes, I have an array of [AnyDataType] which can consist of different concrete types. Conforming AnyDataType to Equatable doesn't help with comparing different types of AnyDataType to each other, and I wouldn't be allowed to declare such an array anyway because it can only be then used as a generic constraint.

I'm not sure how you do equality checks, but I'm assuming it's done using some same type commonality that AnyDataType provides. As such type erasure might be able to help in your situation. Explanation using inline comments.

protocol AnyDataType : Equatable {
  // Your requirments here
  func foo() -> Int
}

extension AnyDataType {
  static func ==(lhs: Self, rhs: Self) -> Bool {
    return lhs.foo() == rhs.foo()
  }
}

final class AnyData<T> : AnyDataType {
  // All requirments would need be done here in a similar fashion
  var _foo: () -> Int
  
  init<U: AnyDataType>(_ store: U) {
    // Assign all methods from store to closures you declared above
    _foo = store.foo
  }
  
  // fulfill protocol requirments
  func foo() -> Int {
    return _foo()
  }
  
  
  static func ==(lhs: AnyData<T>, rhs: AnyData<T>) -> Bool {
    return lhs.foo() == rhs.foo()
  }
}

// Some concrete conforming types
struct A : AnyDataType {
  func foo() -> Int {
    return 1
  }
}

struct B : AnyDataType {
  func foo() -> Int {
    return 2
  }
}

// Quick test
let a = AnyData<Int>(A())
let b = AnyData<Int>(B())

let x = [[a, b], [b, a]]
let y = [[b, a], [b, a]]

print(x == y)

Thanks, yes I might use such a method at some point. For now it's not quite critical and I've been making do with how the standard library used to do equality checks for arrays (just define a == operator without trying to make the whole thing conform to Equatable), but it'll certainly be something I keep in mind for the future!

You mean Swift 4.2? Sorry but for a second I thought we already got CC in the last update :smiley:

You did, conditional conformance appears to be available right now in Swift 4.1 according to my testing and the blog post! Although the evolution proposal appears to incorrectly(?) say it's implemented for Swift 4.2.

Holy cannoli, you're right! I literally only considered what's written in the proposal, instead of actually checking what's new in Xcode 9.3 :smiley:

Ok to quickly clarify: even if CC are present in Swift 4.1, an optional cast would fail: e.g.instanceOfAny as? A would result in nil even if the concrete type internally conforms to A. (EDIT)

Thanks for the clarification!

I edited the answer, but the linked PR explains it better anyway.