sysl

Module

sysl

The core: what every program has without importing anything.

Import import sysl

Option and Result, the operator and rendering traits, print and the assertions, exit for a program that chooses its own status. Nothing here is asked for by name — a file that declares no imports at all can still write Some(3), assert, f"..." and x + y, and that is what makes this module the one a reader meets first.

It is fourteen files and none of them speaks for the whole, which is why this one exists and holds nothing else. check.sysl is the assertions, option.sysl the optional value, ops.sysl the trait catalog, print.sysl the printing surface; each is a good file with a good header, and any one of them standing in for the module would describe a corner of it as though it were the middle.

Index

DebugDepth esink sink assert assert_eq assert_slice_eq cpu cpu_count debug_address debug_enter debug_leave debug_padded debug_quoted display_bf16 display_bool display_char display_digits display_f128 display_f128_shortest display_f16 display_f32 display_f32_shortest display_fill display_int display_pad display_real display_real_shortest display_str display_uint encode_utf8 eprints eputbytes flush hash_bool hash_str hash_u128 hash_u64 os panic printb printbf16 printc printf16 printf32 printi printq printr prints printu putbytes stderr stdout trap exit Counting Cpu FormatSpec Option Os Range Result Rounding Stderr Stdout Add AnyBits BitAnd BitOr BitXor Debug Display Div Drop Eq Fallible FloatBits Fn0 Fn1 Fn2 Fn3 Fn4 From Hash Index IndexPlace IndexSet Integer Iterate Mul Neg Not One Ord Rem Shl Shr Sub Walk Writer Zero Debug for (..A) Debug for []T Debug for [N]T Debug for bf16 Debug for bool Debug for char Debug for f128 Debug for f16 Debug for f32 Debug for Option[T] Debug for real Debug for Result[T, E] Debug for string Debug for T Debug for unit Display for (..A) Display for []T Display for [N]T Display for bf16 Display for bool Display for char Display for f128 Display for f16 Display for f32 Display for Option[T] Display for real Display for Result[T, E] Display for string Display for T Display for unit Eq for (..A) Eq for []T Eq for [N]T Eq for Option[T] Eq for Result[T, E] Eq for unit Fallible for Counting Fallible for Stderr Fallible for Stdout FloatBits for bf16 FloatBits for f128 FloatBits for f16 FloatBits for f32 FloatBits for real Hash for (..A) Hash for [N]T Hash for bool Hash for char Hash for string Hash for T Hash for unit Iterate for Range[T] One for bf16 One for f16 One for f32 One for real Ord for (..A) Ord for []T Ord for [N]T Ord for unit Writer for Counting Writer for Stderr Writer for Stdout Zero for bf16 Zero for f16 Zero for f32 Zero for real

Constants

DebugDepth

const DebugDepth: int = 32

How deep a supplied rendering may nest before it stops and writes … in place of the rest. A reference is followed like any field, so a structure that refers back to itself would otherwise render for ever; the limit is what makes every value safe to show, and it is deep enough that only a cycle or a very long chain reaches it.

Values

esink

val esink: *Stderr = ptr_cast(0usize)

sink

val sink: *Stdout = ptr_cast(0usize)

Functions

assert

assert(cond: bool, msg: string = "", file: string = __FILE__, line: long = __LINE__)

The condition is checked and nothing else is: a false one names where it was written, and the message if it was given one.

file and line are passed on explicitly, which is the one thing about this that is easy to get wrong: letting panic fill its own defaults would report this line, in this file, for every assertion in every program – the call that fills them would be the one three lines down rather than the reader’s. Forwarding them is how a location travels through a helper.

assert_eq

assert_eq[T: Eq + Debug](got: T, want: T, msg: string = "", file: string = __FILE__, line: long = __LINE__)

got and want in that order, which is Go’s and the order a failure is read in: what happened, then what was meant to.

assert_slice_eq

assert_slice_eq[T: Eq + Debug](got: []const T, want: []const T, msg: string = "", file: string = __FILE__, line: long = __LINE__)

The same for a slice, which needs more than == to be useful: a report saying two slices differ sends its reader to find out where, and finding out where is a loop nobody wants to write at each call. Length first, since a length mismatch explains every index after the shorter one.

cpu

cpu() -> Cpu

Which processor this program was compiled for, on the same terms as os above: a constant the compiler already holds, reaching an expression rather than a gated line.

A program wanting an address width should ask for one – sizeof(usize) is the question, and it is right for a processor this list has not got yet.

cpu_count

cpu_count() -> usize

How many logical processors this machine has online – the number to size a pool of workers by, so that a program starting one job per processor neither leaves processors idle nor queues work behind itself.

Unlike cpu above it is a question, not a constant, because it is about the machine the program is running on rather than the one it was compiled for: the same binary answers 4 on a laptop and 64 on a build server. “Online” is the word that matters – a processor the system has taken offline is not one a program can schedule work on, so it is not counted.

It is never less than one, since no program runs on zero processors, and a caller dividing work by it should not have to say so. It needs an operating system to ask, and says so: a module that has given up os is refused at the call. Where there is no POSIX sysconf to ask – a freestanding image, WASI – the answer is that one.

debug_address

debug_address(a: usize, out: *Writer, fmt: FormatSpec)

An address, in lower-case hex with a 0x and no padding digits – what a raw pointer shows as, the null one as 0x0. A pointer is shown rather than followed: what it points at may be gone.

debug_enter

debug_enter() -> bool

Entered by every supplied rendering of a value with parts; whether there is room to go one level deeper. Each call is paired with a debug_leave, whatever it answered.

debug_leave

debug_leave()

debug_padded

debug_padded[T: Debug](x: T, out: *Writer, fmt: FormatSpec)

x‘s debug rendering in the field fmt describes, which is how a supplied Debug honours a width: the value is shown once into a counting sink with no width to learn how long it comes out, then padded once around a second showing. Nothing is gathered.

debug_quoted

debug_quoted(text: []const u8, quote: u8, out: *Writer, fmt: FormatSpec)

text between two quote bytes, escaped, in the field fmt describes. Measured first where a width was asked for, so nothing is gathered.

display_bf16

display_bf16(x: bf16, out: *Writer, fmt: FormatSpec)

A bf16 the way display_real renders a real, in the digits a bf16 needs; under a precision it widens, exactly, and is %g‘s.

display_bool

display_bool(b: bool, out: *Writer, fmt: FormatSpec)

display_char

display_char(ch: char, out: *Writer, fmt: FormatSpec)

display_digits

display_digits(text: []const u8, out: *Writer, fmt: FormatSpec)

display_f128

display_f128(x: f128, out: *Writer, fmt: FormatSpec)

An f128 as print, str and a plain hole render it: display_real‘s rule at binary128’s width, the fewest digits that read back as the same f128, set out positionally. A precision is a count of significant digits, as display_real reads one, at most forty.

display_f128_shortest

display_f128_shortest(x: f128, out: *Writer, fmt: FormatSpec)

An f128 in the fewest digits that read back as the same f128 – 0.1 for 0.1f128, and the thirty-odd digits a value that needs them has. It is binary128’s own reading, never a real‘s: no digit of it passes through a narrower float.

display_f16

display_f16(x: f16, out: *Writer, fmt: FormatSpec)

An f16 the way display_real renders a real, in the digits an f16 needs; under a precision it widens, exactly, and is %g‘s.

display_f32

display_f32(x: f32, out: *Writer, fmt: FormatSpec)

An f32 the way display_real renders a real, taken apart at its own width rather than widened: 0.1 for f32(0.1), the digits an f32 needs and not the seventeen its widened value would.

display_f32_shortest

display_f32_shortest(x: f32, out: *Writer, fmt: FormatSpec)

An f32 in the fewest digits that read back as the same f32 – 0.1 for f32(0.1), where the same value widened to a real needs seventeen.

display_fill

display_fill(out: *Writer, b: u8, n: int)

display_int

display_int(n: long, out: *Writer, fmt: FormatSpec)

The two below render a number a program already holds at 64 bits, and they are what a caller with a long in hand reaches directly.

Nothing in the library routes through them any longer. Every integer, at every width, renders from the one blanket impl Display in display.sysl, whose buffer is measured from the type it was instantiated at. What that replaced was three ranges of special case: these two below 64 bits, a hand-rolled pair between 64 and 128 where C has no conversion wide enough, and above that a fall back through str that allocated – so the widest values were the ones a no alloc module could not print, which is exactly backwards. The three wide renderers are gone with the routing that chose them; these two stay because a long is a thing programs have.

display_digits above is where all of them ended, and it is where the blanket ends still.

display_pad

display_pad(text: []const u8, out: *Writer, fmt: FormatSpec)

display_real

display_real(x: real, out: *Writer, fmt: FormatSpec)

A float as print, str and a plain hole render it, which is what Rust’s {} writes for the same value: the fewest digits that read back as the same real, set out positionally – 0.1, 1, 100000000000000000000, 0.0000001 – never an exponent and never a trailing .0, with -0, inf, -inf and NaN. A width pads it.

A precision asks for %g instead – that many significant digits, correctly rounded, in whichever of fixed point and an exponent %g picks, at most forty. Both are worked out in sysl, so neither needs a C library or an allocator.

display_real_shortest

display_real_shortest(x: real, out: *Writer, fmt: FormatSpec)

A real written in the fewest digits that read back as the same number, which is what a serializer needs and what display_real cannot give it.

A precision is a count of significant digits, so any fixed one is a choice between losing the value and printing digits nobody asked for: six – the default, and the one str shows a person – turns 3.141592653589793 into 3.14159, and seventeen turns 0.1 into 0.10000000000000001. What a document wants is neither, and the answer has been the same since Steele and White: the shortest text that parses back to the value you started with.

The digits are JavaScript’s and Python’s: the fewest that read back; among those, the closest to the value; and a tie to the even digit. shortest_real finds them exactly, with no round trip through a reader, and shortest.sysl says how. The spelling around them is %g‘s, so 1e+23, 0.5, -0, inf and nan read as they always have. A caller that needs null or Infinity written for a non-finite value is writing a format’s syntax and owns that decision.

display_str

display_str(s: string, out: *Writer, fmt: FormatSpec)

display_uint

display_uint(n: ulong, out: *Writer, fmt: FormatSpec)

encode_utf8

encode_utf8(ch: char, into: []u8) -> usize

A character’s UTF-8 bytes, written into storage the caller owns, answering how many were written. No character needs more than four, so var b: [4]u8 is always enough and always on the stack – which is what keeps this usable where there is no allocator.

The caller supplying the buffer is what makes that true, and it is the same shape every other place in the library takes when a result has a known bound. A slice with fewer than the four bytes is a mistake in the program rather than a truncation, and the bounds check says so: how many bytes are needed is not known until the character has been looked at, so there is no honest way to answer “it did not fit” that is cheaper than having room.

It lives in sysl rather than beside from_utf8 in sysl.text, and that is forced. A submodule may name the standard module freely and sysl may not name back (reference/modules.md § The module graph is acyclic), so an encoder over in sysl.text would be one printc below could not call – and the second encoder that would then have to be written for it is exactly the hand-rolled copy this function exists to prevent. One encoder in an odd place beats two in tidy ones.

eprints

eprints(s: string)

eputbytes

eputbytes(b: []const u8)

The other stream, which is where a program’s diagnostics belong.

The distinction is not decoration. A program whose output is being read by another program – piped, redirected, captured in a variable – writes its answer to one stream and its complaints to the other, so that a complaint never lands in the middle of the answer and a redirect that keeps the answer still lets a person see what went wrong. sysl.args puts a usage error here and its --help text on standard output for exactly that reason: the help was what was asked for.

It goes through write rather than through putchar‘s neighbour fputc, and directly rather than a byte at a time, because there is no buffer between this and the descriptor: a diagnostic written just before the program stops has to have left, and C’s standard error is unbuffered for the same reason. That also makes it the one printing path whose output cannot be lost to a trap, which is worth knowing when a program is being debugged.

A short write is looped over rather than ignored, since a signal may cut one anywhere; a write that reports failure ends the loop, because there is nowhere left to report that to.

flush

flush()

Push everything putbytes has been holding out to wherever standard output is going, right now rather than waiting for the buffer to fill or the process to exit.

C fully buffers a stream that is not a terminal. A live report – rows streamed to a log as they complete, output piped into another program, a test runner reading a child’s output while it still runs – can otherwise sit on everything already printed until the process ends, because putbytes writes through C’s buffered stdout. This is the call that pushes it out early.

It goes through sysl.io.sys‘s fd_flush hook for descriptor 1. On a hosted target the library answers it with fflush(NULL), which flushes every open output stream rather than naming one – stdout is a #define on Darwin (reference/ffi.md § An extern also declares a variable), so an extern naming it by pointer would not link there, and flushing everything costs nothing a program would notice, since there is normally only the one stream open to flush. A target with no C library answers the hook itself. It answers zero, or a negative number where the flush failed.

eprints needs no matching call: eputbytes writes with a raw write(2), which keeps no buffer of its own to hold anything back.

hash_bool

hash_bool(b: bool) -> u64

A bool is one bit, and one bit does not widen to a number in this language – the same reason display_bool exists rather than a widening into the integer renderer.

hash_str

hash_str(s: string) -> u64

FNV-1a over the bytes, which is what a string is: a validated []u8, so there is nothing to decode and every byte counts once.

hash_u128

hash_u128(v: u128) -> u64

Past 64 bits the two halves are mixed separately and combined, because the finalizer above is defined on a word and a wider integer has no single one.

hash_u64

hash_u64(v: u64) -> u64

The mixers a built-in’s Hash membership renders through. A built-in has no impl block, so there is no lowered int.hash to call; what it has is one of these, chosen by type, and naming them is what lets a Hash written for a struct mix its own fields.

This is splitmix64’s finalizer: it is the one that turns a counter into something a table can bucket on, since consecutive integers are the input a hash table actually meets and the identity hash is what makes them collide in a row.

os

os() -> Os

Which operating system this program was compiled for.

It is a constant, not a query. A sysl program is compiled for exactly one target, so the answer is something the compiler already holds and this costs a program nothing at run time – there is no table to consult and no environment to read, and a freestanding target pays for it exactly as much as a hosted one does, which is nothing.

What it adds over #if is expression position. #if gates lines, before the lexer, so a program could already define a different val per platform; what it could not do is pass a platform to a function, compare one, or match on one. This is that half:

val leaf = os() match
    MacOS -> "Library/Caches"
    Windows -> "AppData"
    _ -> ".cache"

There is deliberately no #else under this, so an operating system added to the compiler’s registry without a branch here fails to build for that target rather than quietly answering something else.

panic

panic(msg: string, file: string = __FILE__, line: long = __LINE__) -> never

The location is streamed rather than interpolated, and that is not a style choice: an f-string builds a string, and building one makes heap storage, which would put panic and everything through it out of reach of a module that declared @no_alloc (reference/modules.md § Capabilities are a module property). An assertion is exactly what an allocator-free module wants most, so it is composed out of prints and printi, each of which goes straight to putbytes. Caught by the @no_alloc test that already existed.

printb

printb(b: bool)

printbf16

printbf16(x: bf16)

printc

printc(ch: char)

A char is encoded rather than handed to snprintf, which has no conversion that takes a code point.

printf16

printf16(x: f16)

printf32

printf32(x: f32)

printi

printi(n: long)

Every number renders in sysl: the integers through digits_long and digits_ulong beside display_int, each float at its own width through its Display – the fewest digits that read back as the same value, set out positionally, as Rust’s {} writes them (shortest.sysl). Nothing here is formatting by C, so a target with no C library prints a float exactly as a hosted one does.

printq

printq(x: f128)

An f128, in the fewest digits that read back as the same f128 – rendered at its own width, so it allocates nothing and asks for no C library either.

printr

printr(x: real)

prints

prints(s: string)

printu

printu(n: ulong)

putbytes

putbytes(b: []const u8)

The printing surface, which lives in the library rather than in the compiler. print(a, b, c) is a desugaring onto these one-value functions, chosen by each argument’s static type, so the compiler knows a handful of names and implements no printing of its own.

Everything goes out through the single sink putbytes, and that is not incidental: two mechanisms means two buffers, and output emerging in the wrong order. Where there is a C library it hands the whole buffer to stdio’s fwrite in one call – counted rather than terminated, so an interior NUL goes out like any other byte, which puts, %s and even %.*s would all stop at. It stays on stdio rather than a raw write(1, ...) because C in the same program writing through printf shares that buffer, and one queue is what keeps the two in order.

A freestanding target has no stdio, so there it writes a byte at a time through the putchar the board supplies. That is also one of the two things such a target has to supply: answer putchar with a write syscall, and sysl.io.sys‘s fd_read hook with a read one, and the whole surface above both is unchanged.

A short fwrite is looped over, and one that wrote nothing ends the loop – the stream has failed, and there is nowhere left to report that to.

stderr

stderr() -> *Writer

stdout

stdout() -> *Writer

trap

trap() -> never

Stops the program at once, silently, with the target’s trap instruction: udf on Thumb, brk on AArch64, unimp on RISC-V, ud2 on x86-64. It never returns, runs no defer and flushes nothing, which is what a compiler-inserted check does, and it is legal on every target and under @no_alloc.

Externs

exit

extern exit(code: int) -> never

The hosted exit, and the one C declaration the library offers rather than keeps.

It takes no link name, which is deliberate where the four in sysl.sys all take one: this is the library’s offer of the hosted exit – what unwrap and expect stop the program with, a diagnostic printed and a non-zero status, which is what 11-error-handling.md says a trap does under the os capability – and it is the reason those two need no compiler support. A program stopping itself writes the same thing, so exit is a word every program has and the four platform names are not.

An extern’s symbol is not qualified and cannot be, since it names something the linker already has and the linker knows nothing about sysl’s modules. So this is keyed under sysl like every other name here while going on resolving to exit.

Types

Counting

struct Counting
    n: usize

A sink that keeps the length of what it was given and drops the bytes, which is how a value made of parts honours a width without gathering: the parts are rendered twice and stored nowhere, rather than once into a buffer that has to be allocated. sysl cannot reach sysl.buf – that module is built on this one – so a growable buffer is not available here even if one were wanted.

Public, because the problem it answers is not the library’s. A specifier describes the field the whole value occupies (library/core.md § A specifier is the whole value's field), so any Display rendering more than one part has to know how wide the whole came out before it can pad once – and an implementation written outside sysl had nowhere to measure into but a buffer. []T, Option, Result, Complex[F] and the tuple above all honour a width this way, from five separate files – and so does every derived Display, which the compiler writes in this shape (reference/traits.md § The compiler writes four of them).

Cpu

enum Cpu
    Aarch64
    X86_64
    Riscv64
    Riscv32
    Thumb
    Wasm32
    X86
    Craft

The processor a program was compiled for, named as #if and an asm arm name it.

Thumb rather than Arm, because it is the instruction set that a program gating on a processor has to be right about – a Cortex-M executes Thumb and nothing else. The two widths of RISC-V are two rows for the same reason a 32-bit and a 64-bit x86 are: an address width is the thing most code asking this question is asking about.

FormatSpec

struct FormatSpec
    width: int
    prec: int
    left: bool

What a format string’s flags are carried in (library/core.md § A specifier is the whole value's field): the field the whole value occupies, the precision each renderer reads in its own terms, and which side the padding goes on.

What honours it is the display_* family, whose prose is where the three fields are explained.

Option

enum Option[T]
    Some(value: T)
    None

A value that may be absent. This is an ordinary generic enum rather than anything the analyzer builds: what the compiler knows is that ? unwraps it and which variant means which, and it asks Library.tryVariants for that rather than spelling the names.

unwrap and expect stop the program through exit, which is what 11-error-handling.md says a trap does under the os capability – so neither needs compiler support of its own.

MemberSignatureDescription
is_someis_some(self) -> bool
is_noneis_none(self) -> bool
unwrap_orunwrap_or(self, default: T) -> T
unwrapunwrap(self) -> T
expectexpect(self, msg: string) -> T
unwrap_or_elseunwrap_or_else(self, f: () -> T) -> TThe default computed rather than passed, for a default that costs something to make.
mapmap[U](self, f: T -> U) -> Option[U]The payload transformed, absence carried through.
and_thenand_then[U](self, f: T -> Option[U]) -> Option[U]The same for a function that may itself answer nothing, which is what keeps the two from nesting: map of an Option-returning function is an Option[Option[U]] and this is not.
or_elseor_else(self, f: () -> Option[T]) -> Option[T]Another option where this one is absent – the first that has something wins.
filterfilter(self, pred: T -> bool) -> Option[T]Absent unless the payload passes – Some(4).filter(even) is Some(4) and Some(5) is None.
ok_orok_or[E](self, e: E) -> Result[T, E]The crossing into the other enum: absence becomes the error the caller names.

Os

enum Os
    MacOS
    Linux
    Windows
    Freestanding
    Android
    Wasi

The operating system a program was compiled for.

These are the same five machines #if gates on, under the same names a source file writes at the margin: #if macos and os() == MacOS ask one question, and the two must never come to disagree. Freestanding is a member because a target with no operating system under it is still a target with an answer, and a program that has to say what it is running on would otherwise have one case it could not spell.

Android is its own row rather than a kind of Linux, for the reason Conditional.osSymbol gives: what a program asking this wants to know is which libc and which conventions it has, and Bionic is neither glibc’s nor its headers’. A program that means either writes os() == Linux || os() == Android.

Range

struct Range[T]
    lo: T
    hi: T
    inclusive: bool

A range with both ends written, as a value.

0..<n and 0..n are a form first: in a for header, a slice index, a match pattern and a quantifier the compiler reads the two bounds directly and there is no Range anywhere in what it emits. A counted for is still a counter and a comparison, which is what keeps the most ordinary loop in the language free of a struct, an Option a step, and a call.

What this type is for is every other position — a range bound to a name, passed to a function, returned from one, or handed to something generic. sysl.seq is the reason it exists: a range is the one obvious sequence, and (0..<n).map(f) had no receiver to be a method on.

Only a fully-bounded range becomes one. .., lo.. and ..hi mean something in a slice index and nothing on their own — a value would have to say what an absent bound is, and the answer differs by what it is indexing — so they stay index-only and are refused elsewhere by name.

Result

enum Result[T, E]
    Ok(value: T)
    Err(error: E)

An answer that may have failed, carrying why. The error is a type parameter rather than a fixed one so that a module can answer with an enum of its own failure cases and have them matched exhaustively – which is what the library’s own conversions do (from_utf8 answers with a Utf8Error).

Like Option this is an ordinary generic enum; ? reaches it through Library.tryVariants.

MemberSignatureDescription
is_okis_ok(self) -> bool
is_erris_err(self) -> bool
unwrap_orunwrap_or(self, default: T) -> T
unwrapunwrap(self) -> T
expectexpect(self, msg: string) -> T
unwrap_errunwrap_err(self) -> E
unwrap_or_elseunwrap_or_else(self, f: E -> T) -> TThe failure computed rather than passed, for a default that costs something to make.
mapmap[U](self, f: T -> U) -> Result[U, E]The success transformed, the failure carried through.
map_errmap_err[F](self, f: E -> F) -> Result[T, F]The failure transformed, the success carried through – which is how one layer’s error type becomes the next one’s at a boundary the caller chose.
and_thenand_then[U](self, f: T -> Result[U, E]) -> Result[U, E]The same as map for a function that may itself fail, which is what keeps the two from nesting: map of a Result-returning function is a Result[Result[U, E], E] and this is not.
or_elseor_else[F](self, f: E -> Result[T, F]) -> Result[T, F]Another attempt where this one failed, with the failure in hand to decide by.
okok(self) -> Option[T]The success as an Option, dropping the failure – the crossing back from the enum that carries a reason to the one that does not.
errerr(self) -> Option[E]And the failure as an Option, dropping the success.
expect_errexpect_err(self, msg: string) -> E

Rounding

enum Rounding
    HalfEven
    HalfUp
    Down
    Up
    Floor
    Ceiling

How a value that falls between two representable ones is settled.

One vocabulary for every place the library or the language drops digits – a Decimal division (sysl.math.decimal.divide) and a fixed-point conversion (q15(x, Floor)) read the same six words. Each place takes the ones it can honour and says which where it is handed another.

It sits in the standard module, so a variant is written bare wherever the position says it is a Rounding: q15(x, Floor) needs no import.

HalfEven is what money code should use: rounding halves consistently up biases a long run of sums upward, and rounding them to the nearest even value does not. It is what IEEE 754 does with binary floating point and what accountants call banker’s rounding.

Stderr

struct Stderr
end Stderr

Standard error as a Writer, so a value renders itself onto the diagnostic stream through the same Display it uses for standard output. It carries no state for the reason Stdout does not.

Stdout

struct Stdout
end Stdout

Standard output as a Writer, which is what a value writes itself into when it renders through its own Display rather than through one of the print* functions above.

It is an ordinary sink written in ordinary sysl, and it is worth saying why that is possible at all: a destination fixed at compile time keeps no state, so the type has no fields – and a struct may have none. Everything the compiler used to supply here by hand is now three declarations a reader can check, and the sink they end in is still putbytes, so a freestanding target replaces the same one function it always did.

Traits

Add

trait Add[Rhs = Self, Out = Self]
    add(self, rhs: Rhs) -> Out
MemberSignatureDescription
addadd(self, rhs: Rhs) -> Out

AnyBits

trait AnyBits

A type every bit pattern of whose storage is a value of it, so storage nobody wrote still holds one – an unspecified value, never an invalid one.

The membership is the compiler’s, and nothing implements it: a type is one where every part of it is an integer of whole bytes, a float, a fixed-point number of whole bytes, a raw pointer *T, or an array or a struct of members. A bool, an enum, a char, a subtype with a range, and anything the program counts or reads an address through – &T, a string, a slice, a function pointer – are out, each having bit patterns that are not a value.

It is what sysl.buf‘s set_len asks of a buffer’s element type (library/core.md § AnyBits).

BitAnd

trait BitAnd[Rhs = Self, Out = Self]
    bitand(self, rhs: Rhs) -> Out
MemberSignatureDescription
bitandbitand(self, rhs: Rhs) -> Out

BitOr

trait BitOr[Rhs = Self, Out = Self]
    bitor(self, rhs: Rhs) -> Out
MemberSignatureDescription
bitorbitor(self, rhs: Rhs) -> Out

BitXor

trait BitXor[Rhs = Self, Out = Self]
    bitxor(self, rhs: Rhs) -> Out
MemberSignatureDescription
bitxorbitxor(self, rhs: Rhs) -> Out

Debug

trait Debug
    debug(self, out: *Writer, fmt: FormatSpec)

How a value is shown to the person debugging it: its structure, with nothing hidden.

Every type has one, and none has to be written. The compiler supplies a Debug for every struct and enum that does not write its own – Point(x: 1, y: 2), Shape.Circle(r: 3.0), Color.Red – each field shown through its own Debug, so a string inside a structure is quoted and escaped where a top-level one prints raw. The blocks below are the library’s half: the scalars, and every shape the library builds out of parts.

An impl Debug of a program’s own replaces the supplied one, to hide a field or abbreviate a large buffer, and a structure holding the type shows it through that block.

print, str and an interpolation hole reach Display where a value has one and this where it does not, which is what makes every value printable; f"${x}%?" and debug(x) always reach this. The shape is Display‘s – a sink and the field the whole value occupies – so a debug rendering allocates nothing either.

MemberSignatureDescription
debugdebug(self, out: *Writer, fmt: FormatSpec)

Display

trait Display
    display(self, out: *Writer, fmt: FormatSpec)

How a value renders itself.

A Display writes its value’s text into a sink rather than returning a fresh string, so rendering costs no allocation and a no alloc module can still log. The sink is a *Writer, which is the trait object of 02 – and which this file does not declare: a module’s members are one set however many files they came from, so Writer is reached here with nothing to import.

MemberSignatureDescription
displaydisplay(self, out: *Writer, fmt: FormatSpec)

Div

trait Div[Rhs = Self, Out = Self]
    div(self, rhs: Rhs) -> Out
MemberSignatureDescription
divdiv(self, rhs: Rhs) -> Out

Drop

trait Drop
    drop(self)

What a type does when the last reference to one of its values goes (reference/memory.md § A destructor).

It is for a resource the language does not manage. ARC returns the storage and releases whatever the value holds; what it cannot do is close a descriptor, unmap a region, or hand a handle back to the C library that made it – those are the far end of a *T or an integer, and nothing about them says they are owned. A Drop is where that is said, once, next to the type.

defer is the other way to say it and neither replaces the other. defer close(f) covers every site a program can name; a value can also die where there is no site to write one – a File inside a []File that goes out of scope, a resource inside a struct inside a container. Those are the cases this exists for, and they are why the two are not redundant. Where both would work, prefer the destructor: it is written once, and a caller cannot forget it.

It runs when a box’s strong count reaches zero, and that is the whole of the rule.

  • It runs before the value’s own references are released, so self is intact and a field may be read to close what it names.
  • It runs for a value held behind &T. A value that never reached the heap has no single point of death – it is copied, and a copy is not a second resource – so the destructor is not called for one. reference/memory.md § A destructor states the whole limit.
  • It does not run for a value in a reference cycle, which never reaches zero. Its storage is not returned either; that is the cost of counting rather than collecting, and weak T is what breaks a cycle.
  • It does not run for a value in module storage at the end of the program. Storage that lasts the whole run is never let go of (reference/modules.md § static), and a process exiting is what returns what it held.

It answers nothing, and cannot fail. It runs at an arbitrary point in a teardown with no caller to receive an error and no ? to carry one out. A close whose failure the program cares about is called explicitly, where there is somewhere to report it; the destructor is the backstop for the paths that did not.

MemberSignatureDescription
dropdrop(self)

Eq

trait Eq
    eq(self, rhs: Self) -> bool
MemberSignatureDescription
eqeq(self, rhs: Self) -> bool

Fallible

trait Fallible
    failed(*self) -> bool

Whether a stream has gone wrong, which is the one thing reading and writing were always going to have to agree about.

It is a trait of its own, required by both Writer and Reader, because of what a type that is both runs into otherwise. Two traits may each declare a member of one name for one type, and a call says which by naming the trait (reference/traits.md § Reaching a member through its trait) – but failed takes no arguments, and a program that reads and writes a file has both traits in scope by definition, so nothing about the call could say which was meant and it is refused where it is written. The two halves of the byte surface were built a module apart and each grew a failed of its own; an open file is the first thing that had to be both, and one required trait is what makes the question go away rather than move. A diamond needs no rule of its own: the walk takes each trait the first time it reaches it, so a reader that is also a writer carries these members once.

The default is false, which is the library’s own use of the mechanism 02 calls for: most streams cannot fail, and one that cannot should not have to write down that it cannot.

It latches rather than returning, and that is the shape everything above it is built on: an implementation stays straight-line, print(x) stays a statement, and a caller asks once after a run of operations rather than after each. Whether a stream ended is a separate question from whether it ended badly, which is why reading answers the first with an empty result and leaves this to answer the second.

MemberSignatureDescription
failedfailed(*self) -> bool

FloatBits

trait FloatBits
    type Word
    bits(self) -> Self::Word
    from_bits(b: Self::Word) -> Self

A float’s IEEE 754 encoding, as the unsigned integer of the same width.

This is a reinterpretation, not a conversion. u64(1.0) is 1, the value carried across; 1.0.bits() is 0x3ff0000000000000, the sign, exponent and fraction exactly as the machine lays them out. Nothing is rounded and nothing is refused in either direction: every pattern is some float, and every float – a negative zero, an infinity, a NaN with whatever payload it carries – comes back from from_bits as the same bits it went out as.

The associated type is why this is a trait. Each width answers with the integer of its own width – u64 for real, u32 for f32, u16 for the two half widths – so a body generic over the trait can take a value apart and put it back together without knowing which width it holds.

It is in the standard module rather than beside Float, because the code that wants it is the code below the mathematics: a decimal printer, a hash of a float, a nextafter written on the encoding because a freestanding target has no libm to ask.

MemberSignatureDescription
bitsbits(self) -> Self::WordThe encoding of self, sign bit highest.
from_bitsfrom_bits(b: Self::Word) -> SelfThe float whose encoding is b.

Fn0

trait Fn0[R]
    call(*self) -> R

What a value that can be called looks like, one trait per arity.

There is one per arity rather than one variadic trait because a call’s argument types are part of what it promises, and there is no way to write that variadically. call takes *self so that a closure may carry mutable state – a counter that answers differently each time is a thing worth being able to write.

MemberSignatureDescription
callcall(*self) -> R

Fn1

trait Fn1[A, R]
    call(*self, a: A) -> R
MemberSignatureDescription
callcall(*self, a: A) -> R

Fn2

trait Fn2[A, B, R]
    call(*self, a: A, b: B) -> R
MemberSignatureDescription
callcall(*self, a: A, b: B) -> R

Fn3

trait Fn3[A, B, C, R]
    call(*self, a: A, b: B, c: C) -> R
MemberSignatureDescription
callcall(*self, a: A, b: B, c: C) -> R

Fn4

trait Fn4[A, B, C, D, R]
    call(*self, a: A, b: B, c: C, d: D) -> R
MemberSignatureDescription
callcall(*self, a: A, b: B, c: C, d: D) -> R

From

trait From[T]
    from(value: T) -> Self

A value of one type built from a value of another, without either type being changed to know about the other (reference/errors.md § A ? converts through From).

It is what ? converts through. A function returning Result[T, Fault] may write ? on a call returning Result[U, Io] exactly when Fault implements From[Io], so the two layers of error type are joined once, in the block that says how, rather than at every call that crosses between them. Nothing else in the language reads it; a program may implement and call it for its own conversions like any other trait.

The parameter is the SOURCE and Self is the destination, which is the direction that lets a type accept conversions from types it does not own: impl From[Io] for Fault is written where Fault is, and Io need never have heard of it. Written the other way round the block would have to live with the source, and the orphan rule would put every conversion in whichever module lost the argument.

A type may implement it several times over — From[int] and From[real] for one type are two blocks — and a call says which it means by the argument it wrote.

MemberSignatureDescription
fromfrom(value: T) -> Self

Hash

trait Hash
    hash(self) -> u64

What a value mixes down to when something needs to key on it (library/core.md § Hashing).

A u64 rather than a usize, so a hash means the same thing on every target and a table moved between two of them does not resize because the word did.

MemberSignatureDescription
hashhash(self) -> u64

Index

trait Index[I, E]
    index(self, i: I) -> E

What a subscript means on a type that has no elements of its own.

A built-in array or slice is indexed by walking to an address, and that is the compiler’s; these two are what b[i] means when the receiver is a type someone wrote. The difference is not cosmetic – reading through Index is a call, so it yields a value rather than a place, which is why b[i] += 1 is refused rather than quietly evaluating the receiver and the index twice.

Reading and writing are separate traits because a type may offer one without the other: a view that computes its elements has an Index and no IndexSet to give.

MemberSignatureDescription
indexindex(self, i: I) -> E

IndexPlace

trait IndexPlace[I, E]
    index_place(*self, i: I) -> *E

What makes a subscript on a type someone wrote a place, as an array’s element is.

index_place answers the address of the element, checked against the bounds, and b[i] means *b.index_place(i) – so b[i].f reads one field without copying the element, b[i].f = v and b[i] += 1 write the element where it lives, and ref e = b[i] names it. A type that has this is indexed through it rather than through Index and IndexSet, which it may still implement for code generic over them.

The trait takes *self, so a container whose elements are its own bytes answers an address in the container rather than in a copy of it; a handle whose elements live behind it – a Buf – takes self, which an implementation may. The address is good until the storage moves, and a member that can move it is marked @relocates, which is what keeps a live ref to an element from seeing the call.

MemberSignatureDescription
index_placeindex_place(*self, i: I) -> *E

IndexSet

trait IndexSet[I, E]
    index_set(*self, i: I, v: E)
MemberSignatureDescription
index_setindex_set(*self, i: I, v: E)

Integer

trait Integer: Div + Rem + Ord + Eq + Sub + Mul

The integers, which are the family that is open: i5 and u24 are types a program may name, so there is no finite list of widths to write the blocks above for.

What stands in for the list is one block written over the whole family at once, and Integer is what names the family. It declares no member of its own – a type is one of the integers or it is not, which is the compiler’s answer and not something a program can join – so what it carries is the operators the block below needs, exactly as sysl.math‘s Float carries the ones its two widths share. Mul joined this list for rendering, which is the one member here that is not about taking a number apart. Chunked decimal conversion multiplies in two places – computing the chunk divisor, and recovering a remainder as rest - q * chunk – and every integer type has Mul at every width, so nothing is excluded by asking for it and the compiler supplies the membership either way.

Iterate

trait Iterate
    type Item
    next(*self) -> Option[Self::Item]

What a for walks, when what it walks is not a range, an array or a slice.

The cursor is advanced through *self, so a loop iterates a copy of whatever it was given – which is why a cursor that means to report something after the walk (a reader’s failure latch) has to borrow what it reports on rather than own it.

next answers with an Option, so ending and yielding are one question with one answer, and there is no separate has_next for the two to disagree about.

The element is an associated type rather than a parameter, because a cursor walks one kind of thing and the choice is the cursor’s. That is what lets a signature be generic over what it walks – count_all[I: Iterate](it: I) names no element and needs none, where Iterate[E] would have asked for an E nothing in the call could say. An object still says which: *Iterate[string].

MemberSignatureDescription
nextnext(*self) -> Option[Self::Item]

Mul

trait Mul[Rhs = Self, Out = Self]
    mul(self, rhs: Rhs) -> Out
MemberSignatureDescription
mulmul(self, rhs: Rhs) -> Out

Neg

trait Neg
    neg(self) -> Self
MemberSignatureDescription
negneg(self) -> Self

Not

trait Not
    not(self) -> Self
MemberSignatureDescription
notnot(self) -> Self

One

trait One
    one() -> Self

The value x * one() == x holds for.

MemberSignatureDescription
oneone() -> Self

Ord

trait Ord
    lt(self, rhs: Self) -> bool
MemberSignatureDescription
ltlt(self, rhs: Self) -> bool

Rem

trait Rem[Rhs = Self, Out = Self]
    rem(self, rhs: Rhs) -> Out
MemberSignatureDescription
remrem(self, rhs: Rhs) -> Out

Shl

trait Shl[Rhs = Self, Out = Self]
    shl(self, rhs: Rhs) -> Out
MemberSignatureDescription
shlshl(self, rhs: Rhs) -> Out

Shr

trait Shr[Rhs = Self, Out = Self]
    shr(self, rhs: Rhs) -> Out
MemberSignatureDescription
shrshr(self, rhs: Rhs) -> Out

Sub

trait Sub[Rhs = Self, Out = Self]
    sub(self, rhs: Rhs) -> Out
MemberSignatureDescription
subsub(self, rhs: Rhs) -> Out

Walk

trait Walk
    type Walker
    walk(self) -> Self::Walker

What a for walks a container through: for x in c over a type implementing this is for x in c.walk(), the sequence asked for once before the first turn.

A container is deliberately not a cursor itself – a Buf‘s elements already sit in memory, and reading them by index costs an index where a cursor would cost a call apiece – so walk answers with whatever is cheapest to walk: a slice for storage that is contiguous, a cursor for storage that is not. What it answers is what the loop walks, element type and all, so for (k, v) in m over a Map takes apart the pairs its cursor yields.

The sequence is a snapshot in the sense its type gives it: a slice is the elements there when the loop began, so an element pushed during the walk is not visited.

MemberSignatureDescription
walkwalk(self) -> Self::Walker

Writer

trait Writer: Fallible
    write(*self, bytes: []const u8)

The sink every rendering goes through (library/core.md § Rendering to a sink).

It takes bytes rather than a string because that is the direction that is free – a string is a validated []u8 – and it reports failure by latching rather than by returning, so an implementation stays straight-line and print(x) stays a statement.

The latch itself is Fallible, in the file beside this one, and is required rather than declared here: reading asks the same question, and a type that does both may carry only one answer to it. A sink that cannot fail writes impl Fallible for itself and nothing inside the block: failed has a default and membership does not, so the opt-in is still written while the answer is not (reference/traits.md § Conformance is explicit, always). One that can fail – a bounded buffer, a device that goes away – says so once, and nothing about the latch changes.

MemberSignatureDescription
writewrite(*self, bytes: []const u8)The bytes are borrowed for the call and may not be kept*, which is what keeps rendering allocation-free: a renderer writes into a buffer on its own stack and passes a slice of it, and without the promise every such call would put that buffer on the heap.

Zero

trait Zero
    zero() -> Self

The value x + zero() == x holds for.

MemberSignatureDescription
zerozero() -> Self

Implementations

Debug for (..A)

impl[A: Debug] Debug for (..A)

A tuple shows each element through its own Debug, so (1, "a") keeps its quotes.

Debug for []T

impl[T: Debug] Debug for []T

A slice shows as its Display does, [1, 2, 3], with each element through its own Debug.

Debug for [N]T

impl[const N: usize, T: Debug] Debug for [N]T

Debug for bf16

impl Debug for bf16

Debug for bool

impl Debug for bool

Debug for char

impl Debug for char

A character is shown as its literal too, in single quotes.

Debug for f128

impl Debug for f128

Debug for f16

impl Debug for f16

Debug for f32

impl Debug for f32

Debug for Option[T]

impl[T: Debug] Debug for Option[T]

An option shows as its Display does – Some(3), None – with the payload through its own Debug, so Some("x") keeps its quotes.

Debug for real

impl Debug for real

Debug for Result[T, E]

impl[T: Debug, E: Debug] Debug for Result[T, E]

A result shows as its Display does – Ok(3), Err("x") – with the payload through its own Debug.

Debug for string

impl Debug for string

A string is shown as the literal that would write it: quoted, with every quote, backslash and control character escaped the way reference/lexical.md spells it. The quoting is what tells Name(s: "a, b") from two fields.

Debug for T

impl[T: Integer + Display] Debug for T

Debug for unit

impl Debug for unit

Display for (..A)

impl[A: Display] Display for (..A)

Rendered as one field: a specifier describes the field the whole value occupies (library/core.md § A specifier is the whole value's field), so %8s on a pair pads the pair and not its first element.

It is measured rather than gathered, which is what a compound rendering costs when it is done the obvious way: building the text first meant one string per element plus one per separator, all of them thrown away as soon as they were written. Running the same writes into a Counting sink answers the same question and stores nothing, and the sink is built only where a width was asked for – so an ordinary print of a tuple costs one pass and allocates nothing at all.

Display for []T

impl[T: Display] Display for []T

Display for [N]T

impl[const N: usize, T: Display] Display for [N]T

Every array of a printable element, at every length – one block, because a length is a value parameter (reference/generics.md § A parameter may stand for a value) and so an argument to the shape rather than part of it. Before that, one impl per length was the only way to write this and no library could write them all.

The whole-array view is the elements a slice would have walked, so Display for []T renders it and this one is a delegation rather than a second copy of the padding.

Display for bf16

impl Display for bf16

Display for bool

impl Display for bool

Display for char

impl Display for char

Display for f128

impl Display for f128

An f128 renders at its own width too: narrowing to a real would print a number the program does not hold.

Display for f16

impl Display for f16

Display for f32

impl Display for f32

Every float renders at its own width, in the fewest digits that read back as the same value of that type – what Rust’s {} writes – so f32(0.1) and f16(0.1) both print 0.1, the digits their own width needs, rather than the longer reading of the value widened to a real.

Display for Option[T]

impl[T: Display] Display for Option[T]

An option renders as it is written – Some(3) or None – which is what makes a failed assert_eq on one readable at all. Eq alone would not have been enough: assert_eq is bounded over Eq + Display, so a comparable option nothing could render would still have been refused.

The width is honoured by rendering twice into a counter first, exactly as a slice does: a field is the whole value, so the padding cannot be decided until the length of the whole is known, and measuring this way costs no allocation.

Display for real

impl Display for real

Display for Result[T, E]

impl[T: Display, E: Display] Display for Result[T, E]

A result renders as it is written – Ok(3) or Err(NotFound) – which is what makes a failed assert_eq on one readable. assert_eq is bounded over Eq + Display, so this is not a convenience beside Eq for Result[T, E]: without it a comparable result is still refused, and the refusal talks about Display rather than about the comparison.

The width is honoured by rendering twice into a counter first, exactly as a slice does: a field is the whole value, so the padding cannot be decided until the length of the whole is known, and measuring this way costs no allocation.

Display for string

impl Display for string

Display for T

impl[T: Integer] Display for T

Display for unit

impl Display for unit

Eq for (..A)

impl[A: Eq] Eq for (..A)

Eq for []T

impl[T: Eq] Eq for []T

Same length, same elements, same order – which is the equality a sequence has when its elements have one, and the reason it is written here rather than left to each caller.

sysl.slices.equal answers the same question and stays: it is a free function over []const T for a caller that has one, and the module it lives in is @no_alloc and reachable from a freestanding target. What the block adds is == itself, and with it every generic bounded by Eq – assert_eq above all, which is where the absence was found, since a bare bool says a comparison failed and neither of the two sequences that disagreed.

The length test comes first, so the common refusal costs nothing.

Eq for [N]T

impl[const N: usize, T: Eq] Eq for [N]T

Every array of an equatable element, at every length – the whole-array view is the elements a slice would have walked, so this is a delegation rather than a second copy of the loop, exactly as Display for [N]T delegates to Display for []T.

Eq for Option[T]

impl[T: Eq] Eq for Option[T]

Two options are equal when they are absent alike, or present with equal payloads – which is the only reading that keeps == agreeing with match. The bound is on the payload rather than on the whole, so an Option of something incomparable simply is not comparable, and says so at the comparison rather than at the declaration.

Eq for Result[T, E]

impl[T: Eq, E: Eq] Eq for Result[T, E]

Two results are equal when they took the same road and agree at the end of it – which is the only reading that keeps == agreeing with match. An Ok is never equal to an Err however the two payloads compare, and they need not even be comparable to each other: the bounds are on the two parameters separately, so a Result[int, IoError] compares exactly when both halves do.

Eq for unit

impl Eq for unit

Fallible for Counting

impl Fallible for Counting

Fallible for Stderr

impl Fallible for Stderr

Fallible for Stdout

impl Fallible for Stdout

FloatBits for bf16

impl FloatBits for bf16

FloatBits for f128

impl FloatBits for f128

FloatBits for f16

impl FloatBits for f16

FloatBits for f32

impl FloatBits for f32

FloatBits for real

impl FloatBits for real

Hash for (..A)

impl[A: Hash] Hash for (..A)

Mixed with the same FNV prime the string hash uses, so that the parts’ order matters – (1, 2) and (2, 1) are different keys, and a plain XOR would make them the same one. The fold starts at the FNV offset basis rather than at the first part’s hash, which is what lets one loop cover every arity rather than treating the first position as the seed.

Hash for [N]T

impl[const N: usize, T: Hash] Hash for [N]T

Mixed with the same FNV prime the tuple’s hash uses, so that position matters: [1, 2] and [2, 1] are different keys, where a plain XOR would make them one.

An array is hashable and a SLICE IS NOT, which is a decision rather than an omission. A hash is a promise about a key, and a slice is a view: putting one in a table leaves the storage behind it reachable and writable by whoever owns it, so the key can change under the table and the entry is then unfindable. An array is a value and copies, so it has no such owner to be surprised by. A caller holding a slice it means as a key hashes something it owns – a string, or an array built from the elements.

Hash for bool

impl Hash for bool

Hash for char

impl Hash for char

Hash for string

impl Hash for string

Hash for T

impl[T: Integer] Hash for T

The integers, which are the family that is open – i5 and u24 are types a program may name, so no finite list of blocks could cover them. One block over the whole family says it instead, exactly as Display‘s does.

The widening is the law, not an implementation detail. Two integer types compare equal across widths, so they have to hash equal too: 1 and 1 are the same number and reach the same mixer having become the same u64. Past 64 bits the value is mixed in two halves rather than truncated, because a 128-bit identifier is the reason to have the width at all and dropping its top half would bucket every one of them together.

Hash for unit

impl Hash for unit

Iterate for Range[T]

impl[T: Integer + Add + One] Iterate for Range[T]

Walking one, which is what makes a range value worth having.

Integer does not imply Add, which is why the bound reads as long as it does. The trait is declared Integer: Div + Rem + Ord + Eq + Sub + Mul — the list Display‘s blanket body happens to use — so a body bounded by it may divide an integer and may not add one. Add and One are named here for that reason rather than for anything about ranges.

The inclusive end is cleared rather than stepped past, which is the whole subtlety here. The obvious spelling advances lo after yielding and tests lo > hi; at 0..255u8 that advance wraps to zero and the walk starts again, because there is no u8 one greater than the last one. So on yielding the element equal to hi this clears inclusive and leaves lo where it is: the half-open test then stops the next call, and nothing is ever incremented past the end.

One for bf16

impl One for bf16

One for f16

impl One for f16

One for f32

impl One for f32

One for real

impl One for real

Ord for (..A)

impl[A: Ord] Ord for (..A)

Lexicographic, and written as a ladder rather than as < on each part in turn because deciding a position takes two comparisons: this one is less, or it is greater, or the two agree and the next position decides. Every position runs the same ladder and all-tied ends false – a tuple is not less than one it agrees with everywhere. Written per arity this needed a last position that ended with a bare <, having no next position to fall through to; the loop has no such case.

Ord for []T

impl[T: Ord] Ord for []T

Lexicographic, first element first, and a shorter sequence ordering before a longer one it is a prefix of – which is the rule a string already follows and the one a tuple’s Ord follows over its parts.

The ladder is the tuple’s and is written for the same reason: deciding a position takes two comparisons, since this one is less, or it is greater, or the two agree and the next position decides. All-tied then falls through to the lengths, so [1, 2] is less than [1, 2, 3] and two equal sequences are not less than each other.

Ord for [N]T

impl[const N: usize, T: Ord] Ord for [N]T

Every array of an orderable element, delegating to the slice exactly as Eq does. Two arrays of one type have one length, so the length test the slice ends on can never decide here.

Ord for unit

impl Ord for unit

Writer for Counting

impl Writer for Counting

Writer for Stderr

impl Writer for Stderr

Writer for Stdout

impl Writer for Stdout

Zero for bf16

impl Zero for bf16

Zero for f16

impl Zero for f16

Zero for f32

impl Zero for f32

Zero for real

impl Zero for real

Search

Esc
↑↓ to navigate ↵ to open Esc to close