Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Byte literals and bytes

A byte literal spells fixed binary data: protocol magic numbers, framing, test fixtures, search needles. Its type is the built-in immutable type bytes, the byte counterpart of str.

def is_png(header: bytes) -> bool:
    header == b"\x89PNG\r\n\x1a\n"

def main() -> Result[(), IoError]:
    request = b"GET / HTTP/1.1\r\n"
    print(len(request), request[0], request[-1])?
    print(is_png(b"\x89PNG\r\n\x1a\n"))?
    print(b'single "quotes"')?
    Ok(())
16 71 10
True
b"single \"quotes\""

Spelling

A byte literal is b directly followed by a quoted string: b"...", b'...', b"""...""", or b'''...'''. Triple quotes permit physical newlines, as for strings. Each rule has one form:

  • The prefix is a lowercase b. B"..." is rejected with a diagnostic. b followed by anything other than a quote is an ordinary name, so b and B remain usable identifiers.
  • bytes is a built-in type name like str: no function, type, or import binding may take it, so write import std.bytes, not import std.bytes as bytes. A local variable may still be named bytes; the type has no associated functions, so bytes.name always refers to that value.
  • The text holds only ASCII characters. Any other character is rejected at its position with a hint to use \xHH escapes or std.bytes.from_text. A source character never stands for its UTF-8 encoding.
  • Escapes are those of string literals (\0, \n, \r, \t, \\, \', \") plus \xHH, exactly two hexadecimal digits of either case, for any byte. \u{...} is rejected with a hint to write the scalar’s UTF-8 bytes as \xHH; \N and every other escape are errors.
  • There are no other prefixes: no raw form (rb, br) because there are no raw strings, and no formatted form (bf, fb). Build formatted text with a string and convert it with std.bytes.from_text.
  • Adjacent literals are not concatenated, as for strings.
  • A byte literal is not a match pattern; compare with ==.

Values

A bytes value refers to read-only data emitted into the executable. Creating, copying, passing, returning, storing, or comparing one never allocates, and it has no owner to release: assignment copies the reference, like an integer. The contents cannot change.

OperationResult
len(value), value.len()Number of bytes, i64
value[i]The byte at i, u8; list index rules: negative counts from the end, a missing index stops the program with index out of bounds
value[a:b:c]A slice: Result[std.bytes.Bytes, AllocError], a new owned buffer; no import needed
&value[a:b]A shared slice view, &[u8], of the selected bytes; no copy
for byte in value, reversed(value)Each byte as u8
x in valueWhether the u8 x occurs
==, !=Content equality with another bytes
print(value)The literal spelling, which reads back as the same bytes: printable ASCII, \\, \", \n, \r, \t, and \xhh for every other byte; see printing bytes

bytes values can be elements, set members, dictionary keys, class fields, and enum payloads, and can be captured and passed to scoped threads. len is the only method; it lets bytes satisfy a protocol that requires it. There are no ordering comparisons and no concatenation. A slice copies into an owned Bytes rather than producing another bytes. bytes and str are distinct: neither converts to the other implicitly.

Owned buffers

std.bytes.Bytes is the owned, growable buffer. std.bytes.from_bytes(source: &[u8]) copies a literal, or any other byte view, into a new Bytes and returns a Result, because the copy allocates.

Operations that only read bytes, such as find_bytes, matches, extend, and every std.io, std.net, and std.tls stream write, take a &[u8] view. A literal, a Bytes buffer, a list[u8], or part of any of them converts to that view and is read in place; nothing is copied, and there is no implicit conversion between bytes and Bytes. Changing or growing bytes needs a Bytes.

The literal type is a separate prelude type rather than a static &Bytes view so that the prelude does not depend on std.bytes: every program would otherwise know the library class. The &[u8] view is what lets one parameter accept every byte owner.