How do I work with raw bytes in EK9?
← Advanced Type System · Ref: Q1382
Byte is an unsigned 8-bit value - 0 to 255 - and it is the raw-data scalar, not a small number. It is tri-state like every EK9 type.
CREATING
There is no Byte literal. Construct from an Integer, from hex text, or by copy:
fromInteger <- Byte(255) fromHex <- Byte("0xFF") // 0x prefix optional bare <- Byte("FF") copied <- Byte(fromInteger)
Out of range gives UNSET rather than a wrapped value, so a bad octet is visibly absent instead of quietly wrong:
Byte(256)? // false Byte(-1)? // false
NO ARITHMETIC - THIS IS DELIBERATE
Byte has NO +, -, *, / and no ++ or --. The question 'what is 0xFF + 1?' cannot be asked of a Byte at all, so the wraparound bug class does not exist. Java answers it by making byte SIGNED (0xFF reads back as -1, hence the b & 0xFF idiom everywhere), C leaves it to integer promotion, Go wraps silently to 0. EK9 removes the question.
To do arithmetic, convert deliberately:
total <- aByte.toInteger() + 1
Or let #^ promote inside an Integer expression. Note the asymmetry - promotion applies to the ARGUMENT:
- `1 + #^aByte` resolves, and widens: 1 + 0xFF is 256, never 0
- `aByte + 1` does NOT resolve - Byte has no + of its own
HEX IN, HEX OUT
$ emits exactly two upper-case hex characters and the String constructor parses hex, so the round trip always holds:
- `$Byte(15)` gives "0F"
- `Byte($b) == b` is true for all 256 values
Decimal text is deliberately rejected - Byte("255") is UNSET - because "10" cannot mean sixteen to $ and ten to the constructor. Decimal arrives through Byte(Integer).
BITWISE AND SHIFTS
and, or, xor and ~ work as they do on Bits and Boolean. Shifts stay inside eight bits, so bits shifted out are lost and the value never widens:
- `Byte("0F") << 4` gives F0
- `Byte("0F") << 8` gives 00
>> is always a logical shift because a Byte is unsigned - there is no sign bit to propagate.
CONVERSIONS
- `aByte.toInteger()` gives 0-255
- `aByte.toBits()` gives the eight-bit sequence
- `$$aByte` gives a JSON number 0-255, not the hex text
See Q40 for Bits (a variable-length bit sequence, a different job). Use 'ek9 -h Byte' for the full API.
Example
defines module qa.byte.usage defines program ByteUsage() stdout <- Stdout() //Construct from hex text or from an Integer - there is no Byte literal. fromHex <- Byte("0xFF") stdout.println(`fromHex: ${fromHex}`) //$ always emits exactly two upper-case hex characters. fifteen <- Byte(15) stdout.println(`fifteen: ${fifteen}`) //Out of range is UNSET, never a wrapped value. outOfRange <- Byte(256) stdout.println(`outOfRange set: ${outOfRange?}`) //Decimal text is not a Byte - the String form is hex in both directions. decimalText <- Byte("255") stdout.println(`decimalText set: ${decimalText?}`) //Bitwise operators, as on Bits and Boolean. masked <- Byte("AA") and Byte("0F") stdout.println(`masked: ${masked}`) //Shifts stay inside eight bits - what leaves is lost, the value never widens. shifted <- Byte("0F") << 4 stdout.println(`shifted: ${shifted}`) shiftedOut <- Byte("0F") << 8 stdout.println(`shiftedOut: ${shiftedOut}`) //No arithmetic on Byte - promote instead, and it widens rather than wrapping. widened <- 1 + #^fromHex stdout.println(`widened: ${widened}`) //Conversions. asBits <- Byte("AA").toBits() stdout.println(`asBits: ${asBits}`) //The contract that justifies hex-only parsing. roundTripped <- Byte($fifteen) stdout.println(`roundTrip holds: ${roundTripped == fifteen}`)
Other ways to ask this
- Is there a byte type in EK9?
- How do I represent a single octet of binary data?
- Why can I not add two Bytes together?
- How do I convert a Byte to hex text and back?
Coming from another language?
Java: byte is SIGNED -128..127, so reading 0xFF from a stream gives -1 and nearly every use is written 'b & 0xFF'; forgetting the mask is a classic binary-parsing bug the compiler cannot see. C: unsigned char is 0-255 but participates in silent integer promotion. Python: no byte scalar at all - indexing a bytes object yields a full-range int. Go: byte is uint8 and arithmetic wraps silently (0xFF + 1 == 0). Rust: u8 arithmetic panics on overflow in debug and wraps in release. EK9: Byte is unsigned 0-255 with NO arithmetic operators, so overflow cannot be expressed; out-of-range construction is UNSET, not wrapped; the String form is hex in BOTH directions so $ round-trips.
Keywords: raw, data, shift, bitwise, overflow, hexadecimal, unsigned, tristate, octet, byte, binary, hex