Ruta graveolens  ·  notes from a language experiment  ·  cultivated since 2025

Appendix B: Conformance, Undefined Behavior, and Runtime Panics

This appendix is a consolidated reference for how Rue classifies program behavior. It restates the conformance taxonomy introduced in §1.3, catalogues the concrete conditions Rue traps as defined runtime panics, and enumerates the narrow set of operations that are genuinely undefined. It is normative only where a rule carries a normative category (see B.1:5); the catalogues themselves are informative summaries whose normative homes are the chapters they cross-reference.

Behavior Classification

This specification classifies the behavior of a program into four conformance categories — undefined, unspecified, implementation-defined, and erroneous — following C, C++, and Rust. The categories are introduced in §1.3; this appendix collects the concrete instances of each. How a behavior is assigned to a category is governed by a design preference (prefer the most-defined category; confine undefined behavior to unchecked operations that cannot be checked otherwise) recorded in ADR-0036, not by a normative rule of this document.

The four behavior categories and the obligations each places on a conforming implementation:

CategoryObligation on the implementation
UndefinedNone. A program that exhibits undefined behavior is invalid and the implementation may do anything. In Rue this arises only within unchecked code (see B.3).
UnspecifiedChoose from a permitted set of behaviors; no particular choice is required and none need be documented. Rue specifies most such choices (e.g. evaluation order §4.0, drop order §3.9), so this category has few instances today.
Implementation-definedChoose from a permitted set and document the choice. Examples: StrBuf growth/capacity (§3.7:41, documented in §3.10:25 — and observable, since capacity() (§3.10:11) returns the chosen value, so a program that branches on it is not portable), struct and array layout (§3.6), the width of usize/isize (§3.1), and the limits of Appendix C.
ErroneousWell-defined behavior that is nonetheless a program error a conforming implementation is encouraged to diagnose. Rue currently has no erroneous behavior — conditions other languages leave erroneous (e.g. integer overflow) Rue instead traps as a defined panic (see B.2).

The distinction between undefined and erroneous is central to Rue's character: an erroneous operation still has a defined result (and is merely diagnosable), whereas an undefined operation imposes no requirements at all. Rue's memory-safety guarantee is precisely that the safe subset of the language has no undefined behavior; every hazard reachable outside a checked block is either rejected at compile time or trapped at runtime (B.2). Undefined behavior is confined to the unchecked operations catalogued in B.3.

Separately from the behavior categories above, each normative paragraph in this specification carries a paragraph category (the cat= marker) that states what kind of requirement it is. These are defined in §1.3 and summarized here:

Paragraph categoryMeaningNormative?
normativeA general requirement on a conforming implementation.Yes
legality-ruleA compile-time requirement that must be enforced.Yes
syntaxA grammar rule defining valid program structure.Yes
dynamic-semanticsA runtime-behavior requirement.Yes
undefined-behaviorA condition whose behavior is undefined (imposes no requirement, but identifies the hazard).Yes
informativeExplanatory text.No
exampleA code example.No

The two axes are independent: a paragraph's cat= marker classifies the paragraph, while the four behavior categories classify a program's behavior. A dynamic-semantics paragraph may, for instance, define a trap (B.2) or state that an unchecked operation is undefined (B.3). Paragraphs without an explicit cat= marker are informative.

Runtime Panics

Rue detects certain error conditions at runtime and responds with a trap: a defined runtime panic that terminates the program with a specific exit code. These are defined behaviors, not undefined behavior — the program's response is fully specified. This is the mechanism by which Rue keeps conditions other languages leave undefined (overflow, out-of-bounds access, division by zero) inside the defined end of the taxonomy (B.1).

Integer Overflow

Signed or unsigned integer arithmetic that overflows the representable range MUST cause a runtime panic (§8.1).

Operations affected:

  • Addition (+)
  • Subtraction (-)
  • Multiplication (*)
  • Unary negation (-)
  • Division (/) and remainder (%), exactly when the dividend is the signed type's minimum value and the divisor is -1 (the quotient -MIN is not representable; the remainder operation overflows in the same case even though its mathematical result would be 0). This is distinct from division by zero (B.2:3).

Runtime behavior: Panic with exit code 101.

Division by Zero

Division or remainder with a divisor of zero MUST cause a runtime panic (§8.3).

Operations affected:

  • Division (/)
  • Remainder (%)

Runtime behavior: Panic with exit code 101.

Array Bounds Violation

Accessing an array element with an index outside the valid range [0, length) MUST cause a runtime panic (§8.2).

Operations affected:

  • Array indexing (arr[i])
  • Array element assignment (arr[i] = v)

Runtime behavior: Panic with exit code 101.

Exit Codes

ConditionExit Code
Integer overflow (incl. signed MIN / -1)101
Division by zero101
Array out of bounds101

All runtime panics produce exit code 101, matching Rust's convention for unwinding panics.

Undefined Behavior

Rue has undefined behavior only within unchecked code — the raw-pointer and heap intrinsics of Chapter 9, which may appear only inside a checked block (§9.1). No operation reachable from the safe subset is undefined; safe hazards are rejected at compile time or trapped (B.2). The design preference confining undefined behavior to unchecked operations that cannot be checked without changing a value's representation is recorded in ADR-0036.

Inside a checked block, the following operations have undefined behavior. The compiler does not verify these conditions; the programmer is responsible for upholding them (§9, ADR-0028). Each entry cites the normative rule that defines it.

Undefined operationNormative rule
Reading (@ptr_read) or writing (@ptr_write) through a pointer that does not address valid, live storage for the pointee type — including a null pointer, a misaligned address, or memory that was never allocated.§9.2 (9.2:6b), §9.1, ADR-0028
Reading (@ptr_read_unaligned) or writing (@ptr_write_unaligned) through a pointer that does not address valid, live storage for the pointee type. Only the alignment obligation is lifted for this pair; every other requirement of the aligned pair still applies.§9.2 (9.2:14k)
Offsetting a pointer (@ptr_offset) outside the bounds of the allocation it addresses (a one-past-the-end pointer may be formed but not dereferenced).§9.2 (9.2:7)
Using a pointer after the block it addressed has been released with @free (use-after-free), or freeing a block that was not returned by @alloc/@alloc_zeroed/@realloc, or freeing one twice.§9.2 (9.2:11)
Accessing storage through a pointer produced by @int_to_ptr from an address that does not name valid, correctly typed, correctly aligned storage.§9.2 (9.2:6c), ADR-0028
Using a pointer obtained from @raw/@raw_mut/@field_ptr after the value it borrowed has been moved, dropped, or otherwise gone out of scope (a dangling pointer).ADR-0028
Mutating storage through a ptr mut T while another live pointer aliases the same storage in a way the program's reasoning assumes cannot happen (aliasing violation).ADR-0028
Accessing storage through a pointer that does not satisfy the pointee type's alignment requirement, other than through @ptr_read_unaligned/@ptr_write_unaligned, for which an underaligned address is well defined.§9.2 (9.2:6b, 9.2:14k), ADR-0028
Copying with @byte_copy between regions that overlap: [dst, dst + size) and [src, src + size) must be disjoint. @byte_move is the well-defined form for overlapping regions.§9.2 (9.2:14g)
Reading or writing outside live storage with @byte_copy, @byte_move, or @byte_set — that is, when size reaches past the end of the block either operand addresses. A size of zero accesses no memory and is always defined.§9.2 (9.2:14g)
Passing an incorrect size or alignment, a pointer not returned by the allocation family, or an already-freed pointer to @free/@realloc/@resize.§9.2 (9.2:11–13)
Passing an align that is zero or not a power of two to the allocation family when the value is not a compile-time constant (a constant one is rejected at compile time instead).§9.2 (9.2:13a)

These are the memory-safety obligations a checked block places on the programmer (ADR-0028): do not dereference null or dangling pointers, do not create aliasing violations, respect alignment, keep pointed-to memory valid for its type, and do not let @raw/@raw_mut pointers outlive their source. Outside checked blocks, all of Rue's safety guarantees hold and none of the above is reachable.

Undefined behavior is not the same as a trap. A trapped condition (B.2) has a defined outcome — a panic with exit code 101 — and can be relied upon; an undefined operation imposes no requirements and may produce any result, including silent corruption. This is why Rue traps every hazard it can check cheaply and reserves undefined behavior for the unchecked operations where a check would require changing a value's representation (ADR-0036).