Cristian Antonuccio Cristian Antonuccio - 17 days ago 6
Swift Question

Swift extend UInt8 Array

I wanted to extend an array of UInt8 values so I wrote this code in Swift 3:

extension Array where Element: UInt8 {

}


But I got this error:

Type 'Self.Generator.Element' constrained to non-protocol type 'UInt8'

So how do I fix it?

Answer

The syntax Element: X means you want to inpose a type constraint on Element that is defined by X, where X must be a protocol or a class from which Element is derived. As a workaround if you'd like to construct an extension to Array which is only available to arrays with (value type) element UInt8, you could define a protocol to which only UInt8 conforms, and use this protocol as a type constraint in the extension. E.g.

protocol MyType {}
extension UInt8: MyType {}

extension Array where Element: MyType  {
    // ...
}

At its current implementation, any code within the extension knows nothing more about the Element type than what is blueprinted in the MyType protocol (i.e., currently, nothing). Depending on what you want to achieve with your extension, you could add blueprints to MyType that you know are already fulfilled by implementations available to UInt8, e.g. some initializer. But a more sensible approach is to make use of the fact that UInt8 conforms to the useful protocols UnsignedInteger, Equatable and Comparable. By using protocol composition, all methods blueprinted by these protocols can be readily available to Element:s in the extension, however restricting these elements to type conforming to your own protocol MyType (i.e., only UInt8):

protocol MyType {}
extension UInt8: MyType {}

extension Array where Element: MyType & UnsignedInteger {
    mutating func superfluouslyReplaceAllElements(with value: Element) {
        self = self.map { $0.advanced(by: $0.distance(to: value)) }
    } /* using 'advanced(by:)' and 'distance(to:)' methods of
         'Element', blueprinted in 'UnsignedInteger', to which 
         'Element' conforms */

    mutating func elementsSquaredWithOverflow() -> Bool {
        var overflow = false
        self = self.map {
            let result = Element.multiplyWithOverflow($0, $0)
            overflow = overflow || result.overflow
            return result.0
        }
        return overflow // did at least one element cause an arithmetic overflow?
    }
}

var foo: [UInt8] = [14, 87, 13, 240]
print(foo.elementsSquaredWithOverflow()) // true (at least one arithmetic overflow)
print(foo) // [196, 145, 169, 0]

foo.superfluouslyReplaceAllElements(with: 42)
print(foo) // 42, 42, 42, 42

var bar: [UInt16] = [14, 87, 13, 240]
bar.superfluouslyReplaceAllElements(with: 42)
    /* error: type 'UInt16' does not conform to protocol 'MyType' 
       OK, as expected!                                           */

This is just a short lesson in protocols and type constraints, however; if you intend to work with UInt8 sequences in the context of bytes, follow @vadian:s advice in his comment to your question.