Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equatable alternatives for Void in Swift

Tags:

swift

I'm trying to add conditional Equatable conformance to a type say Box<T>, if T is Equatable. Since Swift.Void is not Equatable, Box<Void> is not Equatable.

struct Box<T> {
    //...
}

extension Box: Equatable where T: Equatable {

}

I can define a new type like below as a solution:

public struct Empty: Equatable {

}

And then use Box<Empty> instead of Box<Void> and that would work. But, wondering if there any alternatives to introducing a new type.

Update: I tried this but it doesn't work

struct Box<T> {
    //...
}

extension Box: Equatable where T: Equatable {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}
extension Box where T == Void {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

I get an error during compilation: FooBarBaz does not conform to protocol Equatable

enum FooBarBaz: Equatable { 
    case success(box: Box<Void>)
    // case success(box: Box<Int>) compiles as expected.
}

Please note that I'm using Swift 4.1

like image 302
pshah Avatar asked Sep 14 '25 14:09

pshah


1 Answers

I get an error during compilation: FooBarBaz does not conform to protocol Equatable

This half-answer focuses on explaining why the approach you've tried yourself will not (yet) work.

There is a limitation, currently, with conditional conformances, that will limit you from using this particular technique to achieve your goal. Citing SE-0143: Conditional conformances, implemented in Swift 4.1:

Multiple conformances

Swift already bans programs that attempt to make the same type conform to the same protocol twice, e.g.:

...

This existing ban on multiple conformances is extended to conditional conformances, including attempts to conform to the same protocol in two different ways.

...

The section overlapping conformances describes some of the complexities introduced by multiple conformances, to justify their exclusion from this proposal. A follow-on proposal could introduce support for multiple conformances, but should likely also cover related features such as private conformances that are orthogonal to conditional conformances.

Which wont allow us to construct multiple conditional conformances as e.g.:

struct Box<T> { }

extension Box: Equatable where T == Void {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

// error: redundant conformance of 'Box<T>' to protocol 'Equatable'
extension Box: Equatable where T: Equatable {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

On the other hand, if we look at your own example:

struct Box<T> { }

extension Box: Equatable where T: Equatable {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

extension Box where T == Void {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

// error: type 'Void' does not conform to protocol 'Equatable'
func foo<T: Equatable>(_ _: T) { print(T.self, "is Equatable") }
foo(Box<Void>())

Swift accurately identifies that Box<Void> is not Equatable: the extension Box where T == Void will not mean that Box<Void> conforms to Equatable, as it does not leverage a conditional conformance of Box to Equatable when T is Void (it just provides a == method in case T is Void.

Conditional conformances express the notion that a generic type will conform to a particular protocol only when its type arguments meet certain requirements.


As a side note, the following example yields expected results:

struct Box<T> { }

extension Box: Equatable where T == () {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

func foo<T: Equatable>(_ _: T) { print(T.self, "is Equatable") }
foo(Box<Void>()) // Box<()> is Equatable

whereas, peculiarly, replacing the conditional conformance of Box to Equatable if T == () with the typedef of (), namely Void, crashes the compiler:

struct Box<T> { }

extension Box: Equatable where T == Void {
    static func ==(lhs: Box<T>, rhs: Box<T>) -> Bool {
        return true
    }
}

func foo<T: Equatable>(_ _: T) { print(T.self, "is Equatable") }
foo(Box<Void>()) // compiler crash

Assertion failed: (isActuallyCanonicalOrNull() && "Forming a CanType out of a non-canonical type!"), function CanType,

file /Users/buildnode/jenkins/workspace/oss-swift-4.1-package-osx/swift/include/swift/AST/Type.h, line 393.

...

Edit: apparently is a (now resolved) bug:

  • SR-7101: Compiler crash when implementing a protocol for an enum using generics
like image 182
dfrib Avatar answered Sep 17 '25 06:09

dfrib