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.bfollowed by anything other than a quote is an ordinary name, sobandBremain usable identifiers. bytesis a built-in type name likestr: no function, type, or import binding may take it, so writeimport std.bytes, notimport std.bytes as bytes. A local variable may still be namedbytes; the type has no associated functions, sobytes.namealways refers to that value.- The text holds only ASCII characters. Any other character is rejected at its
position with a hint to use
\xHHescapes orstd.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;\Nand 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 withstd.bytes.from_text. - Adjacent literals are not concatenated, as for strings.
- A byte literal is not a
matchpattern; 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.
| Operation | Result |
|---|---|
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 value | Whether 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.