Skip to main content
1 of 5
Martin R
  • 23.5k
  • 2
  • 36
  • 92

Instead of implementing consume() for each integer type separately, you can implement a single generic method:

mutating func consume<T: BinaryInteger>() throws -> T {
    let size = MemoryLayout<T>.size
    guard idx + size <= bytes.count else {
        throw Error.notEnoughBytes
    }
    defer { idx += size }
    return bytes[idx..<idx + size].enumerated().reduce(0) {
        $0 + T($1.element) << (8 * $1.offset)
    }
}

which can be used for all signed and unsigned integer types (U)Int, (U)Int8, ..., (U)Int64.


Instead of letting the compiler infer the return type from the context, one can alternatively pass it as a parameter:

mutating func consume<T: BinaryInteger>(_: T.Type) throws -> T { ... }

which is then – for example – called as

let crc32 = try buffer.consume(UInt32.self)

I would probably call the method get() or read() instead of consume().


Defining a local enum Error type which conforms to the (global) Error protocol is possible, but might be confusing to the reader. I would use a different name for the concrete error type, for example:

enum ReadError: Error {
    case notEnoughBytes
}

Finally let's have a look how an error would be reported. The caller does not know the actual error type, so a typical calling sequence is:

do {
    let someData = Data(bytes: [1])
    var buffer = ConsumableByteArray(data: someData)
    let crc32: UInt32 = try buffer.consume()
    print(crc32)
} catch {
    print(error.localizedDescription)
}

This produces the output:

The operation couldn’t be completed. (MyProg.ConsumableByteArray.ReadError error 0.)

This can be improved by adopting the LocalizedError protocol (see for example How to provide a localized description with an Error type in Swift? on Stack Overflow):

enum ReadError: Error, LocalizedError {
    case notEnoughBytes
    
    public var errorDescription: String? {
        switch self {
        case .notEnoughBytes:
            return "Not enough bytes in buffer"
        }
    }
}

Now the error output of the above program becomes

Not enough bytes in buffer

You can even store additional information about the error in associated values:

enum ReadError: Error, LocalizedError {
    case notEnoughBytes(available: Int, needed: Int)
    
    public var errorDescription: String? {
        switch self {
        case .notEnoughBytes(let available, let needed):
            return "Not enough bytes in buffer (available: \(available), needed: \(needed))"
        }
    }
}

Then by throwing

 throw ReadError.notEnoughBytes(available: bytes.count - idx, needed: size)

an error message like

Not enough bytes in buffer (available: 1, needed: 4)

is produced.

Martin R
  • 23.5k
  • 2
  • 36
  • 92