A consumable byte array, in Swift
Instead of implementing consume() for each integer type separately,
you can implement a single generic method:

mutating func consume&lt;T: FixedWidthInteger &amp; UnsignedInteger&gt;() throws -&gt; T {
    let size = MemoryLayout&lt;T&gt;.size
    guard idx + size &lt;= bytes.count else {
        throw Error.notEnoughBytes
    }
    defer { idx += size }
    return bytes[idx..&lt;idx + size].enumerated().reduce(0) {
        $0 + T($1.element) &lt;&lt; (8 * $1.offset)
    }
}


which can be used for all unsigned integer types UInt, UInt8, ..., UInt64, e.g.

let header: UInt8 = try buffer.consume()


and for the signed integer types via the bitPattern: initializer, e.g.

let word = try! Int16(bitPattern: buffer.consume())


Another option is to copy the bytes into a value of the desired type
instead of bit shifting and adding:

mutating func consume&lt;T: FixedWidthInteger&gt;() throws -&gt; T {
    let size = MemoryLayout&lt;T&gt;.size
    guard idx + size &lt;= bytes.count else {
        throw Error.notEnoughBytes
    }
    var value: T = 0
    bytes.withUnsafeBytes { _ = memcpy(&amp;value, $0.baseAddress! + idx, size) }
    idx += size
    return T(littleEndian: value)
}




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

mutating func consume&lt;T: FixedWidthInteger&gt;(_: T.Type) throws -&gt; 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
}




Now 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.



Finally note that there is a ByteBuffer type as part of the 
SwiftNIO framework,
which can do all this and more. Even if you decide not to use it,
having a look at its documentation and interface might be instructive.