Search code examples
swiftgenericscomparable

T.RawValue Comparable in Swift is a raw deal?


With Xcode 8.2.1 and Swift 3, if I show the protocol definition for the Error protocol I see this in the generated header:

public protocol Error { }

extension Error { }

// at line 1250:
public func <<T where T.RawValue : Comparable>(lhs: T, rhs: T) -> Bool

public func ><T where T.RawValue : Comparable>(lhs: T, rhs: T) -> Bool

public func <=<T where T.RawValue : Comparable>(lhs: T, rhs: T) -> Bool

public func >=<T where T.RawValue : Comparable>(lhs: T, rhs: T) -> Bool

My question is about the operators that appear immediately under the extension Error { }:

These operators appear to say that default implementations exist for types that have a RawValue subtype that is Comparable.

So I wrote some code in a playground to see if I could use these operators. Here's the first attempt:

struct S<RawValue> {
    let v: RawValue
}

let s = S<Int>(v: 0)
let t = S<Int>(v: 1)

s < t  // error: Binary operator '<' cannot be applied to two 'S<Int>' operands

In the S struct above, we have a RawValue subtype, and when instantiated as shown in variables s and t as Int the RawValue is comparable. Yet < fails.

Here is attempt #2:

enum E: RawRepresentable {
    case value(Int)

    init(rawValue: Int) {
        self = .value(rawValue)
    }

    var rawValue: Int {
        switch self {
        case .value(let rawValue):
            return rawValue
        }
    }
}

let e = E.init(rawValue: 0)
let f = E.init(rawValue: 1)

e < f // error: Binary operator '<' cannot be applied to two 'E' operands

Again, we have a RawValue type that is Comparable, but no joy.

So I guess I'm missing something fundamental here. The Swift "Misc" header tells me that Comparable methods exist for T where T.RawValue: Comparable, but when I try to compare these sorts of Ts it doesn't work.

Any ideas?


Solution

  • Interesting. In Swift 3.1, those overloads show up in the generated header as:

    public func <<T>(lhs: T, rhs: T) -> Bool where T : _SwiftNewtypeWrapper, T.RawValue : Comparable
    
    public func ><T>(lhs: T, rhs: T) -> Bool where T : _SwiftNewtypeWrapper, T.RawValue : Comparable
    
    public func <=<T>(lhs: T, rhs: T) -> Bool where T : _SwiftNewtypeWrapper, T.RawValue : Comparable
    
    public func >=<T>(lhs: T, rhs: T) -> Bool where T : _SwiftNewtypeWrapper, T.RawValue : Comparable
    

    which makes a lot more sense, as without constraining T, there's no knowing that it has a RawValue. So it just looks like a visual error in the pre-Swift 3.1 generated header.

    _SwiftNewtypeWrapper is a protocol, which according to its header, is:

    /// An implementation detail used to implement support importing
    /// (Objective-)C entities marked with the swift_newtype Clang
    /// attribute.
    public protocol _SwiftNewtypeWrapper : RawRepresentable { }
    

    Therefore the overloads you see are used in order to define comparison operations on types that are bridged to Swift by being marked with the swift_newtype Clang attribute (for more info on this attribute, see this article), where their RawValue is Comparable.

    For example, the following:

    #import <Foundation/Foundation.h>
    
    typedef NSString* _Nonnull Foo __attribute__((swift_newtype(enum)));
    
    static const Foo FooBar = @"bar";
    static const Foo FooBaz = @"baz";
    static const Foo FooQux = @"qux";
    

    gets bridged to Swift as:

    public struct Foo : RawRepresentable, _SwiftNewtypeWrapper, Equatable, Hashable, Comparable, _ObjectiveCBridgeable {
    
        public init(rawValue: String)
    
        public static let bar: Foo
        public static let baz: Foo
        public static let qux: Foo
    }
    

    and you are able to use the various comparison operators with instances of Foo:

    func lessThan<T : _SwiftNewtypeWrapper>(lhs: T, rhs: T) -> Bool where T.RawValue : Comparable {
        return lhs < rhs
    }
    
    let b = Foo.bar
    print(lessThan(lhs: b, rhs: b))
    

    (although there appears to be some rough edges around this, for example attempting to use them directly yields an 'ambiguous use of' compiler error)

    However, for 'pure Swift' code, the overloads you've found should bare no relevance. Swift types that conform to RawRepresentable with Comparable RawValues don't automatically get < overloads or Comparable conformance – you have to implement that yourself.