Search code examples
swiftswift-protocolsswift4.1

Conditional Protocol Conformance: Cannot convert value of type 'Array<_>' to specified type '[UInt8]'


Trying to extend an Array, String and Dictionary in Swift 4.1 using conditional conformance but am running into a dead end when trying to initialize an array with Elements that are conforming to the Decodable/Encodable through a custom protocol called BinaryCodable.

The following excerpt is from https://github.com/mikeash/BinaryCoder but has been adjusted to use Swift's new conditional conformances in order for it to compile.

extension Array: BinaryCodable where Element: BinaryDecodable, Element: BinaryEncodable {
    public func binaryEncode(to encoder: BinaryEncoder) throws {
        try encoder.encode(self.count)
        for element in self {
            try element.encode(to: encoder)
        }
    }

    public init(fromBinary decoder: BinaryDecoder) throws {
        let binaryElement = Element.self

        let count = try decoder.decode(Int.self)
        self.init()
        self.reserveCapacity(count)
        for _ in 0 ..< count {
            let decoded = try binaryElement.init(from: decoder)
            self.append(decoded)
        }
    }
}

extension String: BinaryCodable {
    public func binaryEncode(to encoder: BinaryEncoder) throws {
        try (Array(self.utf8) as! BinaryCodable).binaryEncode(to: encoder)
    }

    public init(fromBinary decoder: BinaryDecoder) throws {
        let utf8: [UInt8] = try Array(fromBinary: decoder)
        if let str = String(bytes: utf8, encoding: .utf8) {
            self = str
        } else {
            throw BinaryDecoder.Error.invalidUTF8(utf8)
        }
    }
}

However, I'm getting:

Cannot convert value of type 'Array<_>' to specified type '[UInt8]'

For this line:

let utf8: [UInt8] = try Array(fromBinary: decoder)

Any help would be appreciated.


Solution

  • In order for Array<UInt8> to be BinaryCodable, its element type UInt8 must be BinaryCodable, which it isn't. That protocol has default implementations of its required methods, but conformance must still be declared explicitly:

    extension UInt8: BinaryCodable {}
    

    Then your extension String compiles, and you can even get rid of the forced cast as! BinaryCodable in the encoding method (and using guard allows to save one line):

    extension String: BinaryCodable {
        public func binaryEncode(to encoder: BinaryEncoder) throws {
            try Array(self.utf8).binaryEncode(to: encoder)
        }
    
        public init(fromBinary decoder: BinaryDecoder) throws {
            let utf8: [UInt8] = try Array(fromBinary: decoder)
            guard let str = String(bytes: utf8, encoding: .utf8) else {
                throw BinaryDecoder.Error.invalidUTF8(utf8)
            }
            self = str
        }
    }