swiftprotocolsprotocol-extension

Swift protocol extension with specific Self type


I added an extension for the UnsignedInteger protocol to add a hex method that represents the number in hex format. I also want for specific conforming structs to have default value for a parameter. What I wrote is the below.

extension UnsignedInteger {
    func hex(withFieldWidth fieldWidth: Int, andUseUppercase uppercase: Bool = true) -> String {
        return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
    }
}

extension UnsignedInteger where Self == UInt8 {
    func hex(withFieldWidth fieldWidth: Int = 2, andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: fieldWidth, andUseUppercase: uppercase)
    }
}

extension UnsignedInteger where Self == UInt16 {
    func hex(withFieldWidth fieldWidth: Int = 4, andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: fieldWidth, andUseUppercase: uppercase)
    }
}

However, for the UInt8 and UInt16 specific extensions, it seems to be calling itself and not the hex from the first extension block, as explained by the warning message I get for the UInt8 and UInt16 blocks: All paths through this function will call itself.

If I remove the fieldWidh from the UInt8 and UInt16 blocks, calling hex (with hardcoded values for fieldWidth) seems to compile fine, I believe this way it is calling the hex method from the first extension block. Below is the code that compiles fine.

extension UnsignedInteger {
    func hex(withFieldWidth fieldWidth: Int, andUseUppercase uppercase: Bool = true) -> String {
        return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
    }
}

extension UnsignedInteger where Self == UInt8 {
    func hex(andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: 2, andUseUppercase: uppercase)
    }
}

extension UnsignedInteger where Self == UInt16 {
    func hex(andUseUppercase uppercase: Bool = true) -> String {
        // should call the UnsignedInteger implementation with the default parameters
        return hex(withFieldWidth: 4, andUseUppercase: uppercase)
    }
}

Is there a way to specify a default value for the parameter for specific conforming structs when doing a protocol extension?


Solution

  • Is there a way to specify a default value for the parameter for specific conforming structs when doing a protocol extension?

    You have highlighted the problems with this approach in the question already.


    How can I solve it in a different way?

    UnsignedInteger inherits from BinaryInteger that can provide you bitWidth information (UInt8 => 8, UInt16 => 16 and so on).

    extension UnsignedInteger {
        func hex(uppercase: Bool = true) -> String {
            let fieldWidth = self.bitWidth / 4
            return String(format: "%0\(fieldWidth)\(uppercase ? "X" : "x")", self as! CVarArg)
        }
    }
    

    Above makes it work for UInt, UInt8, UInt16, UInt32 & UInt64.


    Taking it one step further, you can do this with FixedWidthInteger & now it will work for all signed and unsigned integers. enter image description here