Travis Griggs
Travis Griggs

Reputation: 22252

Failing to implement Protocol Extension for conforming type

(feel free to retitle question as appropriate)

I'm working with a lot of BLE data, and for debugging purposes, I've found it easy to extend UInt8 with a HEX computed variable:

extension UInt8 {
    var HEX:String {
        return String(format: "%02X", self)
    }
}

// 190.HEX --> "BE"

I found myself wanting a lowercase variant. And then I wanted it for UInt32 and UInt16 as well. Since the only thing that changes is the number of digits to print, I thought I could do this with a protocol of sorts (at least for education purposes).

protocol HexPrintable {
    var hexDigitCount:Int { get }
}

extension UInt8:HexPrintable {
    var hexDigitCount:Int {
        return 2
    }
}

extension UInt16:HexPrintable {
    var hexDigitCount:Int {
        return 4
    }
}

extension UInt32:HexPrintable {
    var hexDigitCount:Int {
        return 8
    }
}

Then comes the part where I want to take advantage of this and provide default implementations of the HEX and hex methods:

extension HexPrintable {
    var HEX:String {
        return String(format: "%0\(self.hexDigitCount)X", self)
    }

    var hex:String {
        return String(format: "%0\(self.hexDigitCount)x", self)
    }
}

I get a compiler error Argument type 'Self' does not conform to expected type 'CVarArgType'.

I think I understand this. It's saying that as a protocol, it can't guarantee that adopting types will be of a type (CVarArgType) that could be used in the String initializer like that. So I thought I could use a where clause for the first time. I modified my protocol extension to look like:

extension HexPrintable where Self == CVarArgType { ...

Which leads to a Same-type requirement makes generic parameter 'Self' non-generic. At which point my amateur type theorist understanding overflowed. What is the magic to make my two extension methods on different UInt sizes work?

Upvotes: 1

Views: 593

Answers (1)

Martin R
Martin R

Reputation: 539685

The correct syntax would be

extension HexPrintable where Self : CVarArgType { ... }

Alternatively, make your HexPrintable protocol inherit from CVarArgType:

protocol HexPrintable : CVarArgType {
    var hexDigitCount:Int { get }
}

Note that you can implement the same functionality with a single extension on IntegerType, using sizeofValue() to determine the output width:

extension IntegerType where Self : CVarArgType {
    var HEX : String {
        let size = sizeofValue(self)
        return String(format: "%0\(2*size)X", self)
    }
}

But there is another problem: The %X format expects a Int32 argument (corresponding to the C int type). Both your and my above code will not produce correct results for values exceeding the range of a 32-bit integer.

Various possible solutions to that problem are given e.g. in How to create a generic integer-to-hex function for all Integer types? (and I just added another one).

Upvotes: 1

Related Questions