Commit f0b331e95a

mlugg <mlugg@mlugg.co.uk>
2025-02-01 17:47:26
langref: embrace the term "illegal behavior"
Also standardise the terms "safety-checked" and "unchecked".
1 parent def7e2f
doc/langref/test_undefined_behavior.zig → doc/langref/test_illegal_behavior.zig
File renamed without changes
doc/langref/test_setRuntimeSafety_builtin.zig
@@ -2,7 +2,7 @@ test "@setRuntimeSafety" {
     // The builtin applies to the scope that it is called in. So here, integer overflow
     // will not be caught in ReleaseFast and ReleaseSmall modes:
     // var x: u8 = 255;
-    // x += 1; // undefined behavior in ReleaseFast/ReleaseSmall modes.
+    // x += 1; // Unchecked Illegal Behavior in ReleaseFast/ReleaseSmall modes.
     {
         // However this block has safety enabled, so safety checks happen here,
         // even in ReleaseFast and ReleaseSmall modes.
@@ -15,7 +15,7 @@ test "@setRuntimeSafety" {
             // would not be caught in any build mode.
             @setRuntimeSafety(false);
             // var x: u8 = 255;
-            // x += 1; // undefined behavior in all build modes.
+            // x += 1; // Unchecked Illegal Behavior in all build modes.
         }
     }
 }
doc/langref.html.in
@@ -1049,12 +1049,12 @@
       {#header_close#}
       {#header_open|Runtime Integer Values#}
       <p>
-      Integer literals have no size limitation, and if any undefined behavior occurs,
+      Integer literals have no size limitation, and if any Illegal Behavior occurs,
       the compiler catches it.
       </p>
       <p>
       However, once an integer value is no longer known at compile-time, it must have a
-      known size, and is vulnerable to undefined behavior.
+      known size, and is vulnerable to safety-checked {#link|Illegal Behavior#}.
       </p>
       {#code|runtime_vs_comptime.zig#}
 
@@ -1064,7 +1064,7 @@
       {#link|Division by Zero#}.
       </p>
       <p>
-      Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause undefined behavior on
+      Operators such as {#syntax#}+{#endsyntax#} and {#syntax#}-{#endsyntax#} cause {#link|Illegal Behavior#} on
       integer overflow. Alternative operators are provided for wrapping and saturating arithmetic on all targets.
       {#syntax#}+%{#endsyntax#} and {#syntax#}-%{#endsyntax#} perform wrapping arithmetic
       while {#syntax#}+|{#endsyntax#} and {#syntax#}-|{#endsyntax#} perform saturating arithmetic.
@@ -2029,7 +2029,7 @@ or
       </p>
       <p>
         Slices have bounds checking and are therefore protected
-        against this kind of undefined behavior. This is one reason
+        against this kind of Illegal Behavior. This is one reason
         we prefer slices to pointers.
       </p>
       {#code|test_slice_bounds.zig#}
@@ -2048,7 +2048,7 @@ or
 
       <p>
       {#link|@ptrCast#} converts a pointer's element type to another. This
-      creates a new pointer that can cause undetectable illegal behavior
+      creates a new pointer that can cause undetectable Illegal Behavior
       depending on the loads and stores that pass through it. Generally, other
       kinds of type conversions are preferable to
       {#syntax#}@ptrCast{#endsyntax#} if possible.
@@ -2164,7 +2164,7 @@ or
 
       <p>
       Sentinel-terminated slicing asserts that the element in the sentinel position of the backing data is
-      actually the sentinel value. If this is not the case, safety-protected {#link|Undefined Behavior#} results.
+      actually the sentinel value. If this is not the case, safety-checked {#link|Illegal Behavior#} results.
       </p>
       {#code|test_sentinel_mismatch.zig#}
 
@@ -2425,7 +2425,7 @@ or
       or use an {#link|extern union#} or a {#link|packed union#} which have
       guaranteed in-memory layout.
       {#link|Accessing the non-active field|Wrong Union Field Access#} is
-      safety-checked {#link|Undefined Behavior#}:
+      safety-checked {#link|Illegal Behavior#}:
       </p>
       {#code|test_wrong_union_access.zig#}
 
@@ -3023,11 +3023,11 @@ or
       {#syntax#}const number = parseU64("1234", 10) catch unreachable;{#endsyntax#}
       <p>
       Here we know for sure that "1234" will parse successfully. So we put the
-      {#syntax#}unreachable{#endsyntax#} value on the right hand side. {#syntax#}unreachable{#endsyntax#} generates
-      a panic in {#link|Debug#} and {#link|ReleaseSafe#} modes and undefined behavior in
-      {#link|ReleaseFast#} and {#link|ReleaseSmall#} modes. So, while we're debugging the
-      application, if there <em>was</em> a surprise error here, the application would crash
-      appropriately.
+      {#syntax#}unreachable{#endsyntax#} value on the right hand side.
+      {#syntax#}unreachable{#endsyntax#} invokes safety-checked {#link|Illegal Behavior#}, so
+      in {#link|Debug#} and {#link|ReleaseSafe#}, triggers a safety panic by default. So, while
+      we're debugging the application, if there <em>was</em> a surprise error here, the application
+      would crash appropriately.
       </p>
       <p>
       You may want to take a different action for every situation. For that, we combine
@@ -4034,7 +4034,7 @@ fn performFn(start_value: i32) i32 {
       </p>
       <p>
       Luckily, we used an unsigned integer, and so when we tried to subtract 1 from 0, it triggered
-      undefined behavior, which is always a compile error if the compiler knows it happened.
+      {#link|Illegal Behavior#}, which is always a compile error if the compiler knows it happened.
       But what would have happened if we used a signed integer?
       </p>
       {#code|fibonacci_comptime_infinite_recursion.zig#}
@@ -4239,7 +4239,7 @@ pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
       </p>
       <p>
       Failure to declare the full set of clobbers for a given inline assembly
-      expression is unchecked {#link|Undefined Behavior#}.
+      expression is unchecked {#link|Illegal Behavior#}.
       </p>
       {#header_close#}
 
@@ -4805,7 +4805,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       Attempting to convert an integer with no corresponding value in the enum invokes
-      safety-checked {#link|Undefined Behavior#}.
+      safety-checked {#link|Illegal Behavior#}.
       Note that a {#link|non-exhaustive enum|Non-exhaustive enum#} has corresponding values for all
       integers in the enum's integer tag type: the {#syntax#}_{#endsyntax#} value represents all
       the remaining unnamed integers in the enum's tag type.
@@ -4824,7 +4824,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       Attempting to convert an integer that does not correspond to any error results in
-      safety-protected {#link|Undefined Behavior#}.
+      safety-checked {#link|Illegal Behavior#}.
       </p>
       {#see_also|@intFromError#}
       {#header_close#}
@@ -4856,7 +4856,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       <p>
       Converts an error set or error union value from one error set to another error set. The return type is the
 			inferred result type. Attempting to convert an error which is not in the destination error
-			set results in safety-protected {#link|Undefined Behavior#}.
+			set results in safety-checked {#link|Illegal Behavior#}.
       </p>
       {#header_close#}
 
@@ -4912,7 +4912,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       If {#syntax#}field_ptr{#endsyntax#} does not point to the {#syntax#}field_name{#endsyntax#} field of an instance of
-      the result type, and the result type has ill-defined layout, invokes unchecked {#link|Undefined Behavior#}.
+      the result type, and the result type has ill-defined layout, invokes unchecked {#link|Illegal Behavior#}.
       </p>
       {#header_close#}
 
@@ -5029,7 +5029,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       Converts an integer to another integer while keeping the same numerical value.
 			The return type is the inferred result type.
       Attempting to convert a number which is out of range of the destination type results in
-      safety-protected {#link|Undefined Behavior#}.
+      safety-checked {#link|Illegal Behavior#}.
       </p>
       {#code|test_intCast_builtin.zig#}
 
@@ -5090,7 +5090,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       If the integer part of the floating point number cannot fit in the destination type,
-      it invokes safety-checked {#link|Undefined Behavior#}.
+      it invokes safety-checked {#link|Illegal Behavior#}.
       </p>
       {#see_also|@floatFromInt#}
       {#header_close#}
@@ -5250,7 +5250,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       <p>
       The {#syntax#}ptr{#endsyntax#} argument may be any pointer type and determines the memory
       address to prefetch. This function does not dereference the pointer, it is perfectly legal
-      to pass a pointer to invalid memory to this function and no illegal behavior will result.
+      to pass a pointer to invalid memory to this function and no Illegal Behavior will result.
       </p>
       <p>{#syntax#}PrefetchOptions{#endsyntax#} can be found with {#syntax#}@import("std").builtin.PrefetchOptions{#endsyntax#}.</p>
       {#header_close#}
@@ -5262,7 +5262,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       {#link|Optional Pointers#} are allowed. Casting an optional pointer which is {#link|null#}
-      to a non-optional pointer invokes safety-checked {#link|Undefined Behavior#}.
+      to a non-optional pointer invokes safety-checked {#link|Illegal Behavior#}.
       </p>
       <p>
       {#syntax#}@ptrCast{#endsyntax#} cannot be used for:
@@ -5286,7 +5286,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       If the destination pointer type does not allow address zero and {#syntax#}address{#endsyntax#}
-      is zero, this invokes safety-checked {#link|Undefined Behavior#}.
+      is zero, this invokes safety-checked {#link|Illegal Behavior#}.
       </p>
       {#header_close#}
 
@@ -5361,8 +5361,8 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
         <li>
             {#syntax#}Optimized{#endsyntax#} - Floating point operations may do all of the following:
           <ul>
-            <li>Assume the arguments and result are not NaN. Optimizations are required to retain defined behavior over NaNs, but the value of the result is undefined.</li>
-            <li>Assume the arguments and result are not +/-Inf. Optimizations are required to retain defined behavior over +/-Inf, but the value of the result is undefined.</li>
+            <li>Assume the arguments and result are not NaN. Optimizations are required to retain legal behavior over NaNs, but the value of the result is undefined.</li>
+            <li>Assume the arguments and result are not +/-Inf. Optimizations are required to retain legal behavior over +/-Inf, but the value of the result is undefined.</li>
             <li>Treat the sign of a zero argument or result as insignificant.</li>
             <li>Use the reciprocal of an argument rather than perform division.</li>
             <li>Perform floating-point contraction (e.g. fusing a multiply followed by an addition into a fused multiply-add).</li>
@@ -5401,7 +5401,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(@typeInfo(T).int.bits){#endsyntax#} bits.
-              This is because {#syntax#}shift_amt >= @typeInfo(T).int.bits{#endsyntax#} is undefined behavior.
+      This is because {#syntax#}shift_amt >= @typeInfo(T).int.bits{#endsyntax#} triggers safety-checked {#link|Illegal Behavior#}.
       </p>
       <p>
       {#syntax#}comptime_int{#endsyntax#} is modeled as an integer with an infinite number of bits,
@@ -5418,7 +5418,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(@typeInfo(@TypeOf(a)).int.bits){#endsyntax#} bits.
-              This is because {#syntax#}shift_amt >= @typeInfo(@TypeOf(a)).int.bits{#endsyntax#} is undefined behavior.
+      This is because {#syntax#}shift_amt >= @typeInfo(@TypeOf(a)).int.bits{#endsyntax#} triggers safety-checked {#link|Illegal Behavior#}.
       </p>
       {#see_also|@shlExact|@shrExact#}
       {#header_close#}
@@ -5431,7 +5431,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </p>
       <p>
       The type of {#syntax#}shift_amt{#endsyntax#} is an unsigned integer with {#syntax#}log2(@typeInfo(T).int.bits){#endsyntax#} bits.
-              This is because {#syntax#}shift_amt >= @typeInfo(T).int.bits{#endsyntax#} is undefined behavior.
+      This is because {#syntax#}shift_amt >= @typeInfo(T).int.bits{#endsyntax#} triggers safety-checked {#link|Illegal Behavior#}.
       </p>
       {#see_also|@shlExact|@shlWithOverflow#}
       {#header_close#}
@@ -5706,7 +5706,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       {#header_open|@tagName#}
       <pre>{#syntax#}@tagName(value: anytype) [:0]const u8{#endsyntax#}</pre>
       <p>
-      Converts an enum value or union value to a string literal representing the name.</p><p>If the enum is non-exhaustive and the tag value does not map to a name, it invokes safety-checked {#link|Undefined Behavior#}.
+      Converts an enum value or union value to a string literal representing the name.</p><p>If the enum is non-exhaustive and the tag value does not map to a name, it invokes safety-checked {#link|Illegal Behavior#}.
       </p>
       {#header_close#}
 
@@ -5943,7 +5943,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
         <li>Reproducible build</li>
       </ul>
       {#header_close#}
-      {#see_also|Compile Variables|Zig Build System|Undefined Behavior#}
+      {#see_also|Compile Variables|Zig Build System|Illegal Behavior#}
       {#header_close#}
 
       {#header_open|Single Threaded Builds#}
@@ -5958,20 +5958,36 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       </ul>
       {#header_close#}
 
-      {#header_open|Undefined Behavior#}
+      {#header_open|Illegal Behavior#}
       <p>
-      Zig has many instances of undefined behavior. If undefined behavior is
-      detected at compile-time, Zig emits a compile error and refuses to continue.
-      Most undefined behavior that cannot be detected at compile-time can be detected
-      at runtime. In these cases, Zig has safety checks. Safety checks can be disabled
-      on a per-block basis with {#link|@setRuntimeSafety#}. The {#link|ReleaseFast#}
-      and {#link|ReleaseSmall#} build modes disable all safety checks (except where overridden
-      by {#link|@setRuntimeSafety#}) in order to facilitate optimizations.
+      Many operations in Zig trigger what is known as "Illegal Behavior" (IB). If Illegal Behavior is detected at
+      compile-time, Zig emits a compile error and refuses to continue. Otherwise, when Illegal Behavior is not caught
+      at compile-time, it falls into one of two categories.
       </p>
       <p>
-      When a safety check fails, Zig crashes with a stack trace, like this:
+      Some Illegal Behavior is <em>safety-checked</em>: this means that the compiler will insert "safety checks"
+      anywhere that the Illegal Behavior may occur at runtime, to determine whether it is about to happen. If it
+      is, the safety check "fails", which triggers a panic.
       </p>
-      {#code|test_undefined_behavior.zig#}
+      <p>
+      All other Illegal Behavior is <em>unchecked</em>, meaning the compiler is unable to insert safety checks for
+      it. If Unchecked Illegal Behavior is invoked at runtime, anything can happen: usually that will be some kind of
+      crash, but the optimizer is free to make Unchecked Illegal Behavior do anything, such as calling arbitrary functions
+      or clobbering arbitrary data. This is similar to the concept of "undefined behavior" in some other languages. Note that
+      Unchecked Illegal Behavior still always results in a compile error if evaluated at {#link|comptime#}, because the Zig
+      compiler is able to perform more sophisticated checks at compile-time than at runtime.
+      </p>
+      <p>
+      Most Illegal Behavior is safety-checked. However, to facilitate optimizations, safety checks are disabled by default
+      in the {#link|ReleaseFast#} and {#link|ReleaseSmall#} optimization modes. Safety checks can also be enabled or disabled
+      on a per-block basis, overriding the default for the current optimization mode, using {#link|@setRuntimeSafety#}. When
+      safety checks are disabled, Safety-Checked Illegal Behavior behaves like Unchecked Illegal Behavior; that is, any behavior
+      may result from invoking it.
+      </p>
+      <p>
+      When a safety check fails, Zig's default panic handler crashes with a stack trace, like this:
+      </p>
+      {#code|test_illegal_behavior.zig#}
 
       {#header_open|Reaching Unreachable Code#}
       <p>At compile-time:</p>
@@ -6337,7 +6353,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       <p>
       {#syntax#}var{#endsyntax#} declarations inside functions are stored in the function's stack frame. Once a function returns,
       any {#link|Pointers#} to variables in the function's stack frame become invalid references, and
-      dereferencing them becomes unchecked {#link|Undefined Behavior#}.
+      dereferencing them becomes unchecked {#link|Illegal Behavior#}.
       </p>
       <p>
       {#syntax#}var{#endsyntax#} declarations at the top level or in {#link|struct#} declarations are stored in the global
@@ -6445,7 +6461,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
       The API documentation for functions and data structures should take great care to explain
       the ownership and lifetime semantics of pointers. Ownership determines whose responsibility it
       is to free the memory referenced by the pointer, and lifetime determines the point at which
-      the memory becomes inaccessible (lest {#link|Undefined Behavior#} occur).
+      the memory becomes inaccessible (lest {#link|Illegal Behavior#} occur).
       </p>
       {#header_close#}
 
@@ -6733,10 +6749,10 @@ int foo(void) {
         <li>Supports all the syntax of the other two pointer types ({#syntax#}*T{#endsyntax#}) and ({#syntax#}[*]T{#endsyntax#}).</li>
         <li>Coerces to other pointer types, as well as {#link|Optional Pointers#}.
             When a C pointer is coerced to a non-optional pointer, safety-checked
-            {#link|Undefined Behavior#} occurs if the address is 0.
+            {#link|Illegal Behavior#} occurs if the address is 0.
         </li>
         <li>Allows address 0. On non-freestanding targets, dereferencing address 0 is safety-checked
-            {#link|Undefined Behavior#}. Optional C pointers introduce another bit to keep track of
+            {#link|Illegal Behavior#}. Optional C pointers introduce another bit to keep track of
             null, just like {#syntax#}?usize{#endsyntax#}. Note that creating an optional C pointer
             is unnecessary as one can use normal {#link|Optional Pointers#}.
         </li>
@@ -7051,8 +7067,8 @@ fn readU32Be() u32 {}
       <ul>
         <li>Omit any information that is redundant based on the name of the thing being documented.</li>
         <li>Duplicating information onto multiple similar functions is encouraged because it helps IDEs and other tools provide better help text.</li>
-        <li>Use the word <strong>assume</strong> to indicate invariants that cause {#link|Undefined Behavior#} when violated.</li>
-        <li>Use the word <strong>assert</strong> to indicate invariants that cause <em>safety-checked</em> {#link|Undefined Behavior#} when violated.</li>
+        <li>Use the word <strong>assume</strong> to indicate invariants that cause <em>unchecked</em> {#link|Illegal Behavior#} when violated.</li>
+        <li>Use the word <strong>assert</strong> to indicate invariants that cause <em>safety-checked</em> {#link|Illegal Behavior#} when violated.</li>
       </ul>
       {#header_close#}
       {#header_close#}
@@ -7448,8 +7464,8 @@ fn readU32Be() u32 {}
             In particular, inside a {#syntax#}nosuspend{#endsyntax#} scope:
             <ul>
               <li>Using the {#syntax#}suspend{#endsyntax#} keyword results in a compile error.</li>
-              <li>Using {#syntax#}await{#endsyntax#} on a function frame which hasn't completed yet results in safety-checked {#link|Undefined Behavior#}.</li>
-              <li>Calling an async function may result in safety-checked {#link|Undefined Behavior#}, because it's equivalent to <code>await async some_async_fn()</code>, which contains an {#syntax#}await{#endsyntax#}.</li>
+              <li>Using {#syntax#}await{#endsyntax#} on a function frame which hasn't completed yet results in safety-checked {#link|Illegal Behavior#}.</li>
+              <li>Calling an async function may result in safety-checked {#link|Illegal Behavior#}, because it's equivalent to <code>await async some_async_fn()</code>, which contains an {#syntax#}await{#endsyntax#}.</li>
             </ul>
             Code inside a {#syntax#}nosuspend{#endsyntax#} scope does not cause the enclosing function to become an {#link|async function|Async Functions#}.
             <ul>