Furth FortuneEngineering reference
TECHNICAL DOCUMENTATION

2027 · MerTek technology

On this page
Language reference · full comparison

Trident Language

Explore types, control flow, memory, data access, and host boundaries. Select languages to compare their rules and code examples.

Hover any dotted word for a plain-English definition. The comparison cards explain language behavior; they are not a speed ranking.

Compare against
Presets

Each example's left rule and file pill carry its grade against the Trident card beside it: Trident's own answer , the language holds the guarantee that section is about , it carries the construct but gives that guarantee up , it is reachable only through a package , and it is absent . A color on this page is a verdict, never a language's identity.

Source forms that now work

These excerpts come from current language cases. They show the source scope; stored columns and wider runtime integration have separate owners.

Trident: an outline keeps extra fields.tri
outline person { text name = "" }
outline employee { text name = "", i32 level = 0 }
private person identity(person value) { return value }
text main(text[] argv) {
    employee worker = { name = "ada", level = 7 }
    person result = identity(worker)
    i32 level = result.level
    return t.text.of(level)
}

Source case

Trident: a named almanac member.tri
almanac shipping {
    text label = ""
    f64 cost = 0
    shipping standard = { id = 0, label = "Standard", cost = 5 }
    shipping express = { id = 1, label = "Express", cost = 15 }
}
const shipping preferred = shipping.express
f64 price(shipping S) {
    return S.cost
}

Source case with complete property coverage

Named types keep semantic identity

A named type gives one admitted base type a distinct semantic domain. Its base cannot be a collection or a record. Its declaration, module reach, pinned origin, and source round trip work. Values of that type still refuse until semantic value admission lands. This is partial support, not a working value constructor.

Declarations work; values are a separate boundary.tri
type Email = text
type Account<origin="pub.contract.Account"> = u64

text main(text[] argv) {
    return "declarations work"
}
# Email e = "ada@example.com" still refuses.
# Email("ada@example.com") remains pending.

Declaration case; current value refusal.

Without an origin pin, the module path and declaration name determine identity. A rename changes that identity. An explicit origin pin preserves it across a move. A text alias in another language need not create such an identity.

Named type law; remaining value admission.

Language rules at a glance

This compact table compares the stated language rules. It gives no speed ranking. The language controls below select the detailed cards and full matrices.

RuleTridentPythonJavaScriptTypeScriptRustGoCutlass
Type of an ordinary localComplete type requiredRuntime value typeRuntime value typeAnnotation or static inference; types eraseAnnotation or static inferenceAnnotation or static inferenceComplete type required
Integer arithmeticFixed widths fault on overflow; varint uses bounded memoryArbitrary precision intNumber uses binary64; BigInt is separateUses JavaScript numeric runtime rulesFixed widths; checks depend on settings; explicit checked/wrapping methodsFixed widths; defined overflow without panicCurrent native widths fault; explicit wrap/saturate
A plain integer as a conditionRefused; bool requiredTruth-value conversionTruth-value conversionTruth-value tests and type narrowingRefused; bool requiredRefused; bool requiredRefused; bool required
Record field setPlain Chest can add fields; outline requires known structure and keeps extrasA dictionary can add keysAn object can add propertiesDeclared shapes checked statically; runtime is JavaScriptStruct fields fixed at compileStruct fields fixed at compileFixed-storage structs work.

Sources: Trident contract; Python built-in types; ECMAScript values; TypeScript types and narrowing; Rust operators; Go specification; Cutlass bootstrap. Other detailed cards retain earlier examples and are not a fresh all-language compiler run.

Comparison scope and reference editions

The fixed summaries below remain visible for reference. Language buttons filter the detailed language cards and full matrices. These are rule comparisons, not measurements of speed or proof that every example ran on every platform.

ReferenceScope used here
TridentThe source revision in the page header and each linked owning contract. Source, catalog, Host integration, and installed builds have separate boundaries.
PythonPython 3.15 typing. NewType creates a static distinction; the runtime call returns its argument.
TypeScript and JavaScriptTypeScript Handbook aliases and ECMAScript specification. TypeScript types erase. A JavaScript engine or polyfill must supply APIs such as Temporal; a type declaration alone supplies no runtime API.
Rust and GoRust Reference structs and Go specification type definitions. Rust integer overflow settings remain explicit in the earlier summary.
LuaLua 5.4, standard numeric build: 64-bit integer and binary64 float subtypes. Other builds can select narrower widths. Luau has its own numeric rules.
DartDart platform rules: native int uses signed 64-bit values; web int uses JavaScript numbers with a 53-bit exact integer range.
Other detailed cardsLanguage or library versions named in each card apply to that card. Cards with no edition are illustrative and do not establish a version-specific guarantee. This review does not certify foreign compilers.
FeatureTridentPythonTypeScriptRustGo
A distinct value identitytype Name = Base declares a semantic leaf.NewType supplies a checker distinction; no new runtime representation.A type alias adds no identity. A branded structural type is a separate pattern.A tuple struct creates a nominal type and a constructor.A type definition creates a distinct defined type; an alias does not.
Conditional resultif / when arms explicitly use a value at the declared destination type.x if condition else y; match is a statement.The conditional operator chooses a value. if / switch are statements.if / match are expressions; a block's final expression can supply its value.if / switch are statements. Use assignment or a function result.
Open versus immutable recordAn outline preserves extras. An ingot requires exact fields and refuses writes through its value.TypedDict describes dictionary keys to a checker; frozen dataclasses provide a different record form.Object shapes are structural. readonly checks writes statically; it does not freeze an object at runtime.A struct is nominal; field mutation follows ownership and mutability.A struct has declared fields and no general immutable-record declaration.
Type chosen at a native callSelected signatures read complete argument or destination types; runtime admission still checks supplied data.Generic annotations guide a checker; runtime validation is separate.Generic signatures can infer types from context; runtime validation is separate.Generic calls can use context to determine concrete type parameters.Generic functions infer type arguments under Go's inference rules; a type argument can be explicit.

Additional rules: Python conditional expressions; TypeScript readonly; Rust block results; Go inference. No speed ranking follows from these rows.

01

Comments & source text

Trident source is strict UTF-8. A line comment starts with #. A structured comment uses #{... } and holds a Draft document body.

// builds an exact rational; it is never a comment. The compiler reads structured annotations but does not execute them. Diagnostics display lines and byte columns from one.

Trident.tri
#!/usr/bin/env tricli
# an ordinary comment: no loader, no version marker
#{ text note = "a structured comment"
   text span = "spans lines as fields" }
# // is the rational constructor, never a
# comment: 1//2 is the rational one half
text main(text[] argv) {
    return "ok"
}
C.c
// a line comment (C99+)
/* a block comment —
   the only form before C99 */
C#.cs
// a line comment
/* a block comment */
/// an XML doc comment, read by tooling
C++.cpp
// a line comment
/* a block comment */
/// a Doxygen-style doc comment
Cutlass.cut
# target: x64-linux, x64-windows an ordinary comment; only the corpus driver reads the first line,
# as the targets a test program compiles for
#{ text note = "a structured comment"
   text span = "spans lines as fields" }
# // is the rational constructor, never
# a comment: 1//2 is the rational half a # glued to a name opens nothing: width#bytes refuses at lex
# #! would lex as a comment too, but a
# .cut compiles to machine code, and no
# runner reads it as a script
export void main() {
}
Dart.dart
#!/usr/bin/env dart
// an ordinary comment; Dart treats #! specially only as line one (a "script tag")
/* a block comment
   spans lines */
/// a doc comment (markdown, read by dartdoc)
void main() => print("ok");
Elixir.ex
#!/usr/bin/env elixir
# an ordinary comment — no loader, no version marker elixir has exactly one form:
# # runs to the next line feed

defmodule Greeter do
  @moduledoc """
  a doc string is a real
  attribute (@moduledoc), not
  just a comment — ExDoc
  and h() read it at runtime
  """
  def hello, do: "ok"
end
Go.go
// a line comment
/* a block comment */

// Doc comments are plain line comments above a decl.
Haskell.hs
#!/usr/bin/env runghc
-- a line comment, runs to EOL
{- a block comment,
   spans lines -}
-- | a Haddock comment,
-- read by tooling, above the binding it documents
main :: IO ()
main = putStrLn "ok"
Jai.jai
// a line comment
/* a block comment,
   /* and these nest */ still open */
// there is no doc-comment form, and
// # is not a comment at all: it opens
// a COMPILER DIRECTIVE — #import,
// #run, #if, #assert, #foreign
main :: () {
}
Java.java
// a line comment
/* a block comment */
/** a Javadoc comment,
 *  read by tooling */
class Main { }
JavaScript.js
// a line comment
/* a block comment
   spans lines */
function main() {
  return "ok";
}
Julia.jl
#!/usr/bin/env julia
# an ordinary comment; the shebang is just a # line
#= a block comment,
   #= nestable =# still open =#
"""
a docstring right above a
function is Julia's doc
comment, read by `?main`
"""
function main(argv)
    return "ok"
end
# no // form exists at all
Kotlin.kt
// a line comment
/* a block comment
   spans lines */
/** a KDoc comment,
 *  read by tooling */
fun main() {
    println("ok")
}
Lua.lua
-- a line comment
--[[ a block comment,
     spans lines ]]
Luau.luau
--!strict
-- a line comment
--[[ a block comment,
     --[==[ nested with = padding ]==]
]]
-- the first line is not a shebang:
-- --!strict is a MODE directive the
-- type checker reads, and only the leading lines of a file carry one
Nim.nim
#!/usr/bin/env -S nim r
# an ordinary comment, as in Trident; no // form either
#[ a nestable block comment,
   can span lines ]#
## a doc comment, bound to
## the proc below it
proc main(): string = "ok"
Objective-C.m
// a line comment (C-derived)
/* a block comment */
/// a Doc Comment (light, Xcode Quick Help)
// unlike Trident, a leading
// # is preprocessor-only,
// never a comment
Perl.pl
#!/usr/bin/env perl
# an ordinary comment - the shebang line above is also just a comment to perl
use strict;
use warnings;

=pod
a POD block comment,
spans lines
=cut

sub main { return "ok"; }
PHP.php
#!/usr/bin/env php
<?php
# a line comment (# form)
// this form also runs to the next line feed
/* a block comment,
   spans lines */
/** a PHPDoc comment,
 *  read by tooling */
function main(array $argv): string {
    return "ok";
}
Python.py
#!/usr/bin/env python3
# a line comment
"""a triple-quoted string,
often used as a comment"""
def main():
    return "ok"
Ruby.rb
#!/usr/bin/env ruby
# an ordinary comment — runs to end of line; no loader, no version marker
=begin
a block comment: =begin/=end
must each start a line alone;
no // form exists at all
=end
def main
  "ok"
end
Rust.rs
// a line comment
/* a block comment */
/// a doc comment (markdown,
/// read by rustdoc)
Scala.scala
// a line comment
/* a block comment */
/** a Scaladoc comment,
 *  read by tooling */
object Main
Swift.swift
#!/usr/bin/env swift
// a line comment
/* a block comment;
   /* nesting */ allowed */
/// a doc comment (DocC)
// no `#` comment form; `#!` is only special as line one of a script file
TypeScript.ts
#!/usr/bin/env -S npx tsx
// a line comment
/* a block comment
   spans lines */
/** a JSDoc comment,
 *  read by tooling */
function main(): string {
  return "ok";
}
// no bare `#` comment form; only a leading `#!` line is special-cased by the runner, same carve-out
Verse.verse
# a line comment — Verse spells the line comment with # too
<# a block comment,
   <# and these nest #> still open #>
# no doc-comment form. Indentation is significant here, so a comment's column is part of the parse
# rather than trivia the lexer discards
Zig.zig
//! a top-level doc comment for this file — must appear before any other declaration
// an ordinary line comment: the only other comment form Zig has
/// a doc comment, attaches to the declaration written right below
pub fn main() void {}
// no block comment syntax exists, and no real shebang either — scripts fake one via a polyglot
// //usr/bin/env zig run "$0"
02

Declarations & typing philosophy

Every binding states its complete type. A literal uses its destination type. A computed value never converts on assignment. Mixed integer operators can derive an exact common width.

Type parameters may use keyed or positional syntax. Both forms name the same canonical type. A newline or closing brace ends a statement; a semicolon refuses.

Value guarantees are checked before publication. Source writes may use explicit preparation, such as snapping, clamping, or newline conversion. Received and stored values are validated instead of repaired.

The secret and sensitive qualifiers retain their level through derived values. Key custody, encrypted columns, and protected recordings still have separate owners.

Trident.tri
const i32 limit = 3
const bool enabled = true

text main(text[] argv) {
    i32 count = 0
    i64 total = 0      # reads at i64
    f64 rate = 1.5
    f32 narrow = 1.5   # reads at f32
    return "ok"
}
# no terminator: a newline ends each one
C.c
const int limit = 3;
const bool enabled = true;

int count = 0;
long long total = 0LL;
double rate = 1.5;
float narrow = 1.5f;
C#.cs
const int limit = 3;
const bool enabled = true;

int count = 0;
long total = 0L;
double rate = 1.5;
float narrow = 1.5f;
var inferred = 3; // compile-time
C++.cpp
constexpr int limit = 3;
constexpr bool enabled = true;

int count = 0;
long long total = 0LL;
double rate = 1.5;
auto inferred = 3; // compile-time
Cutlass.cut
const i32 limit = 3
const bool enabled = true

export void main() {
    i32 count = 0
    i64 total = 0      # reads at i64
    f64 rate = 1.5
    f32 narrow = 1.5   # reads at f32
    u8[64] buf         # empty: zeroed
}
# no terminator: a newline ends each one, and ; refuses at parse
Dart.dart
const int limit = 3;
const bool enabled = true;

int count = 0;
int total = 0;    // native: signed 64-bit; web: JavaScript number, exact through 53 bits
double rate = 1.5;
double narrow = 1.5; // no separate f32 width
var inferred = 3; // local-only, still sound static typing
Elixir.ex
limit = 3
enabled = true

count = 0
total = 0     # one Integer kind, arbitrary precision
rate = 1.5    # one Float kind, always 64-bit
narrow = rate # no narrower float width exists to read into no type annotations anywhere;
# @spec/@type are advisory,
# checked only by Dialyzer, never by the compiler
Go.go
const limit = 3
const enabled = true

var count int = 0
total := int64(0)
rate := 1.5 // inferred float64
Haskell.hs
import Data.Int (Int64)

limit :: Int
limit = 3
enabled :: Bool
enabled = True

count :: Int
count = 0
total :: Int64
total = 0        -- reads at Int64; the literal itself carries no width suffix
rate :: Double
rate = 1.5
narrow :: Float
narrow = 1.5      -- same literal, reads at Float
Jai.jai
LIMIT   :: 3;        // a constant
ENABLED :: true;

count  : s32 = 0;
total  : s64 = 0;
rate   : float64 = 1.5;
narrow : float32 = 1.5;
inferred := 3;       // := infers s64
// three declaration forms — : with a
// type, := inferred, :: constant —
// and every one of them ends in a ;
Java.java
final int limit = 3;
final boolean enabled = true;

int count = 0;
long total = 0L;
double rate = 1.5;
float narrow = 1.5f;
var inferred = 3; // local-only
JavaScript.js
const limit = 3;
const enabled = true;

let count = 0;
let total = 0;   // one Number kind, always 64-bit float
let rate = 1.5;
Julia.jl
const LIMIT = 3
const ENABLED = true

count = 0
total::Int64 = 0  # typed global,
                    # 1.8+ only
rate = 1.5          # bare float literal: always Float64
narrow = Float32(1.5) # width via constructor, not a suffix
Kotlin.kt
const val LIMIT = 3
const val ENABLED = true

var count = 0            // inferred Int
var total: Long = 0      // explicit type; no suffix on the literal
var rate = 1.5            // inferred Double
var narrow: Float = 1.5f  // suffix still required
Lua.lua
local limit = 3
local enabled = true

local count = 0
local total = 0   -- Lua 5.4 integer subtype; standard build: signed 64-bit no declared local width
local rate = 1.5
Luau.luau
local limit = 3
local enabled = true

local count: number = 0
local total: number = 0
local rate: number = 1.5
-- Luau layers gradual types over Lua. Still one `number` (binary64), plus string, boolean, buffer,
-- vector and unions like `number?` — and the annotation is optional everywhere
Nim.nim
const limit: int32 = 3
const enabled: bool = true

var count: int32 = 0
var total: int64 = 0
var rate: float64 = 1.5
var narrow: float32 = 1.5
let inferred = 3   # unlike Trident, let/var infer from the value
Objective-C.m
static const int32_t limit = 3;
static const BOOL enabled = YES;

int32_t count = 0;
int64_t total = 0;   // must state the width; no suffix
double rate = 1.5;
float narrow = 1.5f;  // narrows on assign either way; suffix is optional
Perl.pl
use constant LIMIT => 3;
use constant ENABLED => 1;
# no bool type; 1 / '' is the truthy/falsy idiom

my $count = 0;
my $total = 0;   # one scalar kind: IV, NV, or PV, picked at runtime
my $rate = 1.5;
my $narrow = 1.5; # no f32 -same NV
PHP.php
<?php
declare(strict_types=1);

const LIMIT = 3;
const ENABLED = true;

$count = 0;
$total = 0;     // one int kind, usually 64-bit
$rate = 1.5;
$narrow = 1.5;   // one float kind, always a double a plain variable carries no static type at all,
// ever
Python.py
limit = 3
enabled = True

count = 0
total: int = 0   # hint is unchecked at runtime
rate = 1.5
Ruby.rb
LIMIT = 3
ENABLED = true
# reassigning a constant only warns — never refuses

count = 0
total = 0      # Integer: one kind, arbitrary precision
rate = 1.5      # Float: always a 64-bit double
narrow = 1.5     # no narrower float to ask for
Rust.rs
const LIMIT: i32 = 3;
const ENABLED: bool = true;

let count: i32 = 0;
let total: i64 = 0;
let rate = 1.5; // inferred f64
let mut m = 0;   // opt in to reassignment
Scala.scala
val limit: Int = 3
val enabled: Boolean = true

var count = 0    // inferred — Trident never does
val total: Long = 0
val rate = 1.5    // inferred Double
val narrow: Float = 1.5f
Swift.swift
let limit: Int32 = 3
let enabled = true // inferred Bool

var count: Int32 = 0
var total: Int64 = 0    // reads at Int64
var rate: Double = 1.5
var narrow: Float = 1.5 // reads at Float
var inferred = 3 // Swift infers types everywhere; Trident never does
TypeScript.ts
const limit: number = 3;
const enabled: boolean = true;

let count: number = 0;
let total: bigint = 0n; // no 32/64 split; one big-int kind
let rate: number = 1.5;
let narrow: number = 1.5; // same type, no f32
Verse.verse
Limit : int = 3          # immutable
Enabled : logic = true

var Count : int = 0      # var to write
set Count = 1

Rate : float = 1.5
# no widths anywhere: one int
# (arbitrary precision), one float
# (binary64), and the boolean type is
# called `logic`, not bool
Zig.zig
const limit: i32 = 3;
const enabled: bool = true;

var count: i32 = 0;
var total: i64 = 0; // literal adapts to i64, same rule as Trident
var rate: f64 = 1.5;
var narrow: f32 = 1.5; // adapts to f32 unlike Trident, mixed i32/i64 arithmetic still needs an
// explicit @intCast — no implicit widening at all

Admission is part of the complete type

Trident.tri
f64 input = 0.5
f64<range=[0,1]> probability = input
text<length=[5,5],bytes=[5,6]> word = "héllo"
f64<range=[0,1]> invalid = 1.5  # refuses

A write is prepared before it is admitted

Trident.tri
i32 reading = 120
i32<range=[0,100],outside=clamp> score = reading  # 100
decimal<scale=2,multiple=0.05,snap=nearestEven> price = 1.23  # 1.25
f64<finite=true> sample = 0.25
i32<range=[0,100],outside=clampLow> low = 101  # refuses
duration taken = 2m
duration<range=[0,30],outside=clamp> timeout = taken  # 30 s, shown in minutes
duration<multiple=0.001,snap=ceil> delay = 2500us  # 3 ms
duration<range=[0,30]> late = 45s  # refuses

A text write is prepared before it is admitted

Trident.tri
text raw = "  Quarterly   report  "
text<trim=both,space=collapse> label = raw  # Quarterly report
text<newlines=lf> portable = "one\r\ntwo\rthree"  # three LF lines
text<length=[0,8],full=dropLast> caption = "Hello, world"  # Hello, w
text<normalize=nfc> name = "e\u{0301}"  # one scalar, U+00E9
text<exclude=[U+002F,U+005C]> slug = "a/b"  # refuses
text path = "a/b"
text<exclude=[U+002F,U+005C]> part = path  # faults

A tensor pins its shape and its lanes

Trident.tri
tensor<dtype=f64> raw = "f64[2,2]0x000000000000f03f000000000000004000000000000000400000000000000840"
tensor<dtype=f64,shape=[*,2],nonnegative=true> grid = raw
tensor<dtype=f64,shape=[*,*],structure=symmetric,finite=true> covariance = raw
tensor<dtype=f64,shape=[*,3]> points = raw  # faults
tensor<dtype=f64,laneRange=[0,1]> fraction = "f64[1]0x0000000000000040"  # refuses

Secret and sensitive flows

Trident.tri
sensitive text token = "session-token"
sensitive text label = `token={token}`
bool same = token == "session-token"
t.log.info(label)  # refuses
03

Constants

A module binding must be const and an admitted literal. A computed expression at module scope refuses. A program can call a function to build a value after entry.

No constant initializer runs before the entry. A module therefore has no hidden startup call, file read, or mutable global state.

Trident.tri
const f64<mass> load = 5kg
const f64<acceleration> g = 9.81m/s^2

# refuses: not a literal
# const f64 piHalf = m.pi() / 2.0
C.c
#define LOAD_KG 5 /* textual macro,
                      no type at all */
const double pi_half = 3.14159265 / 2;
C#.cs
const int LoadKg = 5; // must be a compile-time constant
static readonly double PiHalf =
    Math.PI / 2; // computed, set once
C++.cpp
constexpr int load_kg = 5; // must be compile-time
const double pi_half =
    std::numbers::pi / 2;
Cutlass.cut
const f64<mass> load = 5kg
const f64<acceleration> g = 9.81m/s^2

# admitted, unlike Trident: a const initializer is any expression the compiler can run, calls
# included
const f64 piHalf = 3.14159265 / 2.0
const u32[256] crc = crcFill()
# crcFill runs inside the compiler; an initializer that reaches a static, a device, or a system call
# refuses
Dart.dart
import 'dart:math' as math;

const loadKg = 5;        // literal; must be a compile-time constant
final piHalf =
    math.pi / 2; // any expression; final, set once, not const
Elixir.ex
defmodule Physics do
  @load_kg 5
  @pi_half :math.pi() / 2 # any expression, evaluated at compile time attributes aren't locked:
  # writing @load_kg again below
  # is legal — no error,
  # just a silent redefinition
end
Go.go
const LoadKg = 5 // must be a compile-time constant
var PiHalf = math.Pi / 2 // computed package var
Haskell.hs
loadKg :: Int
loadKg = 5

piHalf :: Double
piHalf = pi / 2
-- every top-level binding is already immutable, full stop; Haskell never additionally restricts one
-- to a literal the way Trident's const does
Jai.jai
LOAD_KG :: 5.0;
PI_HALF :: 3.14159265358979 / 2;
// :: takes any compile-time-known
// expression, not just a literal

TABLE :: #run build_table();
// #run executes ordinary code INSIDE
// the compiler and bakes the result in, so a constant can be whatever a program computed at compile
// time
Java.java
static final int LOAD_KG = 5;
static final double PI_HALF =
    Math.PI / 2; // any expression, evaluated once
JavaScript.js
const LOAD_KG = 5;
const PI_HALF = Math.PI / 2;
// const blocks reassignment, not mutation of an object
Julia.jl
using Unitful

const LOAD_KG = 5u"kg"
const PI_HALF = pi / 2
# any expression is fine here; const only lets the compiler assume the type won't change, it doesn't
# forbid rebinding
Kotlin.kt
const val LOAD_KG = 5      // must be a compile-time constant
val PI_HALF = Math.PI / 2  // computed once; `val`, not `const val`
Lua.lua
local LOAD_KG = 5   -- convention only; nothing is truly immutable
local PI_HALF = math.pi / 2
Luau.luau
local LOAD_KG = 5
local PI_HALF = math.pi / 2
-- convention only: a local binding can always be reassigned. What Luau adds over Lua is
-- table.freeze(t), which seals a TABLE at runtime, and `read` properties in the type checker —
-- neither one makes a number constant
Nim.nim
import std/math

const loadKg: int32 = 5
const piHalf: float64 =
    PI / 2.0
# legal: any compile-time
# -evaluable expression,
# not just a literal
Objective-C.m
#define LOAD_KG 5    /* textual macro,
                         no type at all */
static const double pi_half =
    M_PI / 2; // libc constant; folded at compile, unlike Trident's literal-only rule
Perl.pl
use constant LOAD_KG => 5;
use constant PI_HALF =>
    atan2(1, 1) * 2;
# any expression; inlined by the compiler, not just "assigned once"
PHP.php
<?php
const LOAD_KG = 5;
const PI_HALF = M_PI / 2;
// any constant expression is allowed here, not just a literal
Python.py
import math
LOAD_KG = 5        # ALL_CAPS is convention only
PI_HALF = math.pi / 2 # any expression; nothing is enforced
Ruby.rb
LOAD_KG = 5
PI_HALF = Math::PI / 2
# any expression; nothing is truly immutable — reassigning LOAD_KG later
# only prints a warning
Rust.rs
const LOAD_KG: i32 = 5; // const-expr only
static PI_HALF: f64 =
    std::f64::consts::PI / 2.0;
Scala.scala
object Constants {
  final val LoadKg: Int = 5 // compile-time constant literal
  val PiHalf: Double =
    math.Pi / 2 // any expression; Trident refuses it
}
Swift.swift
let loadKg = 5

let piHalf = Double.pi / 2
// `let` allows any expression, evaluated once at that point; nothing requires a literal
TypeScript.ts
const LOAD_KG: number = 5;
const PI_HALF: number = Math.PI / 2;
// any expression is fine; the type annotation is checked, not enforced at runtime
// (erased once compiled)
Verse.verse
Limit<public> : int = 3
Load : float = 5.0
PiHalf : float = 3.1415926535 / 2.0

# there is no const keyword, because a binding without `var` is already immutable: the constant is
# the default and mutability is the thing a program has to ask for
Zig.zig
const std = @import("std");

const load_kg: f64 = 5.0;

// unlike Trident, this is legal: any comptime-known expression, not just a literal — comptime
// evaluates it once
const pi_half: f64 =
    std.math.pi / 2.0;
04

Enums

An enum is a distinct type over an i32 value. Each member declares its permanent number. A qualified member can appear anywhere; a bare member needs a destination of that enum type.

An i32 destination reads the enum's backing value as a positional projection. Different enum domains cannot compare as one type. A retired member remains readable in predicates but refuses as a new value.

Module identity supplies the domain. An explicit origin name can keep the identity stable when a file moves.

Trident.tri
enum Status { Ok = 0, Warn = 5, Error = 10 }
enum Health { Ok = 1, Hurt = 2 }

Status S = Status.Ok
Health h = Health.Ok   # a second Ok, and no collision
i32 raw = Status.Ok    # projects the i32

# i32 bad = Ok
# refuses: a member needs its enum name
C.c
enum Status { OK = 0, WARN = 5, ERROR = 10 };
/* backing width is
   implementation-defined */
enum Status s = OK;
int raw = s; // decays to int freely
C#.cs
enum Status { Ok = 0, Warn = 5, Error = 10 }

Status s = Status.Ok;
int raw = (int)s; // explicit cast to project it
C++.cpp
enum class Status : int32_t {
  Ok = 0, Warn = 5, Error = 10
};
Status s = Status::Ok;
auto raw =
  static_cast<int32_t>(s); // explicit
Cutlass.cut
enum Status { Ok = 0, Warn = 5, Error = 10 }
enum Health with { width = u8 } {
    Ok = 1, Hurt = 2 }
# a width is a with setting; the default is i32, as in Trident

Status s = Status.Ok
Health h = Health.Ok  # a second Ok, and no collision
i32 code = Status.Ok  # projects the i32

# i32 bad = Ok
# refuses: a member needs its enum name
Dart.dart
enum Status {
  ok(0), warn(5), error(10);
  const Status(this.code);
  final int code;
}

Status s = Status.ok;
int raw = s.code; // explicit field read; .index gives 0,1,2, not code
Elixir.ex
defmodule Status do
  def code(:ok), do: 0
  def code(:warn), do: 5
  def code(:error), do: 10
end

s = :ok                  # atom: its own value, not an int
raw = Status.code(s)     # 0 — atoms
# carry no backing integer; projecting one is a hand-written lookup, not automatic
Go.go
type Status int
const (Ok Status = 0
  Warn Status = 5
  Error Status = 10)
s := Ok
raw := int(s) // explicit convert
Haskell.hs
data Status = Ok | Warn | Error
  deriving (Eq, Show)

code :: Status -> Int
code Ok    = 0
code Warn  = 5
code Error = 10

s :: Status
s = Ok          -- qualified: distinct type
raw :: Int
raw = code Ok   -- projection is an explicit function, never a free bare int
Jai.jai
Status :: enum s32 {
    OK    :: 0;
    WARN  :: 5;
    ERROR :: 10;
}

s : Status = .OK;      // or Status.OK
raw := cast(s32) s;    // explicit

// enum_flags is a second keyword for bit sets, and #complete on a switch makes a missing member a
// compile
// error rather than a silent default
Java.java
enum Status {
  OK(0), WARN(5), ERROR(10);
  final int code;
  Status(int c) { code = c; }
}
Status s = Status.OK;
int raw = s.code;
JavaScript.js
const Status = Object.freeze({
  Ok: 0, Warn: 5, Error: 10, });
// no native enum — s is just a plain number, no distinct type
const s = Status.Ok;
Julia.jl
@enum Status Ok=0 Warn=5 Error=10

s = Ok          # already typed
                  # ::Status; there
                  # is no Status.Ok qualified form
raw = Int32(s)   # explicit projection, never implicit
Kotlin.kt
enum class Status(val code: Int) {
    Ok(0), Warn(5), Error(10)
}

val s = Status.Ok   // qualified: enum type
val raw = s.code     // explicit projection;
                       // ordinal() is
                       // position, not code
Lua.lua
local Status = { Ok = 0, Warn = 5, Error = 10 }
-- no enums — just a table of numbers, no distinct type
local s = Status.Ok
Luau.luau
local Status = table.freeze({
    Ok = 0, Warn = 5, Error = 10, })
type Status = "Ok" | "Warn" | "Error"
-- two half-answers and no enum: a frozen table of numbers with no distinct type, or a
-- string-literal union the checker understands that has no numeric value behind it
Nim.nim
type Status {.size: 4.} = enum
  Ok = 0, Warn = 5, Error = 10

let s = Status.Ok    # qualified
let raw = ord(Ok)    # explicit; bare Ok is still typed Status
Objective-C.m
typedef NS_ENUM(int32_t, Status) {
    StatusOk = 0, StatusWarn = 5, StatusError = 10
};

Status s = StatusOk;
int32_t raw = s;  // decays freely, like C no qualified/bare split: the
// "Status" prefix is convention,
// not a namespace
Perl.pl
use constant {
    Ok    => 0, Warn  => 5, Error => 10, };
# no enum type at all - Ok is a plain inlined sub; qualified vs bare aren't different, both are just
# an integer
my $s = Ok;
my $raw = Ok;
PHP.php
<?php
enum Status: int {
    case Ok = 0;
    case Warn = 5;
    case Error = 10;
}

$s = Status::Ok;        // enum type
$raw = Status::Ok->value; // int;
                            // ->value
                            // is required
Python.py
from enum import IntEnum

class Status(IntEnum):
    Ok, Warn, Error = 0, 5, 10

s = Status.Ok
raw = Status.Ok.value
Ruby.rb
module Status
  OK = 0
  WARN = 5
  ERROR = 10
end

s = Status::OK
raw = s
# no enum type at all — s and raw are both just a plain Integer
Rust.rs
#[repr(i32)]
enum Status { Ok = 0, Warn = 5, Error = 10 }

let s = Status::Ok;
let raw = s as i32; // explicit cast
Scala.scala
enum Status(val code: Int):
  case Ok extends Status(0)
  case Warn extends Status(5)
  case Error extends Status(10)

val s = Status.Ok
val raw = s.code // explicit; no bare projection
Swift.swift
enum Status: Int32 {
    case ok = 0, warn = 5, error = 10
}

let s = Status.ok    // enum type
let raw = s.rawValue  // explicit projection, never implicit
TypeScript.ts
enum Status { Ok = 0, Warn = 5, Error = 10 }

let s: Status = Status.Ok;
let raw: number = Status.Ok;
// numeric enum members ARE already numbers; no separate
// "projected" read, and no
// distinct enum type at runtime
Verse.verse
status := enum{Ok, Warn, Error}

S : status = status.Ok

# a member carries no integer value at all, so there is nothing to project to an int and no
# bare-name spelling to collide — the collision Trident refuses cannot be written here
Zig.zig
const Status = enum(i32) {
    ok = 0, warn = 5, err = 10, };

const s: Status = .ok; // or Status.ok
const raw: i32 = @intFromEnum(s);
// explicit, like Rust/C++ — no bare-name projection to i32
05

Flag sets

A flagset is a distinct type over one u64. Union, intersection, symmetric difference, and complement return that same type. has tests membership; count counts set bits.

Complement is bounded by the declaration's live members. Ordering, arithmetic, and shifts refuse. A u64 destination can read the backing word; an unrelated integer cannot join the set operations.

Trident.tri
flagset Access { Read = 1, Write = 2, Exec = 4 }

Access rw = Access.Read | Access.Write
Access ro = rw & ~Access.Write
rw |= Access.Exec

bool ok = rw.has(Access.Read)
i64 n = rw.count()      # 3
u64 raw = rw            # 7, at a u64
                        # destination only
Access none = 0         # the empty set

# rw + 1     refuses: not arithmetic
# rw < ro    refuses: a set has no order
# rw << 1    refuses: a bit has a name
C.c
enum { READ = 1u, WRITE = 2u, EXEC = 4u };

unsigned rw = READ | WRITE;
unsigned ro = rw & ~WRITE;
rw |= EXEC;

int ok = (rw & READ) == READ;
int n  = __builtin_popcount(rw);
/* ~WRITE is 0xFFFFFFFD: the
   complement of the whole width,
   masked back by hand or not at
   all. rw + 1 and rw < ro both
   compile: it is an unsigned. */
C#.cs
[Flags]
enum Access { Read = 1, Write = 2, Exec = 4 }

var rw = Access.Read | Access.Write;
var ro = rw & ~Access.Write;
rw |= Access.Exec;

bool ok = rw.HasFlag(Access.Read);
int n = BitOperations.PopCount((uint)rw);
// [Flags] changes how a value prints
// and parses, and nothing about the operators: ~ is the integer complement, and (Access)4096,
// rw + 1 and rw < ro all compile
C++.cpp
enum class Access : unsigned {
  Read = 1, Write = 2, Exec = 4 };

// the operators do not exist until you write them — one operator|,
// operator&, operator~ per enum, or
// a macro that emits them
constexpr Access operator|(Access a, Access b) {
  return Access(unsigned(a) | unsigned(b));
}
auto rw = Access::Read | Access::Write;
int n = std::popcount(unsigned(rw));
// std::bitset<3> is the other route,
// and it drops the member names
Cutlass.cut
flagset Access { Read = 1, Write = 2, Exec = 4 }
# the word is u64 by default, and
# with { width = u8 } narrows it

Access rw = Access.Read | Access.Write
Access ro = rw & ~Access.Write
rw |= Access.Exec

bool ok = rw.has(Access.Read)
i64 n = rw.count()  # 3
u64 word = rw       # 7, at a u64
                    # destination only
Access none = 0     # the empty set

# rw + 1     refuses: not arithmetic
# rw < ro    refuses: a set has no order
# rw << 1    refuses: a bit has a name
Dart.dart
enum Access { read, write, exec }

// no bit-set type and no bitwise operators on an enum: a Set, which is a hash table, or a plain int
var rw = <Access>{Access.read, Access.write};
rw.remove(Access.write);
rw.add(Access.exec);

bool ok = rw.contains(Access.read);
int n = rw.length;
// no complement at all; you write
// Access.values.toSet().difference(rw)
Elixir.ex
import Bitwise

@read 1
@write 2
@exec 4

rw = @read ||| @write
ro = rw &&& ~~~@write
ok = (rw &&& @read) == @read

# there is no enum and no bit-set type: a MapSet of atoms, or Bitwise over an arbitrary-precision
# integer, where ~~~ has no universe to stop at
Go.go
type Access uint64

const (Read Access = 1 << iota
    Write
    Exec)

rw := Read | Write
rw &^= Write        // AND NOT
rw |= Exec

ok := rw&Read == Read
n := bits.OnesCount64(uint64(rw))
// Access IS a uint64, so rw + 1,
// rw < ro and rw << 1 all compile
Haskell.hs
import Data.Bits

data Access = Read | Write | Exec
  deriving (Enum, Bounded, Show)

-- base has no bit-set type: Data.Bits over an Int, or the enumset package
rw :: Int
rw = bit (fromEnum Read)
 .|. bit (fromEnum Write)

n :: Int
n = popCount rw
-- complement is over the Int width
Jai.jai
Access :: enum_flags u8 {
    READ;
    WRITE;
    EXEC;
}

rw := Access.READ | Access.WRITE;
rw &= ~Access.WRITE;
rw |= Access.EXEC;

ok := (rw & Access.READ) != 0;
// enum_flags is a real language form: the bits are assigned for you and printing a value names its
// members
Java.java
enum Access { READ, WRITE, EXEC }

EnumSet<Access> rw =
    EnumSet.of(Access.READ, Access.WRITE);
EnumSet<Access> ro = EnumSet.copyOf(rw);
ro.remove(Access.WRITE);
rw.add(Access.EXEC);

boolean ok = rw.contains(Access.READ);
int n = rw.size();
// EnumSet.complementOf IS bounded by the enum — but it is a heap object with a long inside, reached
// by method calls, never by | and &
JavaScript.js
const Access = Object.freeze({
  Read: 1, Write: 2, Exec: 4 });

let rw = Access.Read | Access.Write;
const ro = rw & ~Access.Write;
rw |= Access.Exec;

const ok = (rw & Access.Read) !== 0;
// bitwise operands are coerced to int32, so bit 31 is the sign bit and bit 63 has no spelling at
// all unless you move to BigInt
Julia.jl
using BitFlags

@bitflag Access Read Write Exec

rw = Read | Write
ro = rw & ~Write
rw |= Exec

ok = (rw & Read) == Read
n = count_ones(Integer(rw))
# a registered package, not the language — Base has count_ones and the bitwise operators, and no
# named-bit type to put them on
Kotlin.kt
enum class Access { READ, WRITE, EXEC }

val rw = EnumSet.of(Access.READ, Access.WRITE)
rw.remove(Access.WRITE)
rw.add(Access.EXEC)

val ok = Access.READ in rw
val n = rw.size
// java.util.EnumSet, so it exists on the JVM and nowhere else: Kotlin/Native and Kotlin/JS have no
// equivalent in the standard library
Lua.lua
local Access = { Read = 1, Write = 2, Exec = 4 }

local rw = Access.Read | Access.Write
local ro = rw & ~Access.Write
rw = rw | Access.Exec

local ok = (rw & Access.Read) ~= 0
-- 5.3+ integers are 64-bit signed.
-- There is no set type and no member names on a value: rw is a number and prints as one
Luau.luau
local Access = { Read = 1, Write = 2, Exec = 4 }

local rw = bit32.bor(Access.Read, Access.Write)
local ro = bit32.band(rw, bit32.bnot(Access.Write))
rw = bit32.bor(rw, Access.Exec)

local ok = bit32.band(rw, Access.Read) ~= 0
-- bit32 is 32-bit and the numbers are doubles: bit 63 does not exist
Nim.nim
type Access = enum
  Read, Write, Exec

var rw: set[Access] = {Read, Write}
let ro = rw - {Write}
rw.incl Exec

let ok = Read in rw
let n = rw.card
# set[T] is built into the language
# and its difference is bounded by the type, like Trident's ~. The ordinal range is capped at 65536
# values
Objective-C.m
typedef NS_OPTIONS(NSUInteger, Access) {
    AccessRead  = 1 << 0, AccessWrite = 1 << 1, AccessExec  = 1 << 2, };

Access rw = AccessRead | AccessWrite;
Access ro = rw & ~AccessWrite;
rw |= AccessExec;

BOOL ok = (rw & AccessRead) != 0;
// NS_OPTIONS is a macro over a typedef'd integer: rw + 1 compiles and ~ is the whole width
Perl.pl
use constant { READ => 1, WRITE => 2, EXEC => 4 };

my $rw = READ | WRITE;
my $ro = $rw & ~WRITE;
$rw |= EXEC;

my $ok = ($rw & READ) != 0;
# no enum and no set type at all: a hash of names, a bit string with
# vec(), or constants and a scalar
PHP.php
final class Access {
    const Read  = 1;
    const Write = 2;
    const Exec  = 4;
}

$rw = Access::Read | Access::Write;
$ro = $rw & ~Access::Write;
$rw |= Access::Exec;

$ok = ($rw & Access::Read) !== 0;
// 8.1 enums cannot be combined at
// all, so a bit set stays plain int constants on a class
Python.py
from enum import Flag, auto

class Access(Flag):
    Read = auto()
    Write = auto()
    Exec = auto()

rw = Access.Read | Access.Write
ro = rw & ~Access.Write
rw |= Access.Exec

ok = Access.Read in rw
n = len(rw)              # 3.11+
# enum.Flag is the closest match here:
# ~ is bounded by the declared members
# under the default boundary, as Trident's is
Ruby.rb
module Access
  READ  = 1
  WRITE = 2
  EXEC  = 4
end

rw = Access::READ | Access::WRITE
ro = rw & ~Access::WRITE
rw |= Access::EXEC

ok = (rw & Access::READ) != 0
# Integers are arbitrary precision, so
# ~ has no width to stop at either —
# ~WRITE is -3, and masking is the
# author's job every time
Rust.rs
use bitflags::bitflags;

bitflags! {
    struct Access: u64 {
        const READ  = 1;
        const WRITE = 2;
        const EXEC  = 4;
    }
}

let mut rw = Access::READ | Access::WRITE;
let ro = rw & !Access::WRITE;
rw |= Access::EXEC;

let ok = rw.contains(Access::READ);
let n = rw.bits().count_ones();
// a crate and a macro. ! IS bounded by the declared bits — because the macro writes that mask for
// you
Scala.scala
enum Access { case Read, Write, Exec }

// no flag form in the language: java.util.EnumSet on the JVM, or a scala.collection.BitSet, which
// carries no member names
val rw = java.util.EnumSet.of(Access.Read, Access.Write)
rw.remove(Access.Write)
rw.add(Access.Exec)

val ok = rw.contains(Access.Read)
val n = rw.size
Swift.swift
struct Access: OptionSet {
    let rawValue: UInt64
    static let read  = Access(rawValue: 1)
    static let write = Access(rawValue: 2)
    static let exec  = Access(rawValue: 4)
}

var rw: Access = [.read, .write]
let ro = rw.subtracting(.write)
rw.insert(.exec)

let ok = rw.contains(.read)
let n = rw.rawValue.nonzeroBitCount
// OptionSet is a protocol over a raw integer you declare yourself — the bits are yours to keep
// distinct
TypeScript.ts
enum Access {
  Read = 1, Write = 2, Exec = 4 }

// | over two members widens to
// `number`, so the annotation needs a cast back
let rw: Access =
  (Access.Read | Access.Write) as Access;
const ro = (rw & ~Access.Write) as Access;

const ok = (rw & Access.Read) !== 0;
// it is a number underneath, and bitwise operands are coerced to int32: bit 63 is unreachable
Verse.verse
Access := enum { Read, Write, Exec }

# Verse has the enum and nothing after it: no bit-set type, no bitwise operators on an enum, and no
# set-of-enum in the standard library. A set of options is separate logic fields, or a map keyed by
# the enum — neither of which is one word you can store in a document field
Zig.zig
const Access = enum { read, write, exec };
const Set = std.EnumSet(Access);

var rw = Set.initMany(&.{ .read, .write });
const ro = rw.differenceWith(Set.initOne(.write));
rw.insert(.exec);

const ok = rw.contains(.read);
const n = rw.count();
// std.EnumSet is a packed bit set in the standard library, and its
// complement() IS bounded by the enum;
// a packed struct of bools is the other idiom
06

Functions & the entry point

A function states its result type, name, and complete parameter list. It returns one value or void. Source units execute no top-level statements.

Script mode calls main(text[] argv). Command-line words after -- become that array. A Host can select another admitted entry from a compiled bundle.

Trident.tri
text main(text[] argv) {
    if (argv.count() == 0) {
        return "no arguments"
    }
    return "first argument: "
        + argv[0]
}
# $ tricli entry.tri -- alpha beta
C.c
int main(int argc, char *argv[]) {
  if (argc == 1) {
    puts("no arguments");
  } else {
    printf("first argument: %s\n", argv[1]);
  }
  return 0;
}
C#.cs
class Program {
  static void Main(string[] args) {
    string s = args.Length == 0
      ? "no arguments"
      : "first argument: " + args[0];
    Console.WriteLine(s);
  }
}
C++.cpp
int main(int argc, char* argv[]) {
  std::string s = argc == 1
    ? "no arguments"
    : "first argument: "
        + std::string(argv[1]);
  std::cout << s << "\n";
}
Cutlass.cut
text describe(text[] argv) {
    if (argv.count() == 0) {
        return "no arguments"
    }
    return "first argument: "
        + argv[0]
}

export void main() {
    text s = describe(cut.host.args())
        else fail
    cut.host.write(`{s}\n`) else use 0
}
# the target description names the entry, which takes no parameter; the arguments come from
# cut.host.args(). A call that can fail names its arm
Dart.dart
void main(List<String> args) {
  final s = args.isEmpty
      ? "no arguments"
      : "first argument: ${args[0]}";
  print(s);
}
// $ dart run entry.dart -- alpha beta
Elixir.ex
defmodule Entry do
  def main(argv) do
    case argv do
      [] -> "no arguments"
      [first | _] ->
        "first argument: " <> first
    end
  end
end

IO.puts(Entry.main(System.argv()))
# $ elixir entry.exs alpha beta
# top-level code in a script DOES run on load — there is no isolated entry point like Trident
# enforces
Go.go
func main() {
  args := os.Args[1:]
  s := "no arguments"
  if len(args) > 0 {
    s = "first argument: " + args[0]
  }
  fmt.Println(s)
}
Haskell.hs
import System.Environment (getArgs)

main :: IO ()
main = do
    argv <- getArgs
    putStrLn (run argv)

run :: [String] -> String
run []    = "no arguments"
run (a:_) = "first argument: " ++ a
-- top-level bindings are pure; only main's IO actually runs
-- $ runghc entry.hs -- alpha beta
Jai.jai
#import "Basic";

main :: () {
    args := get_command_line_arguments();
    defer array_free(args);

    s := ifx args.count == 1
         then "no arguments"
         else args[1];
    print("%\n", s);
}
// `main :: ()` is the entry point, and
// args[0] is the executable path, as
// it is in C
Java.java
class Main {
  public static void main(String[] a) {
    String s = a.length == 0
      ? "no arguments"
      : "first argument: " + a[0];
    System.out.println(s);
  }
}
JavaScript.js
function main(argv) {
  if (argv.length === 0) {
    return "no arguments";
  }
  return "first argument: " + argv[0];
}
console.log(main(process.argv.slice(2)));
Julia.jl
function main(argv)
    if isempty(argv)
        return "no arguments"
    end
    return "first argument: " *
        argv[1]
end

println(main(ARGS))
# top-level statements DO run; ARGS holds argv, 1-indexed
# $ julia entry.jl alpha beta
Kotlin.kt
fun main(args: Array<String>) {
    val s = if (args.isEmpty())
        "no arguments"
    else
        "first argument: " + args[0]
    println(s)
}
Lua.lua
local function main(argv)
  if #argv == 0 then
    return "no arguments"
  end
  return "first argument: " .. argv[1]
end
print(main(arg))
Luau.luau
local function main(argv: {string}): string
    if #argv == 0 then
        return "no arguments"
    end
    return "first argument: " .. argv[1]
end
-- no entry point and no argv either. A Script runs top to bottom when the engine loads it; a
-- ModuleScript returns a table instead of running
Nim.nim
import std/os

proc main(argv: seq[string]): string =
  if argv.len == 0:
    return "no arguments"
  return "first argument: " & argv[0]

when isMainModule:
  echo main(commandLineParams())
# top-level code outside a proc still runs when loaded
# $ nim r entry.nim -- alpha beta
Objective-C.m
int main(int argc, char *argv[]) {
  @autoreleasepool {
    NSString *s;
    if (argc == 1) {
      s = @"no arguments";
    } else {
      s = [NSString stringWithFormat:
          @"first argument: %s", argv[1]];
    }
    NSLog(@"%@", s);
  }
  return 0;
}
// @autoreleasepool drains ARC's
// autorelease objects; nothing in C/C++ needs one
Perl.pl
#!/usr/bin/env perl
use strict;
use warnings;

sub main {
    my @argv = @_;
    return "no arguments"
        unless @argv;
    return "first argument: "
        . $argv[0];
}
print main(@ARGV), "\n";
# no mandated entry point -top-level statements run too, unlike Trident
PHP.php
<?php
function main(array $argv): string {
    if (count($argv) === 0) {
        return "no arguments";
    }
    return "first argument: " . $argv[0];
}

echo main(array_slice($argv, 1));
// top-level statements always
// run; $argv is already filled
// in by the CLI SAPI before this
Python.py
import sys

def main(argv):
    if not argv:
        return "no arguments"
    return "first argument: " + argv[0]

if __name__ == "__main__":
    print(main(sys.argv[1:]))
Ruby.rb
def main(argv)
  return "no arguments" if argv.empty?
  "first argument: #{argv[0]}"
end

puts main(ARGV)
# top-level statements run immediately — there's no single entry point the runner calls
Rust.rs
fn main() {
  let a: Vec<String> =
    std::env::args().skip(1).collect();
  let s = if a.is_empty() {
    "no arguments".into()
  } else {
    format!("first argument: {}", a[0])
  };
  println!("{s}");
}
Scala.scala
object Main {
  def main(args: Array[String]): Unit = {
    val s = if (args.isEmpty)
      "no arguments"
    else
      "first argument: " + args(0)
    println(s)
  }
}
// Scala 3: @main def main(
//   argv: String*): Unit
Swift.swift
func main(_ argv: [String]) -> String {
    argv.isEmpty ? "no arguments"
        : "first argument: " + argv[0]
}
print(main(Array(CommandLine.arguments.dropFirst())))
// top-level code in main.swift runs directly too; Trident allows no such implicit mode
TypeScript.ts
function main(argv: string[]): string {
  if (argv.length === 0) {
    return "no arguments";
  }
  return "first argument: " + argv[0];
}
console.log(main(process.argv.slice(2)));
// top-level statements DO run; calling main is a convention here, never a language rule
Verse.verse
using { /Verse.org/Simulation }

my_device := class(creative_device):

    OnBegin<override>()<suspends> : void =
        Print("ok")

# there is no main and no argv: a device class is placed in the level and OnBegin runs. <suspends>
# in the signature declares that this function may take game time to finish
Zig.zig
const std = @import("std");

pub fn main() !void {
    var dbg = std.heap.DebugAllocator(.{}){};
    const argv = try std.process.argsAlloc(dbg.allocator());
    defer std.process.argsFree(dbg.allocator(), argv);

    const s = if (argv.len == 1)
        "no arguments"
    else
        argv[1]; // argv[0] is the
                  // exe path, like C
    std.debug.print("{s}\n", .{s});
}
// source files run no top-level statements either — only
// main (or a test block) executes
07

Linking vs. imports

A module name comes from the source path in the admitted bundle. A sibling can be reached by its relative or complete module path. An alias only shortens that known name.

The compiler resolves every reach before execution. It never fetches a module or searches for source. Shared declarations now include outlines and almanacs reached by module path.

Trident.tri
alias m = t.math

text main(text[] argv) {
    f64 halfPi = m.pi() / 2.0
    return "ok"
}
C.c
#include <stdio.h>
#include "myheader.h" // textual inclusion
C#.cs
using System;
using System.Collections.Generic;
C++.cpp
#include <vector>
import my.module; // C++20 modules,
                    // still rare
Cutlass.cut
alias m = cut.math

f64 hypot(f64 a, f64 b) {
    return m.sqrt(a * a + b * b)
}
# no import: cut, cutrun, and a node across a declared edge are reached by full path, and alias only
# shortens one. A reach into a node the edges do not name refuses at compile. Trident's t is spelled
# cut
Dart.dart
import 'dart:math' as math;
import 'package:http/http.dart' as http;
// pub resolves package: URIs via pubspec.yaml + pub.dev
Elixir.ex
alias :math, as: M
import Integer, only: [is_even: 1]

half_pi = M.pi() / 2
# resolves against the whole BEAM code path at runtime: any loaded module is already reachable by
# its full name with zero declaration at all
# — alias/import are pure
# convenience, never required
Go.go
import ("fmt"
    "math")
Haskell.hs
import qualified Data.Map as Map
import Text.Printf (printf)
-- resolves against the package database a Cabal/Stack build plan pins, never a bare search path or
-- textual
-- #include-style inclusion
Jai.jai
#import "Basic";
#import "Math";
My :: #import "My_Module";

#load "helper.jai";
// #import names a MODULE on the
// module search path; #load splices another file into this one. Both resolve while the compiler
// runs — there is no runtime loader at all
Java.java
import java.util.List;
import static java.lang.Math.PI;
JavaScript.js
import { readFile } from "node:fs/promises";
import express from "express";
Julia.jl
using Dates
using HTTP    # resolved via Project.toml, Manifest.toml, and the General registry
import Base: show
Kotlin.kt
import kotlin.math.PI
import java.util.Random // JVM interop, same import syntax
Lua.lua
local json = require("json")
-- resolves against package.path at runtime
Luau.luau
local Util = require(script.Parent.Util)
local Players = game:GetService("Players")
-- require takes an Instance, not a path string, so the module graph IS the place tree and a moved
-- script breaks its requirers. Luau's own string form, require("@self/util"), is resolved by the
-- runtime
Nim.nim
import std/math
import std/tables
include "myfragment.nim"
# include is textual, like C's #include, distinct from import's module scope
Objective-C.m
#import <Foundation/Foundation.h>
#import "MyHeader.h" // textual inclusion, same as C
@import Foundation;    // Clang's module map, still rare
Perl.pl
use strict;
use warnings;
use POSIX qw(floor);
use List::Util qw(sum);
# use resolves a package name against @INC at compile time; require does the same at runtime
PHP.php
<?php
require __DIR__ . '/helpers.php';
// textual inclusion, like C's
// #include; runs the file's
// top-level code immediately

use App\Service\Mailer;
// resolves via Composer's PSR-4 autoloader, not a file path
Python.py
import math
from collections import Counter

half_pi = math.pi / 2
Ruby.rb
require "json"
require_relative "helpers"
# require walks $LOAD_PATH
# at runtime — a typo raises LoadError only when this line executes
Rust.rs
use std::collections::HashMap;
use rand::Rng; // from Cargo.toml
Scala.scala
import scala.math.Pi
import scala.collection.mutable.Map

val halfPi = Pi / 2
// resolves against the JVM classpath; sbt/Mill fetches deps
Swift.swift
import Foundation

let halfPi = Double.pi / 2
// SwiftPM resolves package modules before the build; nothing is resolved by a running program
// itself
TypeScript.ts
import { readFile } from "node:fs/promises";
import express from "express";
import type { Request } from "express";
// resolved by node_modules and tsconfig at compile time; `import type` erases fully, leaving no
// runtime trace
Verse.verse
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/SpatialMath }

# a path names a published module, not a file on disk, and your own code is a module the same way:
my_module := module:
    Helper() : void = {}
Zig.zig
const std = @import("std"); // the compiler's implicit module

const mymod = @import("mymod.zig");
// a sibling file — unlike Trident, Zig freely imports other files in the same project

const raylib = @import("raylib");
// a name build.zig wired with addImport — no runtime search path here either, just an explicit
// build-time mapping
08

Application layout & composition

An application has source under a/ and identity in _app/app.draft. The Host assembles the source bundle and derives module names from paths. tricli can also compile one script file.

The launch lends the libraries and authority the program needs. An alias adds no dependency.

One application: four files under a/, one nested, and its lend

Tridenta/geometry/units.tri
private const f64 cmPerInch = 2.54

f64 inchesToCm(f64 inches) require (
        inches >= 0.0, "nonnegative") {
    return inches * cmPerInch
}

f64 cmToInches(f64 cm) {
    return cm / cmPerInch
}
# module id a.geometry.units
Tridenta/geometry/shape.tri
f64 rectangleAreaCm(f64 wInches, f64 hInches) {
    f64 w = units.inchesToCm(wInches)
    f64 h = units.inchesToCm(hInches)
    return w * h
}

chest describe(f64 wInches, f64 hInches) {
    chest out = {}
    out.width = wInches
    out.height = hInches
    out.areaCm = rectangleAreaCm(wInches, hInches)
    return out
}
# module id a.geometry.shape; `units` is
# its sibling a.geometry.units, which the
# absolute a.geometry.units.inchesToCm
# names from anywhere
Tridenta/report.tri
alias log = t.log

text line(chest shape) {
    f64 areaCm = shape.areaCm
    return "area " + text(areaCm) + " cm2"
}

void print(chest shape) {
    log.info(line(shape))
}
# module id a.report
Tridenta/main.tri
i32 main(text[] argv) {
    chest r = geometry.shape.describe(3.0, 4.0)
    report.print(r)
    return 0
}
# module id a.main, the entry module;
# the runner calls main(text[] argv)
Draft_grant/grant.draft
u16 schemaVersion = 1
chest[] libraries = [
    {
        u16 schemaVersion = 1
        text id = "t.log"
        chest? config = {
            u16 schemaVersion = 1
            text threshold = "info"
        }
        chest[] storeSlots = []
    }
]
# the lend: a/report.tri reaches t.log,
# which needs a binding, so the lend
# names it; nothing else here does

How a sibling file is reached elsewhere

Tridenttree
calc/
  a/
    main.tri         a.main
    report.tri       a.report
    geometry/
      units.tri      a.geometry.units
      shape.tri      a.geometry.shape
      _test/         a suite; stays out
  _app/
    app.draft        its id and name
  _grant/
    grant.draft      the lend
  README.md          the author's own

# `tricli calc` compiles everything
# under a/: a.geometry.units.f(), and
# units.f() beside it in geometry/,
# resolve with no declaration,
# `private` keeps a name to its file,
# and `alias u = a.geometry.units`
# only shortens a name
C.c
#include "geometry/units.h"
// the header is textual inclusion; the build must also compile geometry/units.c and link it.
// `static` keeps a function to its own translation unit
C#.cs
using App.Geometry;
// an SDK-style .csproj compiles every
// .cs file under the folder; namespaces
// are declared in the file, not read off the path. `internal` and `private` scope the declaration
C++.cpp
#include "geometry/units.hpp"
import app.geometry.units; // C++20
// CMake or the build lists the sources; an anonymous namespace keeps a name to one file
Cutlass.cut
calc/
  CALC.md          the lock: area order
  geometry/
    calc_geometry_units.cut
    calc_geometry_shape.cut
  report/
    calc_report.cut
  tool/
    calc_tool.cut  the one executable
  test/
    calc_canary.cut

# a module is its basename with _ read as .: calc.geometry.units is reached by that path from any
# unit of the node, with no declaration. private keeps a name to its file, the default is node-wide,
# export opens it across a declared edge, and alias only shortens a name. A later area reaches an
# earlier one, never the reverse. A program declaration states memory and grants, and a reach
# outside them refuses at compile
Dart.dart
import 'geometry/units.dart';
// relative import of a sibling file; a leading `_` makes a name library-private
Elixir.ex
alias App.Geometry.Units
# mix compiles all of lib/; the module name is free of the path. `defp` is a private function
Go.go
import "calc/geometry"
// a directory is a package; every file in it shares one scope with no import at all. An initial
// capital exports a name; lower case keeps it in the package
Haskell.hs
import App.Geometry.Units (inchesToCm)
-- module path == file path under src/;
-- the module's export list decides what is visible
Jai.jai
#load "geometry/units.jai";
// #load splices a file into this
// one; #import names a module on the search path
Java.java
package app.geometry;   // in the file
import app.geometry.Units;
// the path must match the package; package-private is the default, `private` narrows to the class
JavaScript.js
import { inchesToCm }
  from "./geometry/units.js";
// a file is a module; only what it `export`s is reachable
Julia.jl
include("geometry/units.jl")
using .Units
# include is textual; a module lists what it `export`s
Kotlin.kt
import app.geometry.inchesToCm
// the package line is free of the path; `private` at top level keeps a declaration to its file
Lua.lua
local units = require("geometry.units")
-- resolved against package.path at runtime; the file returns a table of what it chooses to expose
Luau.luau
local units =
  require(script.Parent.geometry.units)
-- the module graph is the instance tree; a ModuleScript returns what it exposes
Nim.nim
import geometry/units
# a path import; `*` after a name exports it, everything else stays private to the module
Objective-C.m
#import "geometry/Units.h"
// textual inclusion; Units.m must be in the target's compile sources
Perl.pl
use lib 'lib';
use App::Geometry::Units;
# package name maps to lib/App/Geometry/Units.pm
PHP.php
use App\Geometry\Units;
// Composer's PSR-4 map turns the namespace into src/Geometry/Units.php; `private` scopes to the
// class
Python.py
from geometry import units
# a folder is a package; the file is a module; `_name` is private by convention only
Ruby.rb
require_relative "geometry/units"
# textual load at runtime; `private` applies to methods, not files
Rust.rs
mod geometry;          // main.rs
pub mod units;         // geometry/mod.rs
use geometry::units;
// the module tree is declared and the files are found by convention; private is the default, `pub`
// opens
Scala.scala
package app.geometry   // in the file
import app.geometry.Units
// the path is free; `private[this]`
// and `private[geometry]` scope a
// member to an object or a package
Swift.swift
// no import: a target's folder is one module and every file sees the others. `private` keeps a name
// to its file; `internal` is the default
TypeScript.ts
import { inchesToCm }
  from "./geometry/units";
// a file is a module; `export` decides what leaves it
Verse.verse
using { calc.geometry }
# a folder is a module and a `using` names it by its path; `<internal>` and `<private>` scope a
# definition
Zig.zig
const units =
  @import("geometry/units.zig");
// a file is a struct; `pub fn` and `pub const` are what it exposes
09

Contracts: require, ensure, assert

require checks an argument condition. ensure checks the returned value, named result inside that clause. assert checks a condition in the body.

All three checks run in every build. A false condition raises a catchable fault with its clause name and function. A comparison failure can also report its operands.

Trident.tri
i32 halve(i32 n) require (n > 0, "positive") ensure (result <= n, "no larger") {
    assert (n % 2 == 0, "even")
    return n / 2
}
# the head breaks only inside a clause's
# parentheses: { opens on its last line
# a false clause raises a catchable `language` fault that names the
# clause and the function; `on error`
# catches it like any other fault. `result` is bound only inside ensure
C.c
#include <assert.h>
int halve(int n) {
    assert(n > 0);
    return n / 2;
}
// compiled out under NDEBUG; no postcondition form
C#.cs
int Halve(int n) {
    ArgumentOutOfRangeException
        .ThrowIfNegativeOrZero(n);
    Debug.Assert(n % 2 == 0);
    return n / 2;
}
// Debug.Assert vanishes in Release
C++.cpp
int halve(int n)
    pre (n > 0)
    post (r: r <= n) {
    contract_assert(n % 2 == 0);
    return n / 2;
}
// C++26 contract assertions; before
// that, assert() and NDEBUG
Cutlass.cut
i32 halve(i32 n) {
    intent (n > 0)
    assert (n % 2 == 0, "even")
    return n / 2
}
# no require or ensure clause: the head is the parameters, an optional fails
# bound, and {. intent states a fact,
# proved at compile or checked there, failing with fault.intent; a type
# parameter, i32<range=[1,100]> n,
# carries one with the value. assert runs in every build and fails with
# fault.assert, an error the caller
# handles at the call; debug assert is the debug-only form. Nothing names the result
Dart.dart
int halve(int n) {
  assert(n > 0, "positive");
  return n ~/ 2;
}
// assert runs only in debug mode
Elixir.ex
def halve(n) when n > 0, do: div(n, 2)
# a guard on the clause; a call that matches no clause raises FunctionClauseError. No postcondition
Go.go
func halve(n int) int {
    if n <= 0 {
        panic("positive")
    }
    return n / 2
}
// no assert in the language
Haskell.hs
import Control.Exception (assert)
halve :: Int -> Int
halve n = assert (n > 0) (n `div` 2)
-- erased under -O unless
-- -fno-ignore-asserts
Jai.jai
halve :: (n: int) -> int {
    assert(n > 0, "positive");
    return n / 2;
}
// #assert also runs at compile time
Java.java
int halve(int n) {
    assert n > 0 : "positive";
    return n / 2;
}
// off unless the JVM runs with -ea
JavaScript.js
function halve(n) {
  if (!(n > 0)) throw new RangeError("positive");
  return Math.trunc(n / 2);
}
// console.assert only logs
Julia.jl
function halve(n)
    @assert n > 0 "positive"
    n ÷ 2
end
Kotlin.kt
fun halve(n: Int): Int {
    require(n > 0) { "positive" }
    val r = n / 2
    check(r <= n) { "no larger" }
    return r
}
// require for arguments, check for state; both always run
Lua.lua
local function halve(n)
  assert(n > 0, "positive")
  return n // 2
end
Luau.luau
local function halve(n: number): number
    assert(n > 0, "positive")
    return n // 2
end
Nim.nim
proc halve(n: int): int =
  doAssert n > 0, "positive"
  n div 2
# assert is removed with -d:danger, doAssert is not
Objective-C.m
- (int)halve:(int)n {
    NSParameterAssert(n > 0);
    return n / 2;
}
// disabled by NS_BLOCK_ASSERTIONS
Perl.pl
sub halve {
    my ($n) = @_;
    croak "positive" unless $n > 0;
    return int($n / 2);
}
PHP.php
function halve(int $n): int {
    assert($n > 0, "positive");
    return intdiv($n, 2);
}
// zend.assertions = -1 removes it
Python.py
def halve(n: int) -> int:
    assert n > 0, "positive"
    return n // 2
# stripped under python -O
Ruby.rb
def halve(n)
  raise ArgumentError, "positive" unless n > 0
  n / 2
end
Rust.rs
fn halve(n: i32) -> i32 {
    assert!(n > 0, "positive");
    debug_assert!(n % 2 == 0);
    n / 2
}
// debug_assert! is gone in release
Scala.scala
def halve(n: Int): Int = {
  require(n > 0, "positive")
  n / 2
} ensuring (_ <= n)
// require and ensuring are library methods, always evaluated
Swift.swift
func halve(_ n: Int) -> Int {
    precondition(n > 0, "positive")
    assert(n % 2 == 0)
    return n / 2
}
// assert is removed in -O builds, precondition stays
TypeScript.ts
function halve(n: number): number {
  if (!(n > 0)) throw new RangeError("positive");
  return Math.trunc(n / 2);
}
// `asserts n is number` narrows a type; it does not check a value
Verse.verse
Halve(N:int)<decides>:int =
    N > 0
    N / 2
# a failable expression fails the whole call; no assert or message
Zig.zig
fn halve(n: i32) i32 {
    std.debug.assert(n > 0);
    return @divTrunc(n, 2);
}
// unreachable in ReleaseFast: an untaken assert is undefined
10

Arity & the parameter list

Every call supplies the exact parameter count and complete types. A wrong count refuses before any argument executes. Trident has no default parameter, overload, or variadic form.

A const parameter refuses a write. The signature fixes the call shape before the program runs.

Trident.tri
f64 volume(f64 w, f64 h, f64 d) {
    return w * h * d
}

f64 area(f64 w, f64 h) {
    return w * h
}

text main(text[] argv) {
    f64 box = volume(2.0, 3.0, 4.0)
    f64 flat = area(2.0, 3.0)
    return text(box) + " " + text(flat)
}
# volume(2.0, 3.0) refuses at compile:
# every parameter states its type, the count is exact, and there is no default, overload, or
# variadic form
C.c
double volume(double w, double h, double d);
volume(2, 3);   // compile error
printf("%f\n", 2.0, 3.0);
// variadic: the extra argument is silently unused
C#.cs
double Volume(double w, double h, double d = 1.0) => w*h*d;
double Volume(double w) => w*w*w;
Volume(2, 3);   // default fills d defaults, overloads, and `params`
C++.cpp
double volume(double w, double h, double d = 1.0);
double volume(double w);
volume(2, 3);   // default fills d defaults, overloads, variadic templates
Cutlass.cut
f64 volume(f64 w, f64 h, f64 d) {
    return w * h * d
}

f64 area(f64 w, f64 h) {
    return w * h
}

export void main() {
    f64 box = volume(2.0, 3.0, 4.0)
    f64 flat = area(2.0, 3.0)
    cut.host.write(`{box} {flat}\n`)
        else use 0
}
# volume(2.0, 3.0) refuses at compile,
# as in Trident: one signature, an exact count, and no default or variadic form. The entry takes no
# argv: cut.host.args() supplies it
Dart.dart
double volume(double w, double h, {double d = 1.0}) =>
    w * h * d;
volume(2, 3, d: 4);
// positional, named, and optional
Elixir.ex
def volume(w, h, d), do: w * h * d
def volume(w, h), do: w * h * 1
# volume/3 and volume/2 are two functions: the arity is part of the name
Go.go
func volume(w, h, d float64) float64
volume(2, 3)   // compile error
func sum(xs ...float64) float64
// fixed arity; no defaults or overloads; `...` is the one variadic form
Haskell.hs
volume :: Double -> Double -> Double
       -> Double
volume w h d = w * h * d
volume 2 3      -- a function awaiting d curried: a missing argument is a partial application, not
-- an
-- error
Jai.jai
volume :: (w: float, h: float, d: float = 1.0) -> float {
    return w * h * d;
}
volume(2, 3);   // default fills d defaults and named arguments
Java.java
double volume(double w, double h, double d)
double volume(double w, double h)
double sum(double... xs)
// overloads and varargs; no defaults. volume(2) is a
// compile error
JavaScript.js
function volume(w, h, d) {
  return w * h * d;
}
volume(2, 3);      // NaN: d is undefined
volume(2, 3, 4, 5); // 5 ignored
Julia.jl
volume(w, h, d=1.0) = w * h * d
volume(2, 3)     # optional d multiple dispatch: a new method per argument-type tuple
Kotlin.kt
fun volume(w: Double, h: Double, d: Double = 1.0) = w * h * d
volume(2.0, 3.0)
volume(h = 3.0, w = 2.0)
// defaults and named arguments
Lua.lua
local function volume(w, h, d)
  return w * h * d
end
volume(2, 3)   -- error at the *,
               -- d is nil
volume(2, 3, 4, 5) -- 5 dropped
Luau.luau
local function volume(w: number, h: number, d: number): number
  return w * h * d
end
volume(2, 3)  -- a type error under
              -- --!strict; the call
              -- still runs with nil
Nim.nim
proc volume(w, h: float, d = 1.0): float = w*h*d
proc volume(w: float): float = w*w*w
volume(2.0, 3.0)
# defaults, overloads, varargs
Objective-C.m
- (double)volumeWithWidth:(double)w
                   height:(double)h
                    depth:(double)d;
// the selector spells every argument; a different count is a different method name
Perl.pl
use feature 'signatures';
sub volume($w, $h, $d) { $w*$h*$d }
volume(2, 3);
# dies at runtime: "Too few arguments". Without signatures any count is accepted via @_
PHP.php
function volume(float $w, float $h, float $d = 1.0): float {
    return $w * $h * $d;
}
volume(2);
// ArgumentCountError at runtime; extra arguments are ignored
Python.py
def volume(w, h, d=1.0):
    return w * h * d
volume(2, 3)
volume(2)    # TypeError at runtime
def total(*xs, **opts): ...
Ruby.rb
def volume(w, h, d = 1.0) = w * h * d
volume(2, 3)
volume(2)   # ArgumentError at runtime
def total(*xs, **opts) = ...
Rust.rs
fn volume(w: f64, h: f64, d: f64)
    -> f64 { w * h * d }
volume(2.0, 3.0); // compile error
// fixed arity; no defaults or overloads; a macro like vec! is how variadic forms are written
Scala.scala
def volume(w: Double, h: Double, d: Double = 1.0) = w * h * d
def volume(w: Double) = w * w * w
def total(xs: Double*) = xs.sum
// defaults, named, overloads, *
Swift.swift
func volume(width w: Double, height h: Double, depth d: Double = 1) -> Double
volume(width: 2, height: 3)
func total(_ xs: Double...) -> Double
// labels, defaults, variadic
TypeScript.ts
function volume(w: number, h: number, d?: number): number {
  return w * h * (d ?? 1);
}
volume(2, 3);
volume(2, 3, 4, 5); // compile error
// optional `?` and `...rest`
Verse.verse
Volume(W:float, H:float, ?D:float = 1.0):float =
    W * H * D
Volume(2.0, 3.0, ?D := 4.0)
# a named optional parameter is passed with its name
Zig.zig
fn volume(w: f64, h: f64, d: f64) f64 {
    return w * h * d;
}
_ = volume(2, 3); // compile error
// fixed arity; no defaults or overloads; anytype for generics
11

Primitive & domain types

The current registry has 165 value kinds. Equal bytes do not mean equal types: guid is not u128, and dual is not dvec2.

Look up possible constraints for each type, including array layers and types with no additional leaf qualifiers.

The table records kind presence.

T?, T[], fixed arrays, structural records, and resource handles build complete types over the registry kinds. A parameter can carry a currency, unit family, time domain, frame, or color space.

Integers, the boolean, and the 128-bit identity

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
boolboolboolboolboolboolboolean (atom)boolBoolboolbooleanbooleanBoolBooleanbooleanbooleanboolBOOLscalar (truthy)boolbooltrue / falseboolBooleanBoolbooleanlogicbool
i8int8_tsbyteint8_ti8—integer (arbitrary)int8Int8s8byteInt8Array laneInt8Byte——int8int8_tpack c—numpy.int8—i8ByteInt8Int8Array lane—i8
u8uint8_tbyteuint8_t / std::byteu8—integer (arbitrary)uint8 / byteWord8u8—Uint8Array laneUInt8UByte——uint8 / byteuint8_tpack C—numpy.uint8—u8—UInt8Uint8Array lane—u8
i16int16_tshortint16_ti16—integer (arbitrary)int16Int16s16shortInt16Array laneInt16Short——int16int16_tpack s—numpy.int16—i16ShortInt16Int16Array lane—i16
u16uint16_tushortuint16_tu16—integer (arbitrary)uint16Word16u16char (unsigned 16-bit)Uint16Array laneUInt16UShort——uint16uint16_tpack S—numpy.uint16—u16—UInt16Uint16Array lane—u16
i32int32_tintint32_ti32int (64-bit)integer (arbitrary)int32Int32s32intnumber (binary64)Int32Intinteger subtype (64-bit)number (binary64)int32int32_tscalar (IV)int (64-bit)int (arbitrary)Integer (arbitrary)i32IntInt32number (binary64)int (arbitrary)i32
u32uint32_tuintuint32_tu32—integer (arbitrary)uint32Word32u32—Uint32Array laneUInt32UInt——uint32uint32_tscalar (UV)—numpy.uint32—u32—UInt32Uint32Array lane—u32
i64int64_tlongint64_ti64intinteger (arbitrary)int64Int64s64longBigInt (arbitrary)Int64Longinteger subtypenumber (binary64)int64int64_tscalar (IV, 64-bit build)intint (arbitrary)Integer (arbitrary)i64LongInt64bigint (arbitrary)int (arbitrary)i64
u64uint64_tulonguint64_tu64—integer (arbitrary)uint64Word64u64long + unsigned helpersBigInt.asUintN(64)UInt64ULong——uint64uint64_tscalar (UV)—numpy.uint64—u64—UInt64bigint + asUintN—u64
i128__int128 (GNU ext.)Int128 (.NET 7+)__int128 (compiler ext.)i128—integer (arbitrary)math/big.IntInteger (arbitrary)—BigIntegerBigInt.asIntN(128)Int128————__int128 (Clang ext.)Math::BigIntGMP ext.int (arbitrary)Integer (arbitrary)i128—Int128 (Swift 6)bigint + asIntNint (arbitrary)i128
u128unsigned __int128 (GNU ext.)UInt128 (.NET 7+)unsigned __int128 (ext.)u128—integer (arbitrary)math/big.IntNatural (arbitrary)—BigIntegerBigInt.asUintN(128)UInt128————unsigned __int128 (Clang ext.)Math::BigIntGMP ext.int (arbitrary)Integer (arbitrary)u128—UInt128 (Swift 6)bigint + asUintNint (arbitrary)u128
guid—System.Guidboost::uuids::uuidguiduuid pkgEcto.UUID (binary)google/uuid pkguuid pkg—java.util.UUIDrandomUUID() → stringBase.UUIDjava.util.UUID (JVM)——uuids pkgNSUUIDData::UUID (CPAN)ramsey/uuid pkguuid.UUIDSecureRandom.uuid → Stringuuid cratejava.util.UUID (JVM)UUID (Foundation)randomUUID() → string—std.crypto.random, by hand
sha256—byte[] from SHA256.HashData—sha256Digest (crypto pkg)binary from:crypto.hash[32]byte from sha256.Sum256Digest SHA256 (crypton pkg)—byte[] from MessageDigestArrayBuffer from crypto.subtleVector{UInt8} from SHA.sha256ByteArray (JVM MessageDigest)———unsigned char[32] from CC_SHA256string from Digest::SHAstring from hash()bytes from hashlibString from Digest::SHA256GenericArray (sha2 crate)Array[Byte] (JVM MessageDigest)SHA256.Digest (CryptoKit)ArrayBuffer from crypto.subtle—[32]u8 from std.crypto
pwhash—byte[] from Rfc2898DeriveBytes—pwhash—string (argon2_elixir pkg)[]byte from x/crypto/argon2——byte[] from SecretKeyFactoryArrayBuffer from crypto.subtle—ByteArray (JVM SecretKeyFactory)———NSData from CCKeyDerivationPBKDFstring from crypt()string from password_hash()bytes from hashlib.scryptString (bcrypt gem)PasswordHash (password-hash crate)Array[Byte] (JVM SecretKeyFactory)—ArrayBuffer from crypto.subtle—string from std.crypto.pwhash
ip<version>in6_addr (POSIX)System.Net.IPAddressboost::asio::ip::addressip<version>dart:io InternetAddress:inet.ip_address() tuplenet/netip.Addriproute IP—java.net.InetAddressipaddr.js pkgSockets.IPAddrjava.net.InetAddress (JVM)——std/net IpAddressin6_addr (POSIX)NetAddr::IPinet_pton byte stringipaddress.ip_addressIPAddrstd::net::IpAddrjava.net.InetAddress (JVM)Network IPv6Addressipaddr.js pkg—std.net.Address (with port)
ipprefix—System.Net.IPNetwork (.NET 8+)boost::asio::ip::network_v6ipprefix——net/netip.Prefixiproute AddrRange——ipaddr.js pkg——————NetAddr::IP—ipaddress.ip_networkIPAddr (masks host bits)ipnet crate——ipaddr.js pkg——
tree<T,...> / tree<K,T,...>Custom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapperCustom hierarchy and checked family wrapper
graph<V,E,...>Custom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapperCustom adjacency and checked graph wrapper

Floating point and the expansion carriers

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
f16_Float16 (C23)Halfstd::float16_t (C++23)f16—Nx tensor:f16—Numeric.Half (half pkg)——Float16Array laneFloat16———third-party pkg__fp16 (Clang ext.)——numpy.float16—f16 (nightly)—Float16Float16Array lane—f16
bf16__bf16 (compiler ext.)—std::bfloat16_t (C++23)bf16—Nx tensor:bf16—————BFloat16s.jl————__bf16 (Clang ext.)——ml_dtypes / torch.bfloat16—bf16 (half crate)—————
f32floatfloatfloatf32—Nx tensor:f32float32Floatfloat32floatMath.fround / Float32ArrayFloat32Float——float32float——numpy.float32—f32FloatFloatFloat32Array lane—f32
f64doubledoubledoublef64doublefloatfloat64Doublefloat64doublenumberFloat64Doublenumbernumberfloat64doublescalar (NV)floatfloatFloatf64DoubleDoublenumberfloatf64
dd128QD dd_real (lib)—QD / Boost.Multiprecisiondd128———————DoubleFloats.jl————QD dd_real (C lib)————twofloat crate—————
td192———td192———————MultiFloats.jl Float64x3———————————————
qd256QD qd_real (lib)—QD / Boost.Multiprecisionqd256———————MultiFloats.jl Float64x4————QD qd_real (C lib)——————————

Exact and unbounded numerics

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
decimal<scale>—decimaldecNumber / Boost.Multiprecisiondecimal<scale>decimal pkgDecimal libshopspring/decimalData.Fixed (Fixed E2)—BigDecimaldecimal.jsDecimals.jlBigDecimal (JVM)——decimal128 pkgNSDecimalNumberMath::BigFloatbcmath / decimal ext.decimal.DecimalBigDecimal (stdlib)rust_decimal crateBigDecimalDecimal (Foundation)decimal.js——
varintGMP mpzBigIntegerBoost.Multiprecision / GMPvarintBigIntintegermath/big.IntInteger—BigIntegerBigIntBigIntBigInteger (JVM)——bigints pkgGMP mpzMath::BigInt (core)GMP / bcmath ext.intIntegernum-bigint crateBigIntattaswift BigInt pkgbigintintstd.math.big.Int
rationalGMP mpq—Boost.Rationalrationalrational pkgratio libmath/big.RatRational——fraction.jsRational———Rational (std/rationals)GMP mpqMath::BigRat (core)GMP / brick-mathfractions.FractionRationalnum-rational crate——fraction.js——
exact———exact———————Irrational π (rounds on arithmetic)———————sympy.pi (library)———————
money<currency>—NodaMoney lib—money<currency>money2 pkgex_money libgo-money pkgsafe-money pkg—JavaMoney (JSR-354)dinero.jsCurrencies.jlJavaMoney (JVM)———NSDecimalNumber + NSLocaleMath::Currency (CPAN)moneyphp/moneypy-moneyed libmoney gemrusty-money cratesquants Money—dinero.js——
fxrate<base,quote,scale>—NodaMoney ExchangeRate—fxrate<base,quote,scale>money2 ExchangeRate——safe-money ExchangeRate—JavaMoney ExchangeRate (JSR-354)——JavaMoney (JVM)—————moneyphp CurrencyPair——rusty-money ExchangeRatesquants CurrencyExchangeRate————
fieldelem<modulus>OpenSSL BN_mod_* (untyped)BigInteger.ModPow (untyped)NTL ZZ_pfieldelem<modulus>——filippo.io/edwards25519/fieldfinite-field pkg—BigInteger.mod (untyped)@noble/curves FieldNemo.jl GF(p)BigInteger.mod (JVM, untyped)————Math::BigInt bmodpow (untyped)GMP gmp_powm (untyped)galois pkg GF(p)OpenSSL::BN mod_* (untyped)ff crate PrimeField——@noble/curves Field—std.crypto.ecc P256.Fe
curvepoint<curve>OpenSSL EC_POINTECPoint (System.Security.Cryptography)Crypto++ ECP::Pointcurvepoint<curve>pointycastle ECPoint—filippo.io/edwards25519 Pointcryptonite ECC Point—java.security.spec.ECPoint@noble/curves ProjectivePoint—ECPoint (JVM)——————cryptography EC public keysOpenSSL::PKey::EC::Pointp256 / curve25519-dalek pointsECPoint (JVM)CryptoKit public keys@noble/curves ProjectivePoint—std.crypto.ecc P256 / Edwards25519

Complex carriers and the derivative carrier

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
complex64float _Complex—std::complex<float>complex64—Nx tensor:c64complex64Complex Float———ComplexF32———Complex32 (std/complex)float _Complex——numpy.complex64—num-complex crate—swift-numerics Complex<Float>——std.math.Complex(f32)
complex128double _ComplexSystem.Numerics.Complexstd::complex<double>complex128—Complex libcomplex128Complex Double—Commons Math Complexcomplex.jsComplexF64———Complex64 (std/complex)double _ComplexMath::Complex (core)—complexComplexnum-complex cratespire / breezeswift-numerics Complex<Double>complex.js—std.math.Complex(f64)
dual——autodiff / CppADdual———ad package———ForwardDiff.jl Dual———————jax / autograd—num-dual cratespire Jet_Differentiation (experimental)———

Text, bytes, documents, and external bulk

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
textchar*stringstd::stringtextStringString.t / binarystringString / Data.TextstringStringstringStringStringstringstringstringNSString *string (scalar)stringstrStringString / &strStringStringstringstring[]const u8
runewchar_t / char32_tchar (UTF-16 unit) / Runechar32_truneint (runes)codepoint integerruneCharu21char (UTF-16 unit)code point numberCharChar——Runeunichar——str of length 1—charCharCharacter / Unicode.Scalarcode point number—u21
bufferunsigned char*byte[]std::byte[]bufferUint8Listbinary[]byteByteString[] u8byte[]Uint8ArrayVector{UInt8}ByteArraystringbufferseq[byte]NSData *string (scalar, bytes)stringbytesString (binary encoding)&[u8]Array[Byte][UInt8]Uint8Array—[]u8
chest———chest———————————————————————
stream—System.IO.Stream (an object)std::istreamstreamStream<T> (async sequence)Stream (lazy enumerable)io.Reader interfaceconduit / pipes pkg—InputStreamReadableStreamIOInputStream (JVM)file handle—Stream (std/streams)NSInputStreamfilehandlestream resourceio.IOBaseIOstd::io::ReadInputStream (JVM)InputStream / AsyncSequenceReadableStream—std.io.Reader
ciphertext—byte[] from AesGcm—ciphertextSecretBox (cryptography pkg)binary from:crypto AEAD[]byte from cipher.AEAD.SealByteString (crypton pkg)—byte[] from CipherArrayBuffer from crypto.subtle—ByteArray (JVM Cipher)———NSData from CCCryptstring (Crypt:: CPAN)string from sodium_crypto_aeadbytes (cryptography pkg)String from OpenSSL::CipherVec<u8> (aead crate)Array[Byte] (JVM Cipher)AES.GCM.SealedBox (CryptoKit)ArrayBuffer from crypto.subtle—[N]u8 from std.crypto.aead
outlinestruct (nominal)record / interface (nominal)struct (nominal)outlineclass (nominal)map with typespecstruct (nominal)record (nominal)struct (nominal)record / interface (nominal)object (untyped)NamedTuple (exact)data class (nominal)table (untyped)table type (structural)object (nominal)class (nominal)hash (untyped)array shape (static analyzers)Protocol / TypedDictStruct / Hash (untyped)struct (nominal)structural refinement typestruct (nominal)object type / interfacestruct (nominal)struct (nominal)
ingotstruct (nominal)record (nominal)struct (nominal)ingotclass (nominal)struct (nominal)struct (nominal)record (nominal)struct (nominal)record (nominal)—NamedTupledata class (nominal)—table type (open)tuple (structural)class (nominal)—readonly class (nominal)frozen dataclass (nominal)Data (nominal)struct (nominal)case class (nominal)struct (nominal)object type (open)struct (nominal)struct (nominal)

Time: one span, one instant, one range, three civil labels

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
durationmanualTimeSpanstd::chrono::durationdurationDurationDuration (calendar span)time.DurationNominalDiffTimeApollo_Time (femtoseconds)Durationms numberDates.Millisecondkotlin.time.Durationseconds numberseconds numberDuration (std/times)NSTimeInterval (double secs)Time::Seconds (core)DateIntervaltimedeltaActiveSupport gemstd::time::Durationscala.concurrent.durationDurationTemporal.Duration (TS 6+, ES2026)—i64 nanoseconds, manual
timepoint<domain>time_t / struct timespecDateTimeOffsetstd::chrono::time_point<Clock>timepoint<domain>DateTimeDateTimetime.TimeUTCTimeApollo_TimeInstantDate (ms since epoch)DateTimekotlinx.datetime.Instantos.time() numberos.time() numberTime (std/times)NSDateTime::Piece (core)DateTimeImmutabledatetime.datetimeTimeSystemTime / Instantjava.time.Instant (JVM)DateDate / Temporal.Instant—i64 timestamp
timerange<domain>———timerange<domain>Flutter DateTimeRange————ThreeTen-Extra Interval——OpenEndRange (a..<b) over kotlinx Instant——HSlice (a..<b)NSDateIntervalDateTime::Span (CPAN)DatePeriod—Range (a...b)Range<Instant> (a..b)—Range<Date> (a..<b)———
civil—DateTime (Kind=Unspecified)std::chrono::local_timecivil—NaiveDateTime—LocalTime (time pkg)—LocalDateTime / OffsetDateTimeTemporal.PlainDateTime (ES2026)DateTime (no zone)kotlinx.datetime.LocalDateTimeos.date tableos.date table—NSDateComponents——datetime (naive)—chrono NaiveDateTime cratejava.time.LocalDateTime (JVM)DateComponentsTemporal.PlainDateTime——
civildate—DateOnly (.NET 6+)std::chrono::year_month_daycivildate—Date—Day (time pkg)—LocalDateTemporal.PlainDate (ES2026)Datekotlinx.datetime.LocalDate———NSDateComponentsDateTime (CPAN)—datetime.dateDate (stdlib)chrono NaiveDate cratejava.time.LocalDate (JVM)DateComponentsTemporal.PlainDate——
civiltime—TimeOnly (.NET 6+)std::chrono::hh_mm_ssciviltime—Time—TimeOfDay (time pkg)—LocalTimeTemporal.PlainTime (ES2026)Timekotlinx.datetime.LocalTime———NSDateComponents——datetime.time—chrono NaiveTime cratejava.time.LocalTime (JVM)DateComponentsTemporal.PlainTime——

Units, measures, quantities, intervals

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
unit——Boost.Units (type-level)unitunits pkgex_cldr_units lib—units pkg (type-level)—JSR-385 Unit<Q>—Unitful.jl unitJSR-385 (JVM)——units pkgNSUnit (Foundation)Physics::Unit (CPAN)php-units-of-measure pkgpint.Unitruby-units gemuom (type-level)squants libUnit / Dimension (Foundation)safe-units lib——
measure——Boost.Units quantitymeasure—ex_cldr_units lib—units pkg—JSR-385 Quantity—Unitful.jlJSR-385 (JVM)——units pkg (Nimble)NSMeasurement<NSUnitMass>Physics::Unit (CPAN)php-units-of-measure pkgpint libruby-units gemuom cratesquants libMeasurement<UnitMass>safe-units / ts-units libs——
quantity<amount>——mp-units quantity<m, double>quantity<amount>———dimensional pkg—JSR-385 Quantity<Q>—Unitful Quantity{T}JSR-385 (JVM)——units pkg———pint Quantity—uom Quantity<D,U,V>squants lib—safe-units lib——
interval<family>——Boost.Intervalinterval<family>———intervals pkg———IntervalArithmetic.jl———————mpmath.iv / pyinterval—inari cratespire Interval————
measurement<family>———measurement<family>———uncertain pkg———Measurements.jl———measuremancer pkg—Number::WithError (CPAN)—uncertainties lib———————

Fixed-lane vectors

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
vec2—System.Numerics.Vector2glm::vec2 / Eigenvec2vector_math Vector2Nx tensorgo3d / gonum pkglinear pkg V2 FloatVector2 (Math module)JOML Vector2fgl-matrix vec2StaticArrays SVector{2,Float32}JOML (JVM)——vmath / glm pkgsimd_float2——numpy array (no fixed arity)—glam::Vec2 crate—SIMD2<Float>gl-matrix vec2—@Vector(2, f32)
vec3—System.Numerics.Vector3glm::vec3 / Eigenvec3vector_math Vector3Nx tensorgo3d / gonum pkglinear pkg V3 FloatVector3 (Math module)JOML Vector3fgl-matrix vec3StaticArrays SVector{3,Float32}JOML (JVM)—vector (native, 3 f32 lanes)vmath / glm pkgsimd_float3——numpy array (no fixed arity)—glam::Vec3 crate—SIMD3<Float>gl-matrix vec3vector3 (SpatialMath)@Vector(3, f32)
vec4—System.Numerics.Vector4glm::vec4 / Eigenvec4vector_math Vector4Nx tensorgo3d / gonum pkglinear pkg V4 FloatVector4 (Math module)JOML Vector4fgl-matrix vec4StaticArrays SVector{4,Float32}JOML (JVM)——vmath / glm pkgsimd_float4——numpy array (no fixed arity)—glam::Vec4 crate—SIMD4<Float>gl-matrix vec4—@Vector(4, f32)
dvec2——glm::dvec2 / Eigendvec2vector_math_64 Vector2Nx tensorgonum pkglinear pkg V2 Double—JOML Vector2d—StaticArrays SVector{2,Float64}JOML (JVM)——vmath / glm pkgsimd_double2——numpy array (no fixed arity)—glam::DVec2 crate—SIMD2<Double>——@Vector(2, f64)
dvec3——glm::dvec3 / Eigendvec3vector_math_64 Vector3Nx tensorgonum pkglinear pkg V3 Double—JOML Vector3d—StaticArrays SVector{3,Float64}JOML (JVM)——vmath / glm pkgsimd_double3——numpy array (no fixed arity)—glam::DVec3 crate—SIMD3<Double>——@Vector(3, f64)
dvec4——glm::dvec4 / Eigendvec4vector_math_64 Vector4Nx tensorgonum pkglinear pkg V4 Double—JOML Vector4d—StaticArrays SVector{4,Float64}JOML (JVM)——vmath / glm pkgsimd_double4——numpy array (no fixed arity)—glam::DVec4 crate—SIMD4<Double>——@Vector(4, f64)
hvec2——Eigen::Matrix<Eigen::half,2,1>hvec2—Nx tensor {:f, 16}—linear pkg V2 Half——Float16Array (no fixed arity)StaticArrays SVector{2,Float16}————simd_half2——numpy float16 array (no fixed arity)———SIMD2<Float16>Float16Array (no fixed arity)—@Vector(2, f16)
hvec3——Eigen::Matrix<Eigen::half,3,1>hvec3—Nx tensor {:f, 16}—linear pkg V3 Half——Float16Array (no fixed arity)StaticArrays SVector{3,Float16}————simd_half3——numpy float16 array (no fixed arity)———SIMD3<Float16>Float16Array (no fixed arity)—@Vector(3, f16)
hvec4——Eigen::Matrix<Eigen::half,4,1>hvec4—Nx tensor {:f, 16}—linear pkg V4 Half——Float16Array (no fixed arity)StaticArrays SVector{4,Float16}————simd_half4——numpy float16 array (no fixed arity)———SIMD4<Float16>Float16Array (no fixed arity)—@Vector(4, f16)
ivec2——glm::ivec2ivec2—Nx tensor—linear pkg V2 Int32—JOML Vector2i—StaticArrays SVector{2,Int32}JOML (JVM)——vmath / glm pkgsimd_int2——numpy array (no fixed arity)—glam::IVec2 crate—SIMD2<Int32>——@Vector(2, i32)
ivec3——glm::ivec3ivec3—Nx tensor—linear pkg V3 Int32—JOML Vector3i—StaticArrays SVector{3,Int32}JOML (JVM)——vmath / glm pkgsimd_int3——numpy array (no fixed arity)—glam::IVec3 crate—SIMD3<Int32>——@Vector(3, i32)
ivec4——glm::ivec4ivec4—Nx tensor—linear pkg V4 Int32—JOML Vector4i—StaticArrays SVector{4,Int32}JOML (JVM)——vmath / glm pkgsimd_int4——numpy array (no fixed arity)—glam::IVec4 crate—SIMD4<Int32>——@Vector(4, i32)
uvec2——glm::uvec2uvec2—Nx tensor—linear pkg V2 Word32———StaticArrays SVector{2,UInt32}———vmath / glm pkgsimd_uint2——numpy array (no fixed arity)—glam::UVec2 crate—SIMD2<UInt32>——@Vector(2, u32)
uvec3——glm::uvec3uvec3—Nx tensor—linear pkg V3 Word32———StaticArrays SVector{3,UInt32}———vmath / glm pkgsimd_uint3——numpy array (no fixed arity)—glam::UVec3 crate—SIMD3<UInt32>——@Vector(3, u32)
uvec4——glm::uvec4uvec4—Nx tensor—linear pkg V4 Word32———StaticArrays SVector{4,UInt32}———vmath / glm pkgsimd_uint4——numpy array (no fixed arity)—glam::UVec4 crate—SIMD4<UInt32>——@Vector(4, u32)
bvec2——glm::bvec2bvec2—Nx tensor—linear pkg V2 Bool———StaticArrays SVector{2,Bool}———vmath / glm pkgsimd_int2 mask——numpy array (no fixed arity)—glam::BVec2 crate—SIMDMask<SIMD2<Int32>>——@Vector(2, bool)
bvec3——glm::bvec3bvec3—Nx tensor—linear pkg V3 Bool———StaticArrays SVector{3,Bool}———vmath / glm pkgsimd_int3 mask——numpy array (no fixed arity)—glam::BVec3 crate—SIMDMask<SIMD3<Int32>>——@Vector(3, bool)
bvec4——glm::bvec4bvec4—Nx tensor—linear pkg V4 Bool———StaticArrays SVector{4,Bool}———vmath / glm pkgsimd_int4 mask——numpy array (no fixed arity)—glam::BVec4 crate—SIMDMask<SIMD4<Int32>>——@Vector(4, bool)

Matrices and rotations

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
mat2——glm::mat2 / Eigen::Matrix2fmat2vector_math Matrix2Nx tensorgonum matlinear pkg M22—JOML Matrix2fgl-matrix mat2StaticArrays SMatrix{2,2,Float32}JOML (JVM)——vmath / glm pkgsimd_float2x2——numpy array (no fixed shape)—glam::Mat2 crate—simd_float2x2gl-matrix mat2——
mat3——glm::mat3 / Eigen::Matrix3fmat3vector_math Matrix3Nx tensorgonum matlinear pkg M33Matrix3 (Math module)JOML Matrix3fgl-matrix mat3StaticArrays SMatrix{3,3,Float32}JOML (JVM)——vmath / glm pkgsimd_float3x3——numpy array (no fixed shape)—glam::Mat3 crate—simd_float3x3gl-matrix mat3——
mat2x3—System.Numerics.Matrix3x2glm::mat3x2mat2x3—Nx tensor—linear pkg M23—JOML Matrix3x2fgl-matrix mat2dStaticArrays SMatrix{2,3,Float32}JOML (JVM)——glm pkgsimd_float3x2——numpy array (no fixed shape)—glam::Affine2 crate—simd_float3x2gl-matrix mat2d——
mat3x4——glm::mat4x3mat3x4—Nx tensor—linear pkg M34—JOML Matrix4x3f—StaticArrays SMatrix{3,4,Float32}JOML (JVM)——glm pkgsimd_float4x3——numpy array (no fixed shape)———simd_float4x3———
mat4—System.Numerics.Matrix4x4glm::mat4 / Eigen::Matrix4fmat4vector_math Matrix4Nx tensorgonum matlinear pkg M44Matrix4 (Math module)JOML Matrix4fgl-matrix mat4StaticArrays SMatrix{4,4,Float32}JOML (JVM)——vmath / glm pkgsimd_float4x4——numpy array (no fixed shape)—glam::Mat4 crate—simd_float4x4gl-matrix mat4——
dmat2——glm::dmat2 / Eigen::Matrix2ddmat2vector_math_64 Matrix2Nx tensorgonum matlinear pkg M22 Double—JOML Matrix2d—StaticArrays SMatrix{2,2,Float64}JOML (JVM)——glm pkgsimd_double2x2——numpy array (no fixed shape)—glam::DMat2 crate—simd_double2x2———
dmat3——glm::dmat3 / Eigen::Matrix3ddmat3vector_math_64 Matrix3Nx tensorgonum matlinear pkg M33 Double—JOML Matrix3d—StaticArrays SMatrix{3,3,Float64}JOML (JVM)——glm pkgsimd_double3x3——numpy array (no fixed shape)—glam::DMat3 crate—simd_double3x3———
dmat2x3——glm::dmat3x2dmat2x3—Nx tensor—linear pkg M23 Double—JOML Matrix3x2d—StaticArrays SMatrix{2,3,Float64}JOML (JVM)——glm pkgCGAffineTransform / simd_double3x2——numpy array (no fixed shape)—glam::DAffine2 crate—CGAffineTransform / simd_double3x2———
dmat3x4——glm::dmat4x3dmat3x4—Nx tensor—linear pkg M34 Double—JOML Matrix4x3d—StaticArrays SMatrix{3,4,Float64}JOML (JVM)——glm pkgsimd_double4x3——numpy array (no fixed shape)———simd_double4x3———
dmat4——glm::dmat4 / Eigen::Matrix4ddmat4vector_math_64 Matrix4Nx tensorgonum matlinear pkg M44 Double—JOML Matrix4d—StaticArrays SMatrix{4,4,Float64}JOML (JVM)——glm pkgsimd_double4x4——numpy array (no fixed shape)—glam::DMat4 crate—simd_double4x4———
quat—System.Numerics.Quaternionglm::quat / Eigen::Quaternionfquatvector_math Quaternion—go3d quaternionlinear pkg QuaternionQuaternion (Math module)JOML Quaternionfgl-matrix quatQuaternions.jlJOML (JVM)——vmath / glm pkgsimd_quatf——scipy Rotation / numpy-quaternion—glam::Quat cratespire / breezesimd_quatfgl-matrix quatrotation (SpatialMath)—
dquat——glm::dquat / Eigen::Quaternionddquatvector_math_64 Quaternion——linear pkg Quaternion Double—JOML Quaterniond—Quaternions.jlJOML (JVM)——glm pkgsimd_quatd——scipy Rotation—glam::DQuat crate—simd_quatd———
rotor<signature>——klein motor (lib)rotor<signature>——————ganja.js (lib)Grassmann.jl Spinor———————clifford / kingdon—ultraviolet Rotor3 (crate)——ganja.js (lib)——
bivector<signature>——klein line (lib)bivector<signature>——————ganja.js (lib)Grassmann.jl Chain{V,2}———————clifford / kingdon—ultraviolet Bivec3 (crate)——ganja.js (lib)——
multivector<signature>——klein multivector (lib)multivector<signature>——————ganja.js (lib)Grassmann.jl Multivector———————clifford / kingdon—ultraviolet multivector (crate)——ganja.js (lib)——

Bounds, positions, rigid motions, spatial roles, spatial states, planar roles, geometric objects, and discrete global grids

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
aabb——Eigen::AlignedBox3faabbvector_math Aabb3————JOML AABBfthree.js Box3GeometryBasics.RectJOML (JVM)——vmath pkg———shapely bounds—parry / bevy Aabb crate——three.js Box3——
daabb——Eigen::AlignedBox3ddaabbvector_math_64 Aabb3—golang/geo Rect——JOML AABBd—GeometryBasics.RectJOML (JVM)——————shapely bounds—parry crate—————
geodetic<datum>——GeographicLibgeodetic<datum>latlong2 pkggeo libgolang/geogeodetics pkg—GeoTools / JTSturf.js / geolibGeodesy.jlGeoTools (JVM)———CLLocationCoordinate2DGeo::Ellipsoid (CPAN)geotools pkgpyproj / geopygeokit gemgeo crateGeoTools (JVM)CLLocationCoordinate2Dturf.js——
cellid<grid>H3 C libraryH3.nets2geometry / H3cellid<grid>h3_dart pkgh3 libuber/h3-go, geo s2h3 bindings—com.uber.h3 / s2-geometryh3-jsH3.jlh3 (JVM)———H3 (C library)Geo::Hash (CPAN)h3 ext.h3-py / s2sphereh3 gemh3o crateh3 (JVM)H3 (C library)h3-js——
frameidCSPICE frame name string—tf2 frame_id stringframeid—————Orekit Frame—SatelliteToolbox framesOrekit Frame (JVM)——————astropy frame classes——Orekit Frame (JVM)————
pose<from,to>——Eigen::Isometry3f / Drake RigidTransformpose<from,to>—————Orekit Transform—CoordinateTransformations AffineMapOrekit Transform (JVM)——————spatialmath SE3—nalgebra Isometry3Orekit Transform (JVM)————
dpose<from,to>——Eigen::Isometry3d / Drake RigidTransformddpose<from,to>—————Orekit Transform—CoordinateTransformations AffineMapOrekit Transform (JVM)——————spatialmath SE3 / pydrake RigidTransform—nalgebra Isometry3<f64>Orekit Transform (JVM)————
twist<moving,relative,frame>——ROS geometry_msgs Twist / Drake SpatialVelocitytwist<moving,relative,frame>———————————————spatialmath Twist3———————
dtwist<moving,relative,frame>——ROS geometry_msgs Twist / Drake SpatialVelocitydtwist<moving,relative,frame>———————————————spatialmath Twist3 / pydrake SpatialVelocity———————
wrench<frame>——ROS geometry_msgs Wrench / Drake SpatialForcewrench<frame>———————————————————————
dwrench<frame>——ROS geometry_msgs Wrench / Drake SpatialForcedwrench<frame>———————————————pydrake SpatialForce———————
point3<frame>——ROS geometry_msgs Point32point3<frame>———————————————sympy.physics.vector Point—nalgebra Point3—————
dpoint3<frame>——ROS geometry_msgs PointStampeddpoint3<frame>———————————————sympy.physics.vector Point—nalgebra Point3<f64>—————
dir3<frame>——ROS geometry_msgs Vector3dir3<frame>———————————————sympy.physics.vector Vector—nalgebra Vector3—————
ddir3<frame>——ROS geometry_msgs Vector3Stampedddir3<frame>———————————————sympy.physics.vector Vector—nalgebra Vector3<f64>—————
normal3<frame>——pbrt Normal3fnormal3<frame>—————————————————nalgebra Unit<Vector3>—————
dnormal3<frame>——pbrt Normal3<double>dnormal3<frame>—————————————————nalgebra Unit<Vector3<f64>>—————
state3<frame,domain>——ROS nav_msgs Odometry (binary64 only)state3<frame,domain>———————————————————————
dstate3<frame,domain>CSPICE state vector (epoch apart)—ROS nav_msgs Odometrydstate3<frame,domain>—————Orekit TimeStampedPVCoordinates—SatelliteToolbox OrbitStateVector———————astropy CartesianRepresentation + obstime———————
point2<frame>———point2<frame>—————————————————nalgebra Point2—————
dpoint2<frame>———dpoint2<frame>—————————————————nalgebra Point2<f64>—————
dir2<frame>———dir2<frame>—————————————————nalgebra Vector2—————
ddir2<frame>———ddir2<frame>—————————————————nalgebra Vector2<f64>—————
normal2<frame>———normal2<frame>———————————————————————
dnormal2<frame>———dnormal2<frame>———————————————————————
ray<frame>——Eigen::ParametrizedLine (lib)ray<frame>——————three.js Ray (lib)———Ray (Roblox)——————parry3d Ray (crate)—————
dray<frame>——Eigen::ParametrizedLine<double,3> (lib)dray<frame>—————————————————parry3d-f64 Ray (crate)—————
plane<frame>—System.Numerics.PlaneEigen::Hyperplane (lib)plane<frame>——————three.js Plane (lib)——————————parry3d HalfSpace (crate)—————
dplane<frame>——Eigen::Hyperplane<double,3> (lib)dplane<frame>—————————————————parry3d-f64 HalfSpace (crate)—————
sphere<frame>———sphere<frame>——————three.js Sphere (lib)——————————parry3d Ball (crate)—————
dsphere<frame>———dsphere<frame>—————————————————parry3d-f64 Ball (crate)—————
capsule<frame>———capsule<frame>—————————————————parry3d Capsule (crate)—————
dcapsule<frame>———dcapsule<frame>—————————————————parry3d-f64 Capsule (crate)—————
obb<frame>———obb<frame>——————three.js OBB (addon)——————————parry3d Cuboid + Isometry (crate)—————
dobb<frame>———dobb<frame>—————————————————parry3d-f64 Cuboid + Isometry (crate)—————
circle<frame>———circle<frame>—————————————————parry2d Ball (crate)—————
dcircle<frame>———dcircle<frame>—————————————————parry2d-f64 Ball (crate)—————
rect<frame>———rect<frame>—————————————————parry2d Aabb (crate)—————
drect<frame>———drect<frame>—————————————————parry2d-f64 Aabb (crate)—————
obb2<frame>———obb2<frame>—————————————————parry2d Cuboid + Isometry (crate)—————
dobb2<frame>———dobb2<frame>—————————————————parry2d-f64 Cuboid + Isometry (crate)—————
ellipse<frame>———ellipse<frame>—————————————————parry2d ellipse support-map recipe (crate)—————
dellipse<frame>———dellipse<frame>—————————————————parry2d-f64 ellipse support-map recipe (crate)—————
capsule2<frame>———capsule2<frame>—————————————————parry2d Capsule (crate)—————
dcapsule2<frame>———dcapsule2<frame>—————————————————parry2d-f64 Capsule (crate)—————
segment2<frame>———segment2<frame>—————————————————parry2d Segment (crate)—————
dsegment2<frame>———dsegment2<frame>—————————————————parry2d-f64 Segment (crate)—————
ray2<frame>——Eigen::ParametrizedLine<float,2> (lib)ray2<frame>—————————————————parry2d Ray (crate)—————
dray2<frame>——Eigen::ParametrizedLine<double,2> (lib)dray2<frame>—————————————————parry2d-f64 Ray (crate)—————
line2<frame>——Eigen::Hyperplane<float,2> (lib)line2<frame>—————————————————parry2d HalfSpace (crate)—————
dline2<frame>——Eigen::Hyperplane<double,2> (lib)dline2<frame>—————————————————parry2d-f64 HalfSpace (crate)—————
triangle2<frame>———triangle2<frame>—————————————————parry2d Triangle (crate)—————
dtriangle2<frame>———dtriangle2<frame>—————————————————parry2d-f64 Triangle (crate)—————
segment3<frame>———segment3<frame>——————three.js Line3 (lib)——————————parry3d Segment (crate)—————
dsegment3<frame>———dsegment3<frame>—————————————————parry3d-f64 Segment (crate)—————
line3<frame>——Eigen::ParametrizedLine<float,3> (lib)line3<frame>———————————————————————
dline3<frame>——Eigen::ParametrizedLine<double,3> (lib)dline3<frame>———————————————————————
triangle3<frame>———triangle3<frame>——————three.js Triangle (lib)——————————parry3d Triangle (crate)—————
dtriangle3<frame>———dtriangle3<frame>—————————————————parry3d-f64 Triangle (crate)—————
cylinder<frame>———cylinder<frame>—————————————————parry3d Cylinder + Isometry (crate)—————
dcylinder<frame>———dcylinder<frame>—————————————————parry3d-f64 Cylinder + Isometry (crate)—————
cone<frame>———cone<frame>—————————————————parry3d Cone + Isometry (crate)—————
dcone<frame>———dcone<frame>—————————————————parry3d-f64 Cone + Isometry (crate)—————
conicalfrustum<frame>———conicalfrustum<frame>———————————————————————
dconicalfrustum<frame>———dconicalfrustum<frame>———————————————————————
ellipsoid<frame>———ellipsoid<frame>———————————————————————
dellipsoid<frame>———dellipsoid<frame>———————————————————————
torus<frame>———torus<frame>———————————————————————
dtorus<frame>———dtorus<frame>———————————————————————
coord2<frame?>———coord2<frame?>———————————————————————
dcoord2<frame?>———dcoord2<frame?>———————————————————————
coord3<frame?>———coord3<frame?>———————————————————————
dcoord3<frame?>———dcoord3<frame?>———————————————————————

Aggregates and closed domains

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
color<space>—System.Drawing.Color (sRGB only)—color<space>dart:ui Color (sRGB)—image/color.Colorcolour pkg—java.awt.Color + ColorSpaceCSS color stringColors.jl Colorantandroid.graphics.Color / AWT—Color3 (Roblox)chroma pkgNSColor / UIColor——colour-science / PIL—palette cratejava.awt.Color (JVM)Color / CGColorculori / chroma-js——
tensor<dtype>—System.Numerics.Tensors (preview)Eigen::Tensor / xtensortensor<dtype>—Nx.Tensorgorgonia/tensorhmatrix / massiv—ND4J / TensorFlow Javatf.js Tensor / ndarrayArray{Float32,3}multik / ND4J——arraymancer pkgMLMultiArray (Core ML)PDL (CPAN)—numpy.ndarray / torch.Tensornumo-narray gemndarray cratebreeze / ND4JMLMultiArray / MLXtf.js Tensor——
field<dtype>———field<dtype>———————DimensionalData.jl (pkg)———————xarray.DataArray (lib)———————
enum<domain>enumenumenum classenum (declared width)enumatoms (open set)iota consts (no enum type)data (Enum, Bounded)enumenumobject literal / string union@enumenum class—string union typeenumNS_ENUM—enum (8.1+)enum.Enumsymbols / constantsenumenum (Scala 3)enumenumenumenum
flagset<domain>bit-mask #define / enum[Flags] enumstd::bitset / bitmask enumflagset (declared width)int bit maskBitwise on integersiota bit consts (no set type)Data.Bits / enumset pkgenum_flagsEnumSetnumber bit maskBitFlags.jlEnumSet (JVM)——set[Enum]NS_OPTIONS——enum.Flag—bitflags crateEnumSet (JVM) / BitSetOptionSetnumeric enum as bit mask—std.EnumSet
almanac<domain>integer id + static tableenum + attribute metadataenum class + constexpr tablealmanacenhanced enum (fields, no stable id)—int id + map——enum with fields (ordinal not stable)——enum class with properties—————backed enum with methodsEnum with value tuples—enum + const tableenum with parametersenum with raw values———

Present as a distinct type , present only through a package or as a bare number , absent .

Constraints: rules that travel with a value's type

Defined contracts: scalar and array rules, write preparation, dictionary rules, trees, and graphs. This section describes those rules, independent of a particular compiler build.

A value constraint limits which values an existing type admits. A probability can use f64<range=[0,1]>. The value is still one number; the range travels with its complete type. A constraint adds no new semantic identity. Named types answer that separate question.

A require clause checks a condition at one function's entry. A constrained parameter, result, field, or local checks the value whenever that destination receives it. Function contracts and value constraints can work together.

Trident: a bounded number.tri
f64 half(f64<range=[0,1]> p) {
    return p / 2.0
}

f64 input = 0.5
f64<range=[0,1]> chance = input
f64 result = half(chance)
# half(3.0) faults at the argument.
# chance = 2.0 faults before publication.
# The previous chance stays intact.

Admitted value; argument refusal.

Trident: each layer has its own rule.tri
text<length=[1,80],bytes=[1,256]> caption = "Hello"

# Each number is in [0,1].
# This array has between 1 and 64 elements.
f64<range=[0,1]>[]<length=[1,64]> weights = [0.25, 0.75]

# Source writes sort and remove duplicates.
i32[]<distinct,sort> ids = [3, 1, 3]
# ids holds [1, 3].

Leaf and layer rules; array write policies.

Trident: an explicit write policy.tri
i32<range=[0,100],outside=clamp> score = 120
# score holds 100.

decimal<scale=2,multiple=0.05,snap=nearestEven> price = 1.23
# price holds 1.25.

# Without these policies, an excluded value refuses. A received value must already satisfy the
# rules; admission never repairs it.

Clamp case; grid and snap rules.

Possible constraints by type

This hand-written catalog lists every registered value kind, grouped by shared rules. Open a family to see each type. Value constraints describe admitted values; source-write policies prepare a candidate before publication. Existing semantic parameters, such as currency, units, frames, and domains, keep their own rules.

Integers, decimals, and rational numbers

A range uses finite exact bounds: [a,b], [a,b), (a,b] or (a,b). A positive exact step starts its grid at zero. A nondefault snap needs multiple; a nondefault outside needs range. Both policies default to reject. The whole range/grid intersection must contain a representable value. These carriers already exclude nonfinite values, so finite=true refuses. A decimal also requires its existing scale parameter.

Integers, decimals, and rational numbers
TypeValue constraintsSource-write policies
i8range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
u8range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
i16range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
u16range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
i32range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
u32range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
i64range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
u64range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
i128range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
u128range=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
decimalrange=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
varintrange=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
rationalrange=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
decimal<scale=2,range=[0,100],multiple=0.05,snap=nearestEven> price = 1.23

Read the owning rules.

Floating-point numbers

A range uses finite exact bounds: [a,b], [a,b), (a,b] or (a,b). A positive exact step starts its grid at zero. A nondefault snap needs multiple; a nondefault outside needs range. Both policies default to reject. The whole range/grid intersection must contain a representable value. A range already excludes infinity and NaN. finite=true excludes them without a declared bound. Grid membership is exact, with no tolerance.

Floating-point numbers
TypeValue constraintsSource-write policies
f16range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
bf16range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
f32range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
f64range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
dd128range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
td192range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
qd256range=interval, multiple=step, finite=truesnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
f64<finite=true> sample = 0.25
f64<range=[0,1]> probability = 0.75

Read the owning rules.

Complex and dual numbers

Every component must be finite. These types take no scalar range, multiple, snap or outside. A complex value has two components; a dual value has a value and a tangent.

Complex and dual numbers
TypeValue constraintsSource-write policies
complex64finite=trueNone.
complex128finite=trueNone.
dualfinite=trueNone.
complex128<finite=true> result = sourceResult

Read the owning rules.

Enum members

A range follows the enum's signed i32 element order. Its declared domain remains part of the type. This does not add numeric grids or clamp policies to enums. A source declaration uses its enum's name.

Enum members
TypeValue constraintsSource-write policies
enumrange=intervalNone.

Read the owning rules.

Text

class selects the existing text repertoire or grammar. length counts Unicode scalars, bytes counts UTF-8 bytes, and graphemes counts extended grapheme clusters under UAX #29. Count bounds use nonnegative integers. full needs a declared maximum when it removes text. Single-line text refuses newline conversion. The runtime derives the Unicode edition; it is no author option. Defaults preserve text and allow content.

Text
TypeValue constraintsSource-write policies
textclass=name, length=interval, bytes=interval, graphemes=interval, nul=allow|reject, lines=multi|single, controls=allow|reject, exclude=[U+002F,U+005C]trim=none|start|end|both, space=preserve|collapse, normalize=preserve|nfc|nfd|nfkc|nfkd, newlines=preserve|lf|crlf|cr, full=reject|dropFirst|dropLast
text<length=[1,80],bytes=[1,256],lines=single,normalize=nfc,trim=both> caption = "Hello"

Read the owning rules.

Byte buffers

Both rules count bytes. Nondefault full needs a maximum length. The complete input must be block-aligned before any removal. Overflow keeps whole blocks and never pads the buffer.

Byte buffers
TypeValue constraintsSource-write policies
bufferlength=interval, lengthMultiple=positiveIntegerfull=reject|dropFirst|dropLast
buffer<length=[16,4096],lengthMultiple=16,full=dropLast> blocks = sourceBytes

Read the owning rules.

Durations and instants

Bounds and grid steps measure exact seconds. A timepoint measures from its declared domain's epoch and keeps that domain. It requires a time-domain parameter; payload scale is not a type option. These carriers take no finite=true. A range uses finite exact bounds: [a,b], [a,b), (a,b] or (a,b). A positive exact step starts its grid at zero. A nondefault snap needs multiple; a nondefault outside needs range. Both policies default to reject. The whole range/grid intersection must contain a representable value.

Durations and instants
TypeValue constraintsSource-write policies
durationrange=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
timepointrange=interval, multiple=stepsnap=reject|floor|ceil|nearestEven, outside=reject|clampLow|clampHigh|clamp
duration<range=[0,30],multiple=0.001,snap=ceil> delay = sourceDelay
timepoint<tai,range=[0,86400),outside=clamp> instant = sourceInstant

Read the owning rules.

Tensors

Every rule except shape requires dtype. finite needs a dtype that admits nonfinite values. laneRange needs ordered numeric lanes, excluding Boolean and complex lanes. nonnegative also excludes unsigned dtypes, where it would repeat an intrinsic rule. structure needs a numeric, non-Boolean dtype and a rank-two shape; the supplied matrix must be square. * leaves that axis open. Tensor shape differs from an enclosing array's size.

Tensors
TypeValue constraintsSource-write policies
tensorshape=[extent,*,...], finite=true, laneRange=interval, nonnegative=true, structure=symmetric|lowerTriangular|upperTriangularNone. Admission never reshapes or repairs a tensor.
tensor<dtype=f32,shape=[*,3],finite=true> positions = sourcePositions
tensor<dtype=f64,shape=[*,*],structure=symmetric> covariance = sourceCovariance

Read the owning rules.

Records and documents

An outline, an ingot or a trove declaration can state field order, bounds, matches, presence, absence, shared quantity contracts, families, finiteness, and count relations. An optional selector guard limits when a clause applies. A bare chest has no declared field set or record checks of its own. These are static clause forms; arbitrary Boolean programs and callbacks refuse.

Records and documents
TypeValue constraintsSource-write policies
chestField types carry their own constraints. Named record declarations can add closed check clauses.Each field follows its declared write policies before the complete record passes its checks.
outlineField types carry their own constraints. Named record declarations can add closed check clauses.Each field follows its declared write policies before the complete record passes its checks.
ingotField types carry their own constraints. Named record declarations can add closed check clauses.Each field follows its declared write policies before the complete record passes its checks.
outline Window {
    i64 start = 0
    i64 end = 0
    check ordered = start <= end
}

Read the owning rules.

Vectors, matrices, and geometry

Kind-specific validity and declared units, families, frames or signatures still apply. A scalar range or tensor lane rule does not automatically apply to a vector, matrix, quaternion or geometric shape. Use the type's operations or an eligible record check for the required invariant.

Vectors, matrices, and geometry
TypeValue constraintsSource-write policies
vec2No additional leaf qualifier from this catalog.None from this catalog.
vec3No additional leaf qualifier from this catalog.None from this catalog.
vec4No additional leaf qualifier from this catalog.None from this catalog.
dvec2No additional leaf qualifier from this catalog.None from this catalog.
dvec3No additional leaf qualifier from this catalog.None from this catalog.
dvec4No additional leaf qualifier from this catalog.None from this catalog.
quatNo additional leaf qualifier from this catalog.None from this catalog.
mat2No additional leaf qualifier from this catalog.None from this catalog.
mat3No additional leaf qualifier from this catalog.None from this catalog.
mat3x4No additional leaf qualifier from this catalog.None from this catalog.
mat4No additional leaf qualifier from this catalog.None from this catalog.
aabbNo additional leaf qualifier from this catalog.None from this catalog.
ivec2No additional leaf qualifier from this catalog.None from this catalog.
ivec3No additional leaf qualifier from this catalog.None from this catalog.
ivec4No additional leaf qualifier from this catalog.None from this catalog.
uvec2No additional leaf qualifier from this catalog.None from this catalog.
uvec3No additional leaf qualifier from this catalog.None from this catalog.
uvec4No additional leaf qualifier from this catalog.None from this catalog.
bvec2No additional leaf qualifier from this catalog.None from this catalog.
bvec3No additional leaf qualifier from this catalog.None from this catalog.
bvec4No additional leaf qualifier from this catalog.None from this catalog.
dquatNo additional leaf qualifier from this catalog.None from this catalog.
daabbNo additional leaf qualifier from this catalog.None from this catalog.
dmat2No additional leaf qualifier from this catalog.None from this catalog.
dmat3No additional leaf qualifier from this catalog.None from this catalog.
dmat3x4No additional leaf qualifier from this catalog.None from this catalog.
dmat4No additional leaf qualifier from this catalog.None from this catalog.
rotorNo additional leaf qualifier from this catalog.None from this catalog.
bivectorNo additional leaf qualifier from this catalog.None from this catalog.
mat2x3No additional leaf qualifier from this catalog.None from this catalog.
dmat2x3No additional leaf qualifier from this catalog.None from this catalog.
hvec2No additional leaf qualifier from this catalog.None from this catalog.
hvec3No additional leaf qualifier from this catalog.None from this catalog.
hvec4No additional leaf qualifier from this catalog.None from this catalog.
multivectorNo additional leaf qualifier from this catalog.None from this catalog.
poseNo additional leaf qualifier from this catalog.None from this catalog.
dposeNo additional leaf qualifier from this catalog.None from this catalog.
twistNo additional leaf qualifier from this catalog.None from this catalog.
dtwistNo additional leaf qualifier from this catalog.None from this catalog.
wrenchNo additional leaf qualifier from this catalog.None from this catalog.
dwrenchNo additional leaf qualifier from this catalog.None from this catalog.
point3No additional leaf qualifier from this catalog.None from this catalog.
dpoint3No additional leaf qualifier from this catalog.None from this catalog.
dir3No additional leaf qualifier from this catalog.None from this catalog.
ddir3No additional leaf qualifier from this catalog.None from this catalog.
normal3No additional leaf qualifier from this catalog.None from this catalog.
dnormal3No additional leaf qualifier from this catalog.None from this catalog.
state3No additional leaf qualifier from this catalog.None from this catalog.
dstate3No additional leaf qualifier from this catalog.None from this catalog.
point2No additional leaf qualifier from this catalog.None from this catalog.
dpoint2No additional leaf qualifier from this catalog.None from this catalog.
dir2No additional leaf qualifier from this catalog.None from this catalog.
ddir2No additional leaf qualifier from this catalog.None from this catalog.
normal2No additional leaf qualifier from this catalog.None from this catalog.
dnormal2No additional leaf qualifier from this catalog.None from this catalog.
rayNo additional leaf qualifier from this catalog.None from this catalog.
drayNo additional leaf qualifier from this catalog.None from this catalog.
planeNo additional leaf qualifier from this catalog.None from this catalog.
dplaneNo additional leaf qualifier from this catalog.None from this catalog.
sphereNo additional leaf qualifier from this catalog.None from this catalog.
dsphereNo additional leaf qualifier from this catalog.None from this catalog.
capsuleNo additional leaf qualifier from this catalog.None from this catalog.
dcapsuleNo additional leaf qualifier from this catalog.None from this catalog.
obbNo additional leaf qualifier from this catalog.None from this catalog.
dobbNo additional leaf qualifier from this catalog.None from this catalog.
segment2No additional leaf qualifier from this catalog.None from this catalog.
dsegment2No additional leaf qualifier from this catalog.None from this catalog.
ray2No additional leaf qualifier from this catalog.None from this catalog.
dray2No additional leaf qualifier from this catalog.None from this catalog.
line2No additional leaf qualifier from this catalog.None from this catalog.
dline2No additional leaf qualifier from this catalog.None from this catalog.
triangle2No additional leaf qualifier from this catalog.None from this catalog.
dtriangle2No additional leaf qualifier from this catalog.None from this catalog.
segment3No additional leaf qualifier from this catalog.None from this catalog.
dsegment3No additional leaf qualifier from this catalog.None from this catalog.
line3No additional leaf qualifier from this catalog.None from this catalog.
dline3No additional leaf qualifier from this catalog.None from this catalog.
triangle3No additional leaf qualifier from this catalog.None from this catalog.
dtriangle3No additional leaf qualifier from this catalog.None from this catalog.
circleNo additional leaf qualifier from this catalog.None from this catalog.
dcircleNo additional leaf qualifier from this catalog.None from this catalog.
rectNo additional leaf qualifier from this catalog.None from this catalog.
drectNo additional leaf qualifier from this catalog.None from this catalog.
obb2No additional leaf qualifier from this catalog.None from this catalog.
dobb2No additional leaf qualifier from this catalog.None from this catalog.
ellipseNo additional leaf qualifier from this catalog.None from this catalog.
dellipseNo additional leaf qualifier from this catalog.None from this catalog.
capsule2No additional leaf qualifier from this catalog.None from this catalog.
dcapsule2No additional leaf qualifier from this catalog.None from this catalog.
cylinderNo additional leaf qualifier from this catalog.None from this catalog.
dcylinderNo additional leaf qualifier from this catalog.None from this catalog.
coneNo additional leaf qualifier from this catalog.None from this catalog.
dconeNo additional leaf qualifier from this catalog.None from this catalog.
conicalfrustumNo additional leaf qualifier from this catalog.None from this catalog.
dconicalfrustumNo additional leaf qualifier from this catalog.None from this catalog.
ellipsoidNo additional leaf qualifier from this catalog.None from this catalog.
dellipsoidNo additional leaf qualifier from this catalog.None from this catalog.
torusNo additional leaf qualifier from this catalog.None from this catalog.
dtorusNo additional leaf qualifier from this catalog.None from this catalog.
coord2No additional leaf qualifier from this catalog.None from this catalog.
coord3No additional leaf qualifier from this catalog.None from this catalog.
dcoord2No additional leaf qualifier from this catalog.None from this catalog.
dcoord3No additional leaf qualifier from this catalog.None from this catalog.

Read the owning rules.

Other value kinds

The kind's intrinsic rules and existing parameters still apply. For example, rune admits Unicode scalars, money retains its currency, and civil labels retain calendar validity. A new qualifier needs an explicit per-kind contract. field does not inherit tensor qualifiers merely because it also carries a dtype. Arrays around these leaves may still carry the separate layer rules below. Kind presence alone proves no particular operator or library workflow.

Other value kinds
TypeValue constraintsSource-write policies
boolNo additional leaf qualifier from this catalog.None from this catalog.
guidNo additional leaf qualifier from this catalog.None from this catalog.
measureNo additional leaf qualifier from this catalog.None from this catalog.
colorNo additional leaf qualifier from this catalog.None from this catalog.
streamNo additional leaf qualifier from this catalog.None from this catalog.
unitNo additional leaf qualifier from this catalog.None from this catalog.
civildateNo additional leaf qualifier from this catalog.None from this catalog.
civilNo additional leaf qualifier from this catalog.None from this catalog.
civiltimeNo additional leaf qualifier from this catalog.None from this catalog.
quantityNo additional leaf qualifier from this catalog.None from this catalog.
intervalNo additional leaf qualifier from this catalog.None from this catalog.
geodeticNo additional leaf qualifier from this catalog.None from this catalog.
moneyNo additional leaf qualifier from this catalog.None from this catalog.
cellidNo additional leaf qualifier from this catalog.None from this catalog.
flagsetNo additional leaf qualifier from this catalog.None from this catalog.
runeNo additional leaf qualifier from this catalog.None from this catalog.
measurementNo additional leaf qualifier from this catalog.None from this catalog.
fieldNo additional leaf qualifier from this catalog.None from this catalog.
fxrateNo additional leaf qualifier from this catalog.None from this catalog.
frameidNo additional leaf qualifier from this catalog.None from this catalog.
timerangeNo additional leaf qualifier from this catalog.None from this catalog.
sha256No additional leaf qualifier from this catalog.None from this catalog.
fieldelemNo additional leaf qualifier from this catalog.None from this catalog.
curvepointNo additional leaf qualifier from this catalog.None from this catalog.
ipNo additional leaf qualifier from this catalog.None from this catalog.
pwhashNo additional leaf qualifier from this catalog.None from this catalog.
almanacNo additional leaf qualifier from this catalog.None from this catalog.
ciphertextNo additional leaf qualifier from this catalog.None from this catalog.
exactNo additional leaf qualifier from this catalog.None from this catalog.
ipprefixNo additional leaf qualifier from this catalog.None from this catalog.

Read the owning rules.

Arrays, dictionaries, and nullable layers

These forms wrap a leaf type. A qualifier after a suffix binds that layer alone; a qualifier inside the kind's brackets binds the leaf.

Complete type layers
Type formValue constraintsSource-write policies
T[]length=interval, lengthMultiple=positiveInteger, distinct, sort, sort=field.path, sort=(path1,path2), dir=asc|desc, nulls=first|last, order=value|totalSort and distinct prepare source writes. full=reject|dropFirst|dropLast controls overflow at a declared maximum. Writes apply distinct, then stable sort, then full and count admission. A trim preserves whole lengthMultiple blocks. Strict sort keys use matching sort and distinct paths, not a strict option.
T[N]The arity fixes the size. Leaf constraints still apply. The fixed group takes no length, lengthMultiple, or dynamic-layer policies.Leaf write policies still apply.
dict<K,V>length=[lo,hi], distinctValues, and order=total beside distinctValues over binary-float values. Keys and values retain their own rules. Rules follow V inside the dict frame.full=reject|dropFirst|dropLast. Place or replace the key, refuse an equal value held at another key, trim by key order, then admit the count. A removal below the minimum refuses atomically.
tree<T>, tree<K,T>roots, arity, depth, length, whole-tree distinct, sibling sort with dir and nulls, plus the selected family invariant. Node values and optional sibling keys keep their own types.Tree and family writes preserve the declared shape and invariant. A family selects meaning and checked structure, not a byte layout or speed guarantee.
graph<V>, graph<V,E>vertices, arcs, out-degree, simple, undirected, acyclic, connected, bipartite. A numeric arc or weight=field.path supplies weights. E is rank-zero and nonnullable.Typed graph writes preserve the rules. An undirected connect writes a matching reverse arc with the same value. Arc heads and canonical order are checked at entry.
T?Allows null at that layer. Null bypasses present-value predicates. A present value still meets every rule of T.No policy manufactures a present value from null.

distinct needs element equality; sort needs an order. Both bind the innermost dynamic layer over leaves. A field sort needs a declared record field set. dir needs a sort. Strict field keys use the same paths in sort and distinct; there is no strict option. A key that can read null requires nulls; a key that cannot read null refuses it. order=total needs a sort or distinct rule over a binary float, or an eligible binary-float sort key. The binary formats are f16, bf16, f32, and f64.

f64<range=[0,1]>[]<length=[1,64]> weights = [0.25, 0.75]
u32[]<lengthMultiple=3> triangles = [0,1,2,2,3,0]
i32[]<distinct,sort,dir=desc> ids = [3,1,3]

Array order, distinctness, and overflow; block lengths; dictionary boundary.

A known constant is checked at compile time. Other values are checked when they reach their destination, unless the compiler proves the source rules already imply the destination rules. Ordinary arithmetic returns its usual carrier type with these constraints removed. Assignment to a constrained destination checks again.

Source writes may apply declared preparation policies, such as snap, clamp, text normalization, or array sort. The runtime prepares a private candidate and publishes it only after every rule passes. A failed write preserves the destination. Typed inputs, native results, stored values, and transport values must already satisfy their declared rules.

A nullable null bypasses rules about a present value. An empty array has no leaves to check, but still fails a positive minimum array length. Neither a range nor a length rule grants memory.

Selected counterparts in other languages

This fixed summary stays visible with the Trident examples. It compares value validation with nearby features; a generic type constraint answers a different question.

LanguageNearby featureWhere value checks live
TridentQualifiers form part of the complete type.Every constrained destination and typed entry checks the value. Explicit preparation policies apply only to source writes.
C#DataAnnotations attributes such as Range.A validator or framework reads the attributes. Validator.TryValidateObject validates an object; an attribute alone does not intercept every assignment.
C++Concepts and requires constrain template arguments.A wrapper or explicit check can enforce a runtime value range. A template constraint alone adds no range check to each assignment of a plain numeric variable.
GoInterface type sets constrain generic type arguments.Program code checks value ranges. A defined numeric type still admits its underlying type's values unless an API restricts construction and updates.
PythonAnnotated can attach metadata to a type hint.A library may interpret that metadata. Python itself does not enforce type hints or the metadata as runtime value rules.
RustA newtype can hide a value behind a checked API.A constructor and methods enforce the chosen invariant. A type alias alone adds no distinct type or value check.
TypeScriptA type predicate narrows a type after a test.The predicate's code checks the value. Type annotations erase; a static type alone supplies no runtime validator for received data.

Trident sources: language contract; declared constraints; value preparation; text rules; record check rows; failed write preserves values.

Counterpart references: .NET validation; C++ template constraints; Go type constraints and defined types; Python Annotated; Rust newtypes and aliases; TypeScript predicates and type erasure.

Collection layers: arrays, dictionaries, trees, and graphs

Each layer states what it holds and which rules every value meets. The layers compose: a dictionary can hold an array of nullable heaps, and a graph vertex can hold another collection. A rule belongs to the layer where it is written. Nullability belongs to its exact depth.

A complete max heapPriority 9 is the root. Children have priorities 7 and 4. Priority 3 is the left child of 7. Every parent is at least its child; the last level fills from the left.9743
Tree: hierarchy is part of the value. A heap checks complete shape and parent/child order. It need not be globally sorted.
A directed acyclic graphVertex 0 connects to 1 and 2. Vertex 1 also connects to 2, so vertex 2 has two incoming arcs. There is no directed cycle.012
Graph: vertices have positions and arcs can share a head. Rules can require a DAG, a bipartite graph, or an undirected forest.
A dictionary whose values are bounded arraysKey jobs points to an array with three slots: a heap, null, and another heap. Key idle has a null bucket. Missing key is a separate condition.jobsidleheapnullheapnull bucket
Dict + array: keys are unique and ordered. Each bucket has its own count and write policy. A null slot still counts as one array element.
Rules on each collection layer
LayerValue guaranteesWrites and reads
Dynamic array T[]length, lengthMultiple, distinct or distinct field paths; sort by values or field paths, direction and null placement. Float comparisons can state order=total.Writes deduplicate, sort stably, apply full, and admit. takeFirst/takeLast remove; addFirst prepends. A take below the minimum or off a length block refuses without change.
Dictionary dict<K,V,...>length, distinctValues, and total float comparison for distinctValues. Key eligibility and each key/value type apply independently.Keys order entries. dropFirst drops lowest keys; dropLast drops highest keys. A duplicate key replaces. A duplicate value at another key under distinctValues refuses. invert requires distinctValues and key-eligible V.
Tree tree<T,...> or tree<K,T,...>Root count, per-node arity, root-to-leaf depth in nodes, whole node count, whole-tree distinctness, sibling sort, key order, and family invariants.Positional children keep author order; keyed siblings keep key order. Node views have one root. Traversal is preorder, except BST families use in-order. No general tree equality is defined.
Graph graph<V,E,...>Vertex/arc counts, out-degree, simple, undirected, acyclic, connected and bipartite. Heads are valid vertex indices. weight selects a numeric arc field.Arcs order by tail, then head, then canonical arc-value bytes. Simple excludes parallel arcs and self arcs. Undirected requires matching reverse multiplicity and equal arc values. Acyclic checks directed cycles or undirected cycles according to the graph type. Connected means weak connectivity.

Not every rule combines with every element type. Array sort and distinct require leaf or record elements with the needed comparison. Dict distinctValues requires leaf or record values; array, tree, and graph values do not gain equality from the outer dictionary. A keyed tree already has sibling key order and refuses a second sibling sort. A key type is nonnullable and unshaped.

A tree family states an invariant: heap, bst (AVL or red-black), kd, quad, oct, bvh, bsp, segment, interval, trie, or btree. The default plain family states only the layer rules. Families do not select hashes, pointer layouts, or promised run times.

Tree: a checked max heap.tri
ingot job { i32 priority = 0 }
tree<job,family=heap,heap=max(priority)> work = [
    { priority = 9 } {
        { priority = 7 } { { priority = 3 } }, { priority = 4 }
    }]
# Complete binary shape; every parent is >= its child.
# A child of priority 10 under 9 refuses.
job first = work.top().value()
work.pop()
Graph: bounded weighted DAG.tri
graph<text,f64<range=[0,1000]>,vertices=[1,4],
    arcs=[0,6],degree=[0,2],simple,acyclic,connected> roads = [
    "a" { 1 = 1.5, 2 = 2.0 }, "b" { 2 = 1.0 }, "c"]
# 3 vertices; 3 arcs; out-degrees 2, 1, 0.
# A head of 3, a self arc, or a back-cycle refuses. A graph arc type is rank-zero and not nullable.
Dict: an invertible bounded map.tri
dict<text<class=ident,length=[1,8]>,u32,
    length=[0,3],distinctValues,full=dropFirst> ids = {
    "alpha" = 1, "beta" = 2, "gamma" = 3
}
ids["delta"] = 4   # lowest key alpha leaves
ids["beta"] = 9    # same key replaces its old value
# ids["zeta"] = 3 refuses: gamma already holds 3.
dict<u32,text<class=ident,length=[1,8]>,distinctValues>
    names = t.data.dict.invert(ids)
Array: rings, sets, and priority order.tri
i32[]<length=[0,3],full=dropFirst> recent = [1,2,3]
recent += 4          # [2,3,4]
recent.takeFirst()   # [3,4]

text[]<distinct> tags = ["red"]
tags += "red"        # still one red

job[]<length=[0,3],sort=priority,dir=desc,
    full=dropLast> best = []
# The sorted array keeps the highest three priorities. It is a sequence, not a heap-shaped tree.

Design authorities: tree layers and families, graph decisions, dict rules, array rules, array end operations. Execution owners: tree surface, tree families, graph surface, and dict layer rules.

One nested contract, enforced in every language

The comparison uses this complete declaration, including its rules even when the value is null. job is an exact immutable record with one signed 32-bit priority field. The snippets below compare the defined Trident contract with explicit checks and ownership in other languages.

Trident: the whole contract.tri
ingot job { i32 priority = 0 }
const dict<text<class=ident,length=[1,8]>,tree<job,family=heap,heap=max(priority)>?[]<length=[0,3],full=dropFirst>?>? absent = null

# The same complete type, with present values:
dict<text<class=ident,length=[1,8]>,tree<job,family=heap,heap=max(priority)>?[]<length=[0,3],full=dropFirst>?>? queues = {
    "jobs" = [null, [{ priority = 9 } { { priority = 7 }, { priority = 4 } }]], "idle" = null
}
# jobs has two slots: a null heap, then a present heap. idle has a present key and an absent array.
# absent has no dictionary at all. const forbids rebinding and writes through that binding.
  1. 1. Dictionary ?The whole dictionary may be null. A present empty dictionary is a different value. A missing key is another condition.
  2. 2. Keyident is ASCII [A-Za-z_][A-Za-z0-9_]*. Each key has 1 to 8 characters; every character is one byte and one scalar here.
  3. 3. Array ?Each key can hold a null array. A present array has 0 to 3 slots. A growing write drops from the front; admission never trims input.
  4. 4. Heap ?Each array slot can be a null heap. A present heap can be empty. A nonempty heap has complete binary shape and parent priority at least each child.
Required results for the same contract
Input or actionResultRule
null; {}; {"idle" = null}; {"jobs" = [null]}Each is valid and distinct.Presence is tracked independently at all three nullable levels.
"9bad", "ninechars", "a\n", or "é" as a keyRefuse.Match the full ASCII identifier and the 1-to-8 length. A regex prefix is insufficient.
Receive a present array with four slotsRefuse without trimming.Admission checks length; full is a write policy.
Append D to present [A,B,C][B,C,D].Validate the old value and D, stage the append, then dropFirst and admit.
Receive level-order priorities [9,7,4,3] / [3,9]Accept / refuse.The dense sequence supplies complete shape. Compare each child with parent (i-1)/2; do not sort malformed input into validity.
Change an input alias after admissionThe checked value does not change.Immutable values, ownership transfer, recursive snapshots, or an opaque owned object protect the invariant.
Change max to min, remove a nullable level, or widen the key, with a null payloadThe declared types still differ.Null has no present-value checks to run. It does not erase the destination's complete type.

Trace admission and a typed write

The small model below illustrates these named rules. Priority lists are the dense level-order representation used by the comparison code.

Accept: jobs → [null, heap [9,7,4,3]]. The key fits ident/length; two slots fit length; every child meets its parent priority.

Object-oriented, functional, and procedural equivalents

Every comparison language has a row. The language buttons above filter these rows too. Expand a row to read its code. The examples use a dense level-order sequence of Job values for each heap: index 0 is the root, and children of i are 2i+1 and 2i+2. A dense sequence cannot encode a missing interior child, so completeness is guaranteed by the representation.

These are API examples, not replacement language built-ins. A generic Map, List, PriorityQueue, readonly annotation, or const reference alone does not enforce the contract. Typed examples start from the stated native types; a decoder checks those types before entry. Smart construction checks every key, heap and array, preserves every nullable level, and retains no mutable input alias. Append returns a checked candidate; any later publication into a Catalog passes through its constructor again. No example silently turns a bad heap into a good one.

The table compares the declared value rules. A manual heap representation does not reproduce Trident's tree syntax, canonical wire format, tree views, or automatic checks at every unrelated language destination. Each API keeps its own checks on every public entry. The notes state where a language also needs module encapsulation or an explicit ownership boundary.

LanguageRepresentation, admission, and write policy
C
Opaque owned snapshot and explicit checks: show code
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
/* Implementation file only. Public header forward-declares Catalog. */
typedef struct { int32_t priority; } Job;
typedef struct { size_t n; Job *jobs; } Heap;
typedef struct { size_t n; Heap *slots[3]; } Bucket;
typedef struct { char key[9]; Bucket *bucket; } Entry;
typedef struct Catalog { size_t n; Entry *entries; } Catalog;
static bool start(unsigned char c) {
  return c=='_' || (c>='A' && c<='Z') || (c>='a' && c<='z');
}
static bool key(const char s[9]) {
  size_t n=0;while(n<9 && s[n])n++;
  if(n<1 || n>8 || !start(s[0]))return false;
  for(size_t i=1;i<n;i++)if(!start(s[i]) && !(s[i]>='0' && s[i]<='9'))return false;
  return true;
}
static bool heap(const Heap *h) {
  if(!h)return true;
  if(h->n && !h->jobs)return false;
  for(size_t i=1;i<h->n;i++)if(h->jobs[(i-1)/2].priority<h->jobs[i].priority)return false;
  return true;
}
static bool bucket(const Bucket *b) {
  if(!b)return true;if(b->n>3)return false;
  for(size_t i=0;i<b->n;i++)if(!heap(b->slots[i]))return false;
  return true;
}
static void free_heap(Heap *h) {if(h){free(h->jobs);free(h);}}
void bucket_free(Bucket *b) {
  if(b){for(size_t i=0;i<b->n;i++)free_heap(b->slots[i]);free(b);}
}
void catalog_free(Catalog *c) {
  if(c){for(size_t i=0;i<c->n;i++)bucket_free(c->entries[i].bucket);free(c->entries);free(c);}
}
static bool copy_heap(const Heap *h,Heap **out) {
  *out=NULL;if(!h)return true;
  if(!heap(h) || h->n>SIZE_MAX/sizeof(Job))return false;
  Heap *c=calloc(1,sizeof *c);if(!c)return false;c->n=h->n;
  if(h->n){c->jobs=malloc(h->n*sizeof(Job));if(!c->jobs){free(c);return false;}
    memcpy(c->jobs,h->jobs,h->n*sizeof(Job));}
  *out=c;return true;
}
static bool copy_bucket(const Bucket *b,Bucket **out) {
  *out=NULL;if(!b)return true;if(!bucket(b))return false;
  Bucket *c=calloc(1,sizeof *c);if(!c)return false;c->n=b->n;
  for(size_t i=0;i<b->n;i++)if(!copy_heap(b->slots[i],&c->slots[i])){bucket_free(c);return false;}
  *out=c;return true;
}
static int by_key(const void *a,const void *b) {return strcmp(((const Entry*)a)->key,((const Entry*)b)->key);}
bool catalog_new(const Catalog *raw,Catalog **out) {
  *out=NULL;if(!raw)return true;
  if(raw->n>SIZE_MAX/sizeof(Entry) || (raw->n && !raw->entries))return false;
  Catalog *c=calloc(1,sizeof *c);if(!c)return false;c->n=raw->n;
  c->entries=calloc(c->n?c->n:1,sizeof(Entry));if(!c->entries){free(c);return false;}
  for(size_t i=0;i<c->n;i++) {
    if(!key(raw->entries[i].key) || !copy_bucket(raw->entries[i].bucket,&c->entries[i].bucket))
      {catalog_free(c);return false;}
    memcpy(c->entries[i].key,raw->entries[i].key,9);
  }
  qsort(c->entries,c->n,sizeof(Entry),by_key);
  for(size_t i=1;i<c->n;i++)if(!strcmp(c->entries[i-1].key,c->entries[i].key)){catalog_free(c);return false;}
  *out=c;return true;
}
bool bucket_append(const Bucket *b,const Heap *h,Bucket **out) {
  if(!b || !bucket(b) || !heap(h))return false;
  Heap *v=NULL;if(!copy_heap(h,&v))return false;
  Bucket *c=NULL;if(!copy_bucket(b,&c)){free_heap(v);return false;}
  if(c->n==3){free_heap(c->slots[0]);c->slots[0]=c->slots[1];c->slots[1]=c->slots[2];c->n=2;}
  c->slots[c->n++]=v;*out=c;return true;
}
static const Catalog *const absent=NULL;

Each pointer has its own presence: null catalog, null bucket, null heap. Empty heap uses a nonnull descriptor with count zero. The public header exposes no backing fields; construction copies every owned byte. Invalid pointers remain outside C's memory-safety guarantees. Language authority.

C#
Sealed class and immutable collections: show code
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text.RegularExpressions;
public readonly record struct Job(int Priority);
public sealed class Catalog {
  private readonly ImmutableSortedDictionary<string,
    ImmutableArray<ImmutableArray<Job>?>?>? entries;
  private static ImmutableArray<Job>? Heap(Job[]? xs) {
    if (xs is null) return null;
    var h = xs.ToImmutableArray();
    for (int i=1; i<h.Length; ++i)
      if (h[(i-1)/2].Priority < h[i].Priority) throw new ArgumentException("max heap");
    return h;
  }
  private static ImmutableArray<ImmutableArray<Job>?>? Bucket(Job[]?[]? xs) {
    if (xs is null) return null;
    if (xs.Length>3) throw new ArgumentException("length");
    return xs.Select(Heap).ToImmutableArray();
  }
  public Catalog(IReadOnlyDictionary<string,Job[]?[]?>? raw) {
    if (raw is null) { entries=null; return; }
    var b=ImmutableSortedDictionary.CreateBuilder<string,
      ImmutableArray<ImmutableArray<Job>?>?>(StringComparer.Ordinal);
    foreach (var (k,v) in raw) {
      if (!Regex.IsMatch(k,@"\A[A-Za-z_][A-Za-z0-9_]{0,7}\z"))
        throw new ArgumentException("key");
      b.Add(k,Bucket(v));
    }
    entries=b.ToImmutable();
  }
  public static Job[]?[] Append(Job[]?[] xs,Job[]? h) {
    _=Bucket(xs); _=Heap(h);
    return xs.Append(h).TakeLast(3).Select(x=>x?.ToArray()).ToArray();
  }
  public static readonly Catalog Absent=new(null);
}

Private immutable storage preserves ordinal key order and all nullable levels. Append returns a detached candidate; Catalog re-admits it before retention. Language authority.

C++
Private class with owned containers: show code
#include <cstdint>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
struct Job { std::int32_t priority; };
using Heap=std::vector<Job>;
using Bucket=std::vector<std::optional<Heap>>;
using Raw=std::optional<std::map<std::string,std::optional<Bucket>>>;
class Catalog {
  const Raw data;
  static bool start(unsigned char c) {
    return c=='_' || (c>='A' && c<='Z') || (c>='a' && c<='z');
  }
  static void heap(const std::optional<Heap>& h) {
    if (!h) return;
    for (std::size_t i=1;i<h->size();++i)
      if ((*h)[(i-1)/2].priority<(*h)[i].priority)
        throw std::invalid_argument("max heap");
  }
  static void bucket(const std::optional<Bucket>& b) {
    if (!b) return;
    if (b->size()>3) throw std::invalid_argument("length");
    for (const auto& h:*b) heap(h);
  }
  static Raw admit(Raw r) {
    if (r) for (const auto& [k,b]:*r) {
      if (k.empty() || k.size()>8 || !start(k[0])) throw std::invalid_argument("key");
      for (unsigned char c:k) if (!start(c) && !(c>='0' && c<='9'))
        throw std::invalid_argument("key");
      bucket(b);
    }
    return r;
  }
public:
  explicit Catalog(Raw r):data(admit(std::move(r))) {}
  static Bucket append(Bucket b,std::optional<Heap> h) {
    bucket(b); heap(h); b.push_back(std::move(h));
    if (b.size()>3) b.erase(b.begin());
    return b;
  }
};
const Catalog absent{std::nullopt};

Value containers are copied or moved into private const storage. Append works on a copy and returns a candidate for re-admission. Language authority.

Cutlass
Shared constraints plus a checked heap adapter: show code
ingot Job { i32 priority = 0 }
# A dense level-order sequence represents the complete heap shape. The array/key constraints are
# shared value-type rules.
private void checkHeap(Job[]? h) {
    if (h == null) { return }
    repeat (h.count(), i64 i = 0) {
        if (i > 0) {
            assert (h[(i - 1) / 2].priority >= h[i].priority, "max heap")
        }
    }
}
dict<text<class=ident,length=[1,8]>,Job[]?[]<length=[0,3],full=dropFirst>?>?
admit(dict<text<class=ident,length=[1,8]>,Job[]?[]<length=[0,3],full=dropFirst>?>? raw) {
    if (raw != null) {
        foreach (k, bucket in raw) {
            if (bucket != null) {
                foreach (h in bucket) { checkHeap(h) }
            }
        }
    }
    return raw
}
const dict<text<class=ident,length=[1,8]>,Job[]?[]<length=[0,3],full=dropFirst>?>? absent = null

Job[]?[]<length=[0,3],full=dropFirst> append(
    Job[]?[]<length=[0,3],full=dropFirst> old, Job[]? item) {
    foreach (h in old) { checkHeap(h) }
    checkHeap(item)
    old += item
    return old
}
# Keep raw storage unit-private. Every public entry checks its heap values; clients receive values,
# never a mutable alias to the stored owner.

Cutlass’s defined type rules supply ident/length, nullable depths, ingot records and array full. The dense-sequence adapter supplies the heap predicate explicitly on admission and append. A plain Job[] remains unconstrained outside that checked API; the wrapper does not create an automatic language-level heap type. Language authority.

Dart
Final class and unmodifiable copies: show code
final class Job {
  final int priority;
  Job(this.priority) {
    if(priority < -2147483648 || priority > 2147483647) throw ArgumentError('i32');
  }
}
typedef Heap=List<Job>;
typedef Bucket=List<Heap?>;
final class Catalog {
  final Map<String,Bucket?>? _data;
  static Heap? heap(Heap? xs) {
    if(xs==null) return null;
    final h=List<Job>.unmodifiable(xs);
    for(var i=1;i<h.length;i++) {
      if(h[(i-1)~/2].priority<h[i].priority) throw ArgumentError('max heap');
    }
    return h;
  }
  static Bucket? bucket(Bucket? b) {
    if(b==null) return null;
    if(b.length>3) throw ArgumentError('length');
    return List<Heap?>.unmodifiable(b.map(heap));
  }
  static Map<String,Bucket?>? admit(Map<String,Bucket?>? raw) {
    if(raw==null) return null;
    final out=<String,Bucket?>{};
    for(final k in raw.keys.toList()..sort()) {
      final m=RegExp(r'[A-Za-z_][A-Za-z0-9_]{0,7}').matchAsPrefix(k);
      if(m==null || m.end!=k.length) throw ArgumentError('key');
      out[k]=bucket(raw[k]);
    }
    return Map.unmodifiable(out);
  }
  Catalog(Map<String,Bucket?>? raw):_data=admit(raw);
  static Bucket append(Bucket b,Heap? h) {
    final xs=[...bucket(b)!,heap(h)];
    return bucket(xs.sublist(xs.length>3 ? xs.length-3 : 0))!;
  }
}
final absent=Catalog(null);

Each mutable container is copied into an unmodifiable one. The exact-match check rejects a trailing newline as well as an invalid key. Language authority.

Elixir
Immutable terms with checked module functions: show code
defmodule Checked do
  defp heap(nil), do: nil
  defp heap(h) when is_list(h) do
    xs=Enum.map(h,fn
      %{priority: p}=j when map_size(j)==1 and is_integer(p) and
        p>=-2147483648 and p<=2147483647 -> j
      _ -> raise ArgumentError,"Job"
    end) |> List.to_tuple()
    Enum.with_index(h) |> Enum.each(fn {j,i} ->
      if i>0 and elem(xs,div(i-1,2)).priority<j.priority, do: raise(ArgumentError,"max heap")
    end)
    h
  end
  defp bucket(nil), do: nil
  defp bucket(b) when is_list(b) and length(b)<=3, do: Enum.map(b,&heap/1)
  defp bucket(_), do: raise(ArgumentError,"length")
  def admit(nil), do: {:catalog,nil}
  def admit(raw) when is_map(raw) do
    entries=Enum.map(raw,fn {k,b} ->
      unless is_binary(k) and Regex.match?(~r/\A[A-Za-z_][A-Za-z0-9_]{0,7}\z/,k),
        do: raise(ArgumentError,"key")
      {k,bucket(b)}
    end) |> Enum.sort_by(&elem(&1,0))
    {:catalog,entries}
  end
  def append(b,h) when is_list(b) do
    bucket(b)
    bucket(Enum.take(b++[heap(h)],-3))
  end
  def absent, do: admit(nil)
end

Maps, lists and jobs are immutable terms. The tagged result is a checked boundary value, not an unforgeable abstract type; functions must re-admit terms at public entries. Language authority.

Go
Unexported fields and deep copies: show code
package checked
import("errors";"regexp";"sort")
type Job struct { Priority int32 }
type Heap []Job
type Bucket []*Heap
type Raw map[string]*Bucket
type entry struct { key string; value *Bucket }
type Catalog struct { entries *[]entry }
func heap(h *Heap)(*Heap,error) {
  if h==nil{return nil,nil}
  c:=append(Heap{},(*h)...)
  for i:=1;i<len(c);i++ {if c[(i-1)/2].Priority<c[i].Priority {
    return nil,errors.New("max heap")}}
  return &c,nil
}
func bucket(b *Bucket)(*Bucket,error) {
  if b==nil{return nil,nil}
  if len(*b)>3{return nil,errors.New("length")}
  c:=Bucket{}
  for _,h:=range *b {v,e:=heap(h);if e!=nil{return nil,e};c=append(c,v)}
  return &c,nil
}
func New(raw *Raw)(Catalog,error) {
  if raw==nil{return Catalog{},nil}
  keys:=make([]string,0,len(*raw));for k:=range *raw {keys=append(keys,k)}
  sort.Strings(keys);out:=[]entry{}
  for _,k:=range keys {
    if !regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]{0,7}$`).MatchString(k) {
      return Catalog{},errors.New("key")}
    v,e:=bucket((*raw)[k]);if e!=nil{return Catalog{},e};out=append(out,entry{k,v})
  }
  return Catalog{&out},nil
}
func Append(b Bucket,h *Heap)(Bucket,error) {
  c,e:=bucket(&b);if e!=nil{return nil,e}
  v,e:=heap(h);if e!=nil{return nil,e}
  xs:=append(*c,v);if len(xs)>3{xs=xs[1:]};return xs,nil
}
func Absent() Catalog {return Catalog{}}

Pointers encode presence independently of zero-length slices/maps. No mutable backing is exported. Go has no const aggregate binding; Absent returns a fresh immutable API value. Language authority.

Haskell
Abstract newtype and pure checks: show code
module Checked (Job(..), Catalog, admit, append, absent) where
import Data.Int (Int32)
import qualified Data.Map.Strict as M
data Job=Job { priority::Int32 }
type Heap=[Job]
type Bucket=[Maybe Heap]
newtype Catalog=Catalog (Maybe (M.Map String (Maybe Bucket)))
key k=length k>=1 && length k<=8 && start (head k) && all rest k
  where letter c=('A'<=c && c<='Z') || ('a'<=c && c<='z')
        start c=letter c || c=='_'
        rest c=start c || ('0'<=c && c<='9')
heap Nothing=True
heap (Just h)=and [priority p>=priority c |
  (p,l,r)<-zip3 h (children 1 h) (children 2 h), c<-l++r]
  where children offset xs=go (drop offset xs)
        go []=repeat []
        go (x:xs)=[x]:go (drop 1 xs)
bucket Nothing=True
bucket (Just b)=length b<=3 && all heap b
admit raw
  | maybe True (all (\(k,b)->key k && bucket b) . M.toList) raw=Right (Catalog raw)
  | otherwise=Left "constraint"
append b h
  | length b>3 || not (all heap b && heap h)=Left "constraint"
  | otherwise=Right (drop (max 0 (length b+1-3)) (b++[h]))
absent=Catalog Nothing

Catalog hides its constructor in the export list. Child streams visit the heap list once, without repeated list indexing. Values are immutable. Language authority.

Jai
Opaque native-backed adapter: show code
// The C row supplies the complete checked owner and both copy routines.
// C ABI ingress records; no owned backing is exposed by the handle API.
#scope_file
Job :: struct { priority:s32; }
Heap :: struct { count:u64; jobs:*Job; }
Bucket :: struct { count:u64; slots:[3]*Heap; }
Entry :: struct { key:[9]u8; bucket:*Bucket; }
RawCatalog :: struct { count:u64; entries:*Entry; }
checked :: #foreign_library "checked";
catalog_new :: (raw:*RawCatalog,out:**void)->bool #foreign checked;
catalog_free :: (value:*void) #foreign checked;
bucket_append :: (raw:*Bucket,h:*Heap,out:**void)->bool #foreign checked;
bucket_free :: (value:*void) #foreign checked;
#scope_export
admit :: (raw:*RawCatalog)->(*void,bool) {
    owned:*void=null;
    ok:=catalog_new(raw,*owned);
    return owned,ok;
}
append :: (raw:*Bucket,h:*Heap)->(*void,bool) {
    owned:*void=null;
    ok:=bucket_append(raw,h,*owned);
    return owned,ok;
}
close_catalog :: (owned:*void) { catalog_free(owned); }
close_bucket :: (owned:*void) { bucket_free(owned); }
ABSENT :: cast(*void) null;

This equivalent deliberately reuses the complete C implementation shown in this table. The native-backed module enforces keys, canonical uniqueness, every nullable level, heap shape/order, admission, deep ownership and dropFirst. The opaque handle exposes no raw fields; close releases its owner. The ingress layout shown is for a 64-bit C ABI. Language authority.

Java
Final class with null-aware defensive copies: show code
import java.util.*;
record Job(int priority) {}
final class Catalog {
  private final Map<String,List<List<Job>>> data;
  private static List<Job> heap(List<Job> xs) {
    if(xs==null) return null;
    var h=new ArrayList<Job>(xs);
    for(var j:h) Objects.requireNonNull(j);
    for(int i=1;i<h.size();i++)
      if(h.get((i-1)/2).priority()<h.get(i).priority())
        throw new IllegalArgumentException("max heap");
    return Collections.unmodifiableList(h);
  }
  private static List<List<Job>> bucket(List<List<Job>> xs) {
    if(xs==null) return null;
    if(xs.size()>3) throw new IllegalArgumentException("length");
    var b=new ArrayList<List<Job>>();
    for(var h:xs) b.add(heap(h));
    return Collections.unmodifiableList(b);
  }
  Catalog(Map<String,List<List<Job>>> raw) {
    if(raw==null) {data=null;return;}
    var m=new TreeMap<String,List<List<Job>>>();
    raw.forEach((k,b)->{
      if(k==null || !k.matches("[A-Za-z_][A-Za-z0-9_]{0,7}"))
        throw new IllegalArgumentException("key");
      m.put(k,bucket(b));
    });
    data=Collections.unmodifiableMap(m);
  }
  static List<List<Job>> append(List<List<Job>> xs,List<Job> h) {
    var b=new ArrayList<>(Objects.requireNonNull(bucket(xs)));
    b.add(heap(h)); if(b.size()>3) b.remove(0);
    return bucket(b);
  }
  static final Catalog ABSENT=new Catalog(null);
}

The copies preserve null buckets and null heaps. List.copyOf would reject null elements, so it is not the factory used here. Language authority.

JavaScript
Private state and frozen snapshots: show code
const check = (ok, why) => { if (!ok) throw Error(why); };
function key(k) {
  check(typeof k === 'string' && /^[A-Za-z_][A-Za-z0-9_]{0,7}(?![\s\S])/.test(k), 'key');
}
function heap(xs) {
  if (xs === null) return null;
  check(Array.isArray(xs), 'heap');
  const h = Array.from(xs, j => {
    check(j && Object.keys(j).length === 1 &&
      Number.isInteger(j.priority) && j.priority >= -2147483648 &&
      j.priority <= 2147483647, 'Job');
    return Object.freeze({priority: j.priority});
  });
  h.forEach((j,i) => check(!i || h[Math.floor((i-1)/2)].priority >= j.priority,'max heap'));
  return Object.freeze(h);
}
function bucket(xs) {
  if (xs === null) return null;
  check(Array.isArray(xs) && xs.length <= 3, 'length');
  return Object.freeze(Array.from(xs, heap));
}
class Catalog {
  #entries;
  constructor(raw) {
    check(raw === null || raw instanceof Map, 'catalog');
    this.#entries = raw === null ? null : Object.freeze(
      [...raw].map(([k,v]) => { key(k); return Object.freeze([k,bucket(v)]); })
        .sort((a,b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    Object.freeze(this);
  }
}
function append(xs, h) {
  check(xs !== null, 'absent bucket');
  return bucket([...bucket(xs), heap(h)].slice(-3));
}
const absent = new Catalog(null);

Array.from visits holes, so sparse input cannot skip a check. A frozen Map is still mutable; private storage uses frozen entry arrays. Language authority.

Julia
Immutable tuple snapshot behind an inner constructor: show code
struct Job
    priority::Int32
end
function heap(h)
    isnothing(h) && return nothing
    xs=Tuple(Job(j.priority) for j in h)
    all(xs[fld(i-2,2)+1].priority>=xs[i].priority for i in 2:length(xs)) || error("max heap")
    xs
end
function bucket(b)
    isnothing(b) && return nothing
    length(b)<=3 || error("length")
    Tuple(heap(h) for h in b)
end
struct Catalog
    entries::Union{Nothing,Tuple}
    function Catalog(raw)
        isnothing(raw) && return new(nothing)
        entries=Tuple(begin
            k isa String && occursin(r"\A[A-Za-z_][A-Za-z0-9_]{0,7}\z",k) || error("key")
            (k,bucket(v))
        end for (k,v) in sort!(collect(raw);by=first))
        new(entries)
    end
end
function append(b,h)
    isnothing(b) && error("absent bucket")
    xs=(bucket(b)...,heap(h))
    bucket(xs[max(1,length(xs)-2):end])
end
const absent=Catalog(nothing)

The inner constructor removes the unchecked default constructor. Retained containers are tuples, not mutable vectors or Dict objects. Language authority.

Kotlin
Private state and defensive JVM collections: show code
import java.util.Collections
data class Job(val priority:Int)
typealias Heap=List<Job>
typealias Bucket=List<Heap?>
class Catalog(raw:Map<String,Bucket?>?) {
  private val data:Map<String,Bucket?>?=raw?.toSortedMap()?.mapValues { (k,b) ->
    require(Regex("[A-Za-z_][A-Za-z0-9_]{0,7}").matches(k));bucket(b)
  }?.let {Collections.unmodifiableMap(it)}
  companion object {
    private fun heap(xs:Heap?):Heap? {
      if(xs==null)return null
      val h=ArrayList(xs)
      require((1 until h.size).all{h[(it-1)/2].priority>=h[it].priority})
      return Collections.unmodifiableList(h)
    }
    private fun bucket(xs:Bucket?):Bucket? {
      if(xs==null)return null
      require(xs.size<=3)
      return Collections.unmodifiableList(xs.map{heap(it)})
    }
    fun append(b:Bucket,h:Heap?):Bucket {
      val xs=bucket(b)!!+listOf(heap(h))
      return bucket(xs.takeLast(3))!!
    }
    val absent=Catalog(null)
  }
}

List is a read-only interface, not proof that its backing cannot change. The code copies nested lists and wraps each retained collection. Language authority.

Lua
Closure-owned tables and detached reads: show code
local NULL={} -- explicit absence; never a hole in a sequence
local function dense(xs)
  if type(xs)~='table' or getmetatable(xs)~=nil then error('plain sequence') end
  local n=0
  for k in pairs(xs) do
    if type(k)~='number' or k%1~=0 or k<1 then error('index') end
    n=n+1
  end
  for i=1,n do if rawget(xs,i)==nil then error('hole') end end
  return n
end
local function heap(h)
  if h==NULL then return NULL end
  local out={}
  for i=1,dense(h) do
    local j=h[i]
    if type(j)~='table' or getmetatable(j)~=nil then error('Job') end
    local p=j.priority
    if type(p)~='number' or p%1~=0 or p< -2147483648 or p>2147483647 then error('Job') end
    for k in pairs(j) do if k~='priority' then error('Job field') end end
    if i>1 and h[math.floor(i/2)].priority<p then error('heap') end
    out[i]={priority=p}
  end
  return out
end
local function bucket(b)
  if b==NULL then return NULL end
  local n=dense(b);if n>3 then error('length') end
  local out={};for i=1,n do out[i]=heap(b[i]) end;return out
end
local function snapshot(raw)
  if raw==NULL then return NULL end
  local keys={}
  for k in pairs(raw) do
    if type(k)~='string' or #k<1 or #k>8 or
      not k:match('^[A-Za-z_][A-Za-z0-9_]*$') then error('key') end
    keys[#keys+1]=k
  end
  table.sort(keys)
  local out={};for _,k in ipairs(keys) do out[k]=bucket(raw[k]) end
  return out
end
local function Catalog(raw)
  local owned=snapshot(raw)
  return function()
    if owned==NULL then return NULL end
    local keys={};for k in pairs(owned) do keys[#keys+1]=k end
    table.sort(keys)
    local entries={}
    for i,k in ipairs(keys) do entries[i]={key=k,value=bucket(owned[k])} end
    return entries -- detached read in canonical key order
  end
end
local function append(b,h)
  if b==NULL then error('absent bucket') end
  local xs=bucket(b);xs[#xs+1]=heap(h)
  if #xs>3 then table.remove(xs,1) end
  return xs
end
local absent=Catalog(NULL)

NULL represents absence at every nullable level; nil would erase a map entry or create an array hole. The closure never exposes its stored tables. Lua 5.4 can mark the local binding <const>. Language authority.

Luau
Checked closure with explicit null sentinel: show code
local NULL={} -- explicit absence; never a hole in a sequence
local function dense(xs)
  if type(xs)~='table' or getmetatable(xs)~=nil then error('plain sequence') end
  local n=0
  for k in pairs(xs) do
    if type(k)~='number' or k%1~=0 or k<1 then error('index') end
    n=n+1
  end
  for i=1,n do if rawget(xs,i)==nil then error('hole') end end
  return n
end
local function heap(h)
  if h==NULL then return NULL end
  local out={}
  for i=1,dense(h) do
    local j=h[i]
    if type(j)~='table' or getmetatable(j)~=nil then error('Job') end
    local p=j.priority
    if type(p)~='number' or p%1~=0 or p< -2147483648 or p>2147483647 then error('Job') end
    for k in pairs(j) do if k~='priority' then error('Job field') end end
    if i>1 and h[math.floor(i/2)].priority<p then error('heap') end
    out[i]={priority=p}
  end
  return out
end
local function bucket(b)
  if b==NULL then return NULL end
  local n=dense(b);if n>3 then error('length') end
  local out={};for i=1,n do out[i]=heap(b[i]) end;return out
end
local function snapshot(raw)
  if raw==NULL then return NULL end
  local keys={}
  for k in pairs(raw) do
    if type(k)~='string' or #k<1 or #k>8 or
      not k:match('^[A-Za-z_][A-Za-z0-9_]*$') then error('key') end
    keys[#keys+1]=k
  end
  table.sort(keys)
  local out={};for _,k in ipairs(keys) do out[k]=bucket(raw[k]) end
  return out
end
local function Catalog(raw)
  local owned=snapshot(raw)
  return function()
    if owned==NULL then return NULL end
    local keys={};for k in pairs(owned) do keys[#keys+1]=k end
    table.sort(keys)
    local entries={}
    for i,k in ipairs(keys) do entries[i]={key=k,value=bucket(owned[k])} end
    return entries -- detached read in canonical key order
  end
end
local function append(b,h)
  if b==NULL then error('absent bucket') end
  local xs=bucket(b);xs[#xs+1]=heap(h)
  if #xs>3 then table.remove(xs,1) end
  return xs
end
local absent=Catalog(NULL)

Luau uses the same explicit sentinel and copies shown here. Types alone do not freeze tables; table.freeze can protect each copied level, but freezing only the outer table is insufficient. Language authority.

Nim
Module-private object field and copied sequences: show code
import std/[options,tables,algorithm,strutils]
type
  Job* = object
    priority*:int32
  Heap* = seq[Job]
  Bucket* = seq[Option[Heap]]
  Catalog* = object
    entries:Option[seq[(string,Option[Bucket])]]
proc heap(h:Option[Heap]):Option[Heap] =
  if h.isNone:return none(Heap)
  var xs:Heap = @[]
  for j in h.get:xs.add j
  for i in 1..<xs.len:
    if xs[(i-1) div 2].priority<xs[i].priority:raise newException(ValueError,"heap")
  some(xs)
proc bucket(b:Option[Bucket]):Option[Bucket] =
  if b.isNone:return none(Bucket)
  if b.get.len>3:raise newException(ValueError,"length")
  var xs:Bucket = @[]
  for h in b.get:xs.add heap(h)
  some(xs)
proc admit*(raw:Option[Table[string,Option[Bucket]]]):Catalog =
  if raw.isNone:return Catalog()
  var xs:seq[(string,Option[Bucket])] = @[]
  for k,v in raw.get:
    if k.len notin 1..8 or k[0] notin Letters+{'_'} or
      not k.allCharsInSet(Letters+Digits+{'_'}):
      raise newException(ValueError,"key")
    xs.add((k,bucket(v)))
  xs.sort(proc(a,b:(string,Option[Bucket])):int=cmp(a[0],b[0]))
  Catalog(entries:some(xs))
proc append*(b:Bucket,h:Option[Heap]):Bucket =
  result=bucket(some(b)).get
  result.add heap(h)
  if result.len>3:result.delete(0)
let absent=admit(none(Table[string,Option[Bucket]]))

Only starred symbols leave the module. No public storage setter or mutable view bypasses admission; a default Catalog is the valid absent value. Language authority.

Objective-C
Immutable Foundation copies in private storage: show code
#import <Foundation/Foundation.h>
#include <math.h>
#include <stdint.h>
static void Need(BOOL ok) { if(!ok) @throw [NSException exceptionWithName:@"Constraint" reason:nil userInfo:nil]; }
static id Heap(id raw) {
  if(raw==NSNull.null)return raw;
  Need([raw isKindOfClass:NSArray.class]);
  NSMutableArray *out=[NSMutableArray array];
  for(NSUInteger i=0;i<[raw count];i++) {
    id j=raw[i];Need([j isKindOfClass:NSDictionary.class] && [j count]==1);
    NSNumber *p=j[@"priority"];
    Need([p isKindOfClass:NSNumber.class] &&
      CFGetTypeID((__bridge CFTypeRef)p)!=CFBooleanGetTypeID());
    double x=p.doubleValue;Need(isfinite(x) && floor(x)==x && x>=INT32_MIN && x<=INT32_MAX);
    if(i)Need([out[(i-1)/2][@"priority"] intValue]>=p.intValue);
    [out addObject:@{@"priority":@(p.intValue)}];
  }
  return [out copy];
}
static id Bucket(id b) {
  if(b==NSNull.null)return b;
  Need([b isKindOfClass:NSArray.class] && [b count]<=3);
  NSMutableArray *out=[NSMutableArray array];
  for(id h in b)[out addObject:Heap(h)];return [out copy];
}
@interface Catalog:NSObject
- (instancetype)initWithRaw:(id)raw;
@end
@implementation Catalog { NSArray *_entries; }
- (instancetype)initWithRaw:(id)raw {
  self=[super init];if(!self)return nil;
  if(raw==NSNull.null)return self;
  Need([raw isKindOfClass:NSDictionary.class]);
  NSMutableArray *out=[NSMutableArray array];
  for(id k in [[raw allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
    Need([k isKindOfClass:NSString.class]);
    Need([k rangeOfString:@"\\A[A-Za-z_][A-Za-z0-9_]{0,7}\\z" options:NSRegularExpressionSearch].length==[k length] && [k length]>0);
    [out addObject:@[[k copy],Bucket(raw[k])]];
  }
  _entries=[out copy];return self;
}
@end
static NSArray *Append(NSArray *b,id h) {
  NSMutableArray *xs=[Bucket(b) mutableCopy];[xs addObject:Heap(h)];
  if(xs.count>3)[xs removeObjectAtIndex:0];return [xs copy];
}
// Catalog *const absent=[[Catalog alloc] initWithRaw:NSNull.null];

NSNull holds a present map key with an absent bucket, or a null heap inside an array. nil cannot be an NSArray element. No mutable ivar is exposed. Language authority.

Perl
Private serialized snapshot with detached reads: show code
use strict; use warnings;
use JSON::PP qw(encode_json decode_json);
use Scalar::Util qw(looks_like_number);
sub heap {
  my($h)=@_;return undef unless defined $h;
  die 'heap' unless ref($h) eq 'ARRAY';
  my @out;
  for my $i (0..$#$h) {
    my $j=$h->[$i];die 'Job' unless ref($j) eq 'HASH' && keys(%$j)==1;
    my $p=$j->{priority};
    die 'i32' unless defined($p) && !ref($p) && looks_like_number($p) &&
      $p==int($p) && $p>=-2147483648 && $p<=2147483647;
    die 'heap' if $i && $h->[int(($i-1)/2)]->{priority}<$p;
    push @out,{priority=>0+$p};
  }
  return \@out;
}
sub bucket {
  my($b)=@_;return undef unless defined $b;
  die 'length' unless ref($b) eq 'ARRAY' && @$b<=3;
  return [map {heap($_)} @$b];
}
sub catalog {
  my($raw)=@_;my $out;
  if(defined $raw) {
    die 'map' unless ref($raw) eq 'HASH';$out=[];
    for my $k (sort keys %$raw) {
      die 'key' unless $k=~/\A[A-Za-z_][A-Za-z0-9_]{0,7}\z/;
      push @$out,[$k,bucket($raw->{$k})];
    }
  }
  my $owned=encode_json($out);
  return sub {decode_json($owned)};
}
sub append {
  my($b,$h)=@_;die 'absent bucket' unless defined $b;
  my $xs=bucket($b);push @$xs,heap($h);shift @$xs if @$xs>3;return $xs;
}
my $absent=catalog(undef);

Perl scalars may be numeric strings, so the adapter explicitly converts admitted priorities to numbers. The closure stores private JSON and returns a fresh decode; it exposes no shared mutable reference. Language authority.

PHP
Readonly Job and private snapshot: show code
<?php
declare(strict_types=1);
final readonly class Job {
  public function __construct(public int $priority) {
    if($priority < -2147483648 || $priority > 2147483647) throw new InvalidArgumentException('i32');
  }
}
final readonly class Catalog {
  private ?array $data;
  private static function heap(?array $h):?array {
    if($h===null)return null;
    if(!array_is_list($h))throw new InvalidArgumentException('dense heap');
    $out=[];
    foreach($h as $i=>$j) {
      if(!$j instanceof Job)throw new InvalidArgumentException('Job');
      if($i>0 && $h[intdiv($i-1,2)]->priority<$j->priority)
        throw new InvalidArgumentException('max heap');
      $out[]=new Job($j->priority);
    }
    return $out;
  }
  private static function bucket(?array $b):?array {
    if($b===null)return null;
    if(!array_is_list($b) || count($b)>3)throw new InvalidArgumentException('length');
    return array_map(self::heap(...),$b);
  }
  public function __construct(?array $raw) {
    if($raw===null){$this->data=null;return;}
    $out=[];
    foreach($raw as $k=>$b) {
      if(!is_string($k) || !preg_match('/\A[A-Za-z_][A-Za-z0-9_]{0,7}\z/D',$k))
        throw new InvalidArgumentException('key');
      $out[$k]=self::bucket($b);
    }
    ksort($out,SORT_STRING);$this->data=$out;
  }
  public static function append(array $b,?array $h):array {
    $xs=self::bucket($b);$xs[]=self::heap($h);
    return self::bucket(array_slice($xs,-3));
  }
}
$absent=new Catalog(null);

The factory rebuilds every heap so PHP array references cannot retain an input alias; Job is readonly. The runtime variable name is not const; the object retains no mutable references from its input. Language authority.

Python
Frozen records and tuples: show code
import re
from dataclasses import dataclass
from typing import Final
@dataclass(frozen=True)
class Job:
    priority: int
    def __post_init__(self):
        if type(self.priority) is not int or not -(2**31) <= self.priority < 2**31:
            raise ValueError("i32 priority")
def heap(xs):
    if xs is None: return None
    if type(xs) not in (list,tuple): raise ValueError("heap sequence")
    if any(type(x) is not Job for x in xs): raise ValueError("Job")
    h = tuple(Job(x.priority) for x in xs)
    if any(h[(i-1)//2].priority < h[i].priority for i in range(1,len(h))):
        raise ValueError("max heap")
    return h
def bucket(xs):
    if xs is None: return None
    if type(xs) not in (list,tuple): raise ValueError("bucket sequence")
    if len(xs) > 3: raise ValueError("length")
    return tuple(heap(x) for x in xs)
def key(k):
    if type(k) is not str or re.fullmatch(r'[A-Za-z_][A-Za-z0-9_]{0,7}',k) is None:
        raise ValueError("key")
    return True
@dataclass(frozen=True, init=False)
class Catalog:
    _entries: tuple | None
    def __init__(self, raw):
        if raw is not None and type(raw) is not dict: raise ValueError("dictionary")
        result = None if raw is None else tuple(
            (k, bucket(v)) for k,v in sorted(raw.items()) if key(k))
        object.__setattr__(self, '_entries', result)
def append(xs, h):
    if xs is None: raise ValueError("absent bucket")
    old, item = bucket(xs), heap(h)
    return bucket((*old, item)[-3:])
absent: Final = Catalog(None)

Tuples and frozen Job records retain no mutable children. Final is a static-checker rule, not a runtime ban on rebinding a module name. Language authority.

Ruby
Recursive snapshots and freeze: show code
class Catalog
  def self.heap(xs)
    return nil if xs.nil?
    h=xs.map do |j|
      p=j.fetch(:priority)
      raise ArgumentError unless j.keys==[:priority] && p.is_a?(Integer) && (-2**31...2**31).cover?(p)
      {priority:p}.freeze
    end
    (1...h.size).each{|i|raise ArgumentError,'heap' if h[(i-1)/2][:priority]<h[i][:priority]}
    h.freeze
  end
  def self.bucket(xs)
    return nil if xs.nil?
    raise ArgumentError,'length' if xs.size>3
    xs.map{|h|heap(h)}.freeze
  end
  def initialize(raw)
    @data=raw.nil? ? nil : raw.sort.to_h do |k,b|
      raise ArgumentError,'key' unless k.is_a?(String) && /\A[A-Za-z_][A-Za-z0-9_]{0,7}\z/.match?(k)
      [k.dup.freeze,self.class.bucket(b)]
    end.freeze
    freeze
  end
  def self.append(b,h)
    raise ArgumentError,'absent bucket' if b.nil?
    bucket((bucket(b)+[heap(h)]).last(3))
  end
end
ABSENT=Catalog.new(nil)

Every retained string, job, heap, bucket, and map is frozen. Ruby constants can still be reassigned with a warning; freeze protects the value. Language authority.

Rust
Private module and owned values: show code
mod checked {
  use std::collections::BTreeMap;
  #[derive(Clone)] pub struct Job { pub priority:i32 }
  pub type Heap=Vec<Job>;
  pub type Bucket=Vec<Option<Heap>>;
  pub struct Catalog(Option<BTreeMap<String,Option<Bucket>>>);
  fn heap(h:&Option<Heap>)->bool {
    h.as_ref().is_none_or(|h| (1..h.len()).all(|i|
      h[(i-1)/2].priority>=h[i].priority))
  }
  fn bucket(b:&Option<Bucket>)->bool {
    b.as_ref().is_none_or(|b| b.len()<=3 && b.iter().all(heap))
  }
  fn key(k:&str)->bool {
    let b=k.as_bytes();
    (1..=8).contains(&b.len()) &&
      (b[0].is_ascii_alphabetic() || b[0]==b'_') &&
      b.iter().all(|c| c.is_ascii_alphanumeric() || *c==b'_')
  }
  impl Catalog {
    pub fn new(raw:Option<BTreeMap<String,Option<Bucket>>>)->Result<Self,&'static str> {
      if raw.as_ref().is_some_and(|m|
        m.iter().any(|(k,v)| !key(k) || !bucket(v))) { return Err("constraint") }
      Ok(Self(raw))
    }
  }
  pub fn append(xs:&Bucket,h:Option<Heap>)->Result<Bucket,&'static str> {
    if xs.len()>3 || !xs.iter().all(heap) || !heap(&h) {return Err("constraint")}
    let mut b=xs.clone(); b.push(h);
    if b.len()>3 {b.remove(0);}
    Ok(b)
  }
  pub const ABSENT:Catalog=Catalog(None);
}

Construction moves the map and all vectors. The tuple field stays private; no mutable reference to stored data escapes. Language authority.

Scala
Private constructor and immutable collections: show code
import scala.collection.immutable.TreeMap
object Checked {
  final case class Job(priority:Int)
  type Heap=Vector[Job]
  type Bucket=Vector[Option[Heap]]
  final class Catalog private[Checked](private val data:
    Option[TreeMap[String,Option[Bucket]]])
  private def key(k:String)=k.matches("[A-Za-z_][A-Za-z0-9_]{0,7}")
  private def heap(h:Option[Heap])=h.forall(xs=>
    (1 until xs.size).forall(i=>xs((i-1)/2).priority>=xs(i).priority))
  private def bucket(b:Option[Bucket])=b.forall(xs=>xs.size<=3 && xs.forall(heap))
  def admit(raw:Option[Map[String,Option[Bucket]]]):Catalog = {
    require(raw.forall(_.forall{case(k,b)=>key(k) && bucket(b)}),"constraint")
    new Catalog(raw.map(m=>TreeMap.from(m)))
  }
  def append(b:Bucket,h:Option[Heap]):Bucket = {
    require(b.size<=3 && b.forall(heap) && heap(h),"constraint")
    (b :+ h).takeRight(3)
  }
  val absent:Catalog=admit(None)
}

Private construction prevents a public case-class copy from bypassing Catalog admission. Jobs, vectors and maps are immutable. Language authority.

Swift
Value structs and private storage: show code
struct Job { let priority:Int32 }
typealias Heap=[Job]
typealias Bucket=[Heap?]
enum Invalid:Error { case constraint }
struct Catalog {
  private let entries:[(String,Bucket?)]?
  init(_ raw:[String:Bucket?]?) throws {
    guard let raw else { entries=nil; return }
    var result:[(String,Bucket?)]=[]
    for (k,b) in raw.sorted(by:{$0.key<$1.key}) {
      let bytes=Array(k.utf8)
      func start(_ c:UInt8)->Bool { c==95 || (65...90).contains(c) || (97...122).contains(c) }
      guard (1...8).contains(bytes.count),start(bytes[0]),
        bytes.allSatisfy({start($0) || (48...57).contains($0)}),Self.valid(b)
      else {throw Invalid.constraint}
      result.append((k,b))
    }
    entries=result
  }
  private static func heap(_ h:Heap?)->Bool {
    guard let h else {return true}
    return h.indices.dropFirst().allSatisfy{h[($0-1)/2].priority>=h[$0].priority}
  }
  private static func valid(_ b:Bucket?)->Bool {
    b.map{$0.count<=3 && $0.allSatisfy(heap)} ?? true
  }
  static func append(_ b:Bucket,_ h:Heap?) throws -> Bucket {
    guard valid(b),heap(h) else {throw Invalid.constraint}
    return Array((b+[h]).suffix(3))
  }
  static let absent=try! Catalog(nil)
}

Arrays and structs have value semantics. A missing dictionary key differs from a present key whose bucket is nil. Language authority.

TypeScript
Checked private class: show code
type Job = Readonly<{priority:number}>;
type Heap = readonly Job[];
type Bucket = readonly (Heap|null)[];
type Raw = ReadonlyMap<string, Bucket|null>|null;
function check(ok: unknown, why: string): asserts ok {
  if (!ok) throw Error(why);
}
function heap(xs: Heap|null): Heap|null {
  if (xs === null) return null;
  check(Array.isArray(xs), 'heap');
  const h = Array.from(xs, j => {
    check(j && Object.keys(j).length === 1 &&
      Number.isInteger(j.priority) && j.priority >= -2147483648 &&
      j.priority <= 2147483647, 'Job');
    return Object.freeze({priority:j.priority});
  });
  h.forEach((j,i) => check(!i || h[Math.floor((i-1)/2)].priority >= j.priority,'max heap'));
  return Object.freeze(h);
}
function bucket(xs: Bucket|null): Bucket|null {
  if (xs === null) return null;
  check(Array.isArray(xs) && xs.length <= 3, 'length');
  return Object.freeze(Array.from(xs,heap));
}
class Catalog {
  #entries: readonly (readonly [string,Bucket|null])[]|null;
  constructor(raw: Raw) {
    this.#entries = raw === null ? null : Object.freeze([...raw].map(([k,v]) => {
      check(/^[A-Za-z_][A-Za-z0-9_]{0,7}(?![\s\S])/.test(k), 'key');
      return Object.freeze([k,bucket(v)] as const);
    }).sort((a,b) => a[0]<b[0] ? -1 : a[0]>b[0] ? 1 : 0));
    Object.freeze(this);
  }
}
function append(xs: Bucket, h: Heap|null): Bucket {
  return bucket([...bucket(xs)!,heap(h)].slice(-3))!;
}
const absent = new Catalog(null);

readonly helps typed callers; recursive snapshots supply runtime protection. A cast or brand alone supplies no value checks. Language authority.

Verse
Immutable arrays/options and failable admission: show code
job := struct:
    Priority:int
Start(C:char)<transacts>:logic =
    for (A : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"):
        if (C=A):
            return true
    false
Key(K:string)<transacts>:logic =
    if (K.Length<1 or K.Length>8):
        return false
    if (C:=K[0],not Start(C)?):
        return false
    for (C:K):
        if (not Start(C)?):
            var Digit:logic=false
            for (D:"0123456789"):
                if (C=D):
                    set Digit=true
            if (not Digit?):
                return false
    true
Heap(H:?[]job)<transacts>:logic =
    if (Xs:=H?):
        for (I->J:Xs):
            if (J.Priority< -2147483648 or J.Priority>2147483647):
                return false
            if (L:=Xs[2*I+1],L.Priority>J.Priority):
                return false
            if (R:=Xs[2*I+2],R.Priority>J.Priority):
                return false
    true
Bucket(B:?[]?[]job)<transacts>:logic =
    if (Xs:=B?):
        if (Xs.Length>3):
            return false
        for (H:Xs):
            if (not Heap(H)?):
                return false
    true
Admit(Raw:?[string]?[]?[]job)<transacts><decides>:?[string]?[]?[]job =
    if (M:=Raw?):
        for (K->B:M):
            Key(K)?
            Bucket(B)?
    Raw
Append(B:[]?[]job,H:?[]job)<transacts><decides>:[]?[]job =
    Bucket(option{B})?
    Heap(H)?
    Xs:=B+array{H}
    for (I->V:Xs,I>=Xs.Length-3):
        V
# In a failure context: Absent := Admit[false]

This is an immutable value adapter with complete key, nullable-level, heap and length checks. Public functions re-admit raw values; no caller can mutate a returned array or job through an alias. Its map is used for lookup only: a key-order iterator must supply ASCII order rather than Verse insertion order. As with the Elixir adapter, value validation is explicit, not an unforgeable schema type. Language authority.

Zig
Opaque owner with arena-backed deep copies: show code
const std=@import("std");
const Job=struct {priority:i32};
const Heap=[]const Job;
const Bucket=[]const ?Heap;
const Entry=struct {key:[]const u8,value:?Bucket};
const Catalog=struct {entries:[]const Entry}; // implementation-private
fn start(c:u8) bool {
    return c=='_' or (c>='A' and c<='Z') or (c>='a' and c<='z');
}
fn heap(h:?Heap) !?Heap {
    if(h) |xs| for(xs,0..) |j,i| {
        if(i>0 and xs[(i-1)/2].priority<j.priority)return error.Heap;
    };
    return h;
}
fn bucket(a:std.mem.Allocator,b:?Bucket) !?Bucket {
    const xs=b orelse return null;
    if(xs.len>3)return error.Length;
    const out=try a.alloc(?Heap,xs.len);
    for(xs,0..) |h,i| {
        out[i]=if(try heap(h)) |v| try a.dupe(Job,v) else null;
    }
    return out;
}
fn admit(a:std.mem.Allocator,raw:?[]const Entry) !?Catalog {
    const xs=raw orelse return null;
    const out=try a.alloc(Entry,xs.len);
    for(xs,0..) |e,i| {
        if(e.key.len<1 or e.key.len>8 or !start(e.key[0]))return error.Key;
        for(e.key) |c| if(!start(c) and !(c>='0' and c<='9')) {return error.Key;};
        out[i]=.{.key=try a.dupe(u8,e.key),.value=try bucket(a,e.value)};
    }
    std.mem.sort(Entry,out,{},struct {
        fn less(_:void,x:Entry,y:Entry) bool {return std.mem.lessThan(u8,x.key,y.key);}
    }.less);
    for(out,0..) |e,i| if(i>0 and std.mem.eql(u8,out[i-1].key,e.key)) {return error.Key;};
    return .{.entries=out};
}
fn append(a:std.mem.Allocator,b:Bucket,h:?Heap) !Bucket {
    _=try bucket(a,b);_=try heap(h);
    var xs:[4]?Heap=undefined;
    @memcpy(xs[0..b.len],b);xs[b.len]=h;
    const n=b.len+1;
    return (try bucket(a,xs[if(n>3) n-3 else 0..n])).?;
}
const absent:?Catalog=null;

The allocator belongs to a private arena whose owner deinitializes it on refusal and at close. admit duplicates every slice; only the opaque owner crosses the public API. A const slice by itself would still allow mutation through another alias. Language authority.

Trident authority: complete heap shape and invariant, nullable depths, ident class, array length/full, and exact ingot records. Other-language links identify the relevant language or collection contract. The code is authored for this comparison.

12

Literals

A literal uses its destination's declared type. Width suffixes are unnecessary. Unit suffixes attach to the numeral, such as 5kg or 9.81m/s^2.

The destination resolves a shared symbol: 5m means meters for length and minutes for time. A unit literal without the required destination refuses. Duration terms can combine, such as 1h30m.

Trident.tri
i64 wide = 3000000000
f32 narrow = 1.5
duration step = 16ms
duration frame = 1h30m
f64<mass> load = 5kg
f64<angle> turn = -180pi^-1degree
# exact -1 coherent radian: the symbolic pi factors cancel before the one f64 rounding
f64<acceleration> g = 9.81m/s^2
rational oneHalf = 1//2   # construction, never division
f64 precise = 1.5
f32 small = f32(precise)   # exact, or a fault
C.c
int64_t wide = 3000000000LL;
float narrow = 1.5f;
/* no duration or unit type;
   milliseconds as a plain long */
long step_ms = 16;
C#.cs
long wide = 3_000_000_000L;
float narrow = 1.5f;
TimeSpan step =
    TimeSpan.FromMilliseconds(16);
TimeSpan frame = new TimeSpan(1, 30, 0);
C++.cpp
int64_t wide = 3'000'000'000LL;
float narrow = 1.5f;
using namespace std::chrono_literals;
auto step = 16ms;   // <chrono>
auto frame = 1h + 30min;
Cutlass.cut
i64 wide = 3000000000
f32 narrow = 1.5
u8 mask = 0b1010   # 0b beside 0x
duration step = 16ms
duration frame = 1h30m
f64<mass> load = 5kg
f64<acceleration> g = 9.81m/s^2
rational oneHalf = 1//2  # construction, never division
f64 precise = 1.5
f32 small = f32(precise)  # exact, or fails fault.convert KiB, MiB and GiB read only at a byte
# count: region (bytes = 64KiB) { }
# 0x with no digit refuses at lex
Dart.dart
int wide = 3_000_000_000;
double narrow = 1.5; // one float width: no f32
Duration step =
    const Duration(milliseconds: 16);
Duration frame = const Duration(hours: 1, minutes: 30);
// no compound duration literal, and no unit-of-measure type
Elixir.ex
wide = 3_000_000_000  # Integer, arbitrary precision
narrow = 1.5            # Float, always 64-bit
step_ms = 16             # no sub-second unit in Duration; plain integer
frame = Duration.new!(hour: 1, minute: 30)
# f64<family> has no
# literal or built-in type at all — reach for the ex_cldr_units lib
Go.go
wide := int64(3_000_000_000)
narrow := float32(1.5)
step := 16 * time.Millisecond
frame := 90 * time.Minute
// no unit-of-measure type
Haskell.hs
import Data.Int (Int64)
import Data.Time.Clock (NominalDiffTime)

wide :: Int64
wide = 3000000000
narrow :: Float
narrow = 1.5
step :: NominalDiffTime
step = 0.016        -- seconds; no ms/h compound literal
frame :: NominalDiffTime
frame = 90 * 60
-- units need a third-party package such as `units`
Jai.jai
wide   : s64 = 3_000_000_000;
narrow : float32 = 1.5;
hex    :: 0xff;
bin    :: 0b1010;

step_ms :: 16;
// no duration literal and no unit suffix: a millisecond is an s64 you named. Basic's Apollo_Time
// counts femtoseconds and is a library type with library constructors
Java.java
long wide = 3_000_000_000L;
float narrow = 1.5f;
Duration step = Duration.ofMillis(16);
Duration frame = Duration.ofMinutes(90);
// no built-in unit-of-measure type
JavaScript.js
const wide = 3000000000n; // BigInt
const narrow = Math.fround(1.5);
const stepMs = 16;   // plain number
const frameMs = 90 * 60_000;
// no native duration or unit type
Julia.jl
using Dates, Unitful

wide::Int64 = 3_000_000_000
narrow = Float32(1.5)
step = Millisecond(16)
frame = Hour(1) + Minute(30)
load = 5u"kg"
turn = -90u"°"
# bare integer literal is Int
# (64-bit) here, not i32; no
# width suffix exists, same as Trident
Kotlin.kt
val wide: Long = 3_000_000_000L
val narrow: Float = 1.5f
val step = 16.milliseconds
val frame = 1.hours + 30.minutes
// kotlin.time.Duration is the nearest builtin; no unit-of
// -measure type at all
Lua.lua
local wide = 3000000000
local narrow = 1.5
-- Lua 5.4 has integer and float subtypes. No width suffix or duration literal
local step_ms = 16
Luau.luau
local wide = 3_000_000_000
local narrow = 1.5
local hex = 0xff
local bin = 0b1010
local name = "world"
local greet = `hello {name}`
-- _ separators, binary literals and
-- backtick interpolation are Luau's additions to Lua. One number type still, and no duration or
-- unit form
Nim.nim
import std/times

let wide: int64 = 3_000_000_000
let narrow: float32 = 1.5
let step = initDuration(milliseconds = 16)
let frame = initDuration(hours = 1, minutes = 30)
# unit-of-measure needs the third-party `units` package
Objective-C.m
int64_t wide = 3000000000LL;
float narrow = 1.5f;
NSTimeInterval step = 0.016; // secs; no literal duration syntax
NSTimeInterval frame = 90 * 60;
NSMeasurement *load =
    [[NSMeasurement alloc]
      initWithDoubleValue:5
      unit:[NSUnitMass kilograms]];
// units need NSMeasurement, built programmatically only
Perl.pl
my $wide = 3_000_000_000;
my $narrow = 1.5;
# one scalar numeric kind: no width suffix at all
use Time::HiRes qw(time);
my $step_ms = 16;
my $frame_ms = 90 * 60_000;
# no native duration or unit-of-measure literal
PHP.php
<?php
$wide = 3_000_000_000;
$narrow = 1.5;   // one float kind, always a double
$stepMs = 16;     // DateInterval has no ms precision
$frame = new DateInterval('PT1H30M');
// no unit-of-measure type in core; packages such as php-units-of-measure fill it
Python.py
wide = 3_000_000_000
narrow = numpy.float32(1.5)
from datetime import timedelta
step = timedelta(milliseconds=16)
frame = timedelta(hours=1, minutes=30)
# units need a third-party package such as pint
Ruby.rb
wide = 3_000_000_000
narrow = 1.5
# one Integer kind (arbitrary precision) and one Float
# kind (f64); no width suffix
# exists at all
step = 0.016          # seconds
frame = 90 * 60         # seconds; no native duration type units need a gem such as
# 'ruby-units'
Rust.rs
let wide: i64 = 3_000_000_000;
let narrow = 1.5_f32;
let step = Duration::from_millis(16);
let frame = Duration::from_secs(90 * 60);
// units need the `uom` crate
Scala.scala
val wide: Long = 3_000_000_000L
val narrow: Float = 1.5f
import scala.concurrent.duration._
val step = 16.millis
val frame = 1.hour + 30.minutes
// no unit-of-measure type; squants is a 3rd-party lib
Swift.swift
let wide: Int64 = 3_000_000_000
let narrow: Float = 1.5
let step = Duration.milliseconds(16)
let frame = Duration.seconds(90 * 60)
// no compound "1h30m" literal; combine calls or add durations no built-in unit-of-measure type
TypeScript.ts
const wide: bigint = 3_000_000_000n;
const narrow: number = 1.5; // same type as any number
const step = Temporal.Duration.from({ milliseconds: 16 });
const frame = Temporal.Duration.from({ hours: 1, minutes: 30 });
// Temporal needs runtime support or a polyfill plus matching declarations. TypeScript types supply
// no runtime. No built-in unit-of-measure type.
Verse.verse
Wide : int = 3000000000
Narrow : float = 1.5
Text : string = "text"
Ch : char = 'c'

# an int LITERAL still has to fit 64 bits to be written, while the value it makes is arbitrary
# precision. No width suffix, no duration literal and no unit suffix exist
Zig.zig
const std = @import("std");

const wide: i64 = 3_000_000_000; // adapts to i64, no width suffix at all — same rule as Trident
const narrow: f32 = 1.5; // adapts to f32
const step_ns: i64 =
    16 * std.time.ns_per_ms;
const frame_ns: i64 =
    90 * std.time.ns_per_min;
// no duration type at all: just a plain i64 of nanoseconds, and no unit-of-measure type either
13

Text, runes, and bytes

Text is strict UTF-8. A length or index must name bytes, Unicode scalars, or grapheme clusters. Bare text indexing and count() refuse; ASCII text admits them because those units agree.

A rune is one Unicode scalar. A buffer is bytes. Neither silently converts to text. text(n) names a numeric text conversion.

Text policies prepare source writes in the declared order, then validate the result. newlines=lf, crlf, and cr select line endings. Stored and received values are validated without repair.

Interpolated text evaluates each hole once, in source order, through explicit text conversion. A privacy-qualified hole preserves its level in the completed text.

Line endings are a declared preparation

Quoted from text value handling: Line-ending conversion

// Source \n denotes an actual LF, not two printable characters.
text<newlines=lf> portable = "one\r\ntwo\rthree\nfour"
// "one\ntwo\nthree\nfour"

text<newlines=crlf> windowsFile = "one\ntwo\rthree\r\nfour"
// Windows-style CRLF: "one\r\ntwo\r\nthree\r\nfour"

text<newlines=cr> legacyFile = "one\ntwo\r\nthree"
// "one\rtwo\rthree"
Trident.tri
text word = "héllo"
text<class=ascii> code = "AB-12"
rune first = code[0]      # U+0041
i64 n = code.count()      # 5
i64 bytes = t.text.bytes.count(word)
i64 marks = t.text.graphemes.count(word)
# 6 bytes, 5 graphemes; word[0] and
# word.count() refuse and name the unit
text line = `n={n}`
# "n=" + n refuses, naming text(n)
rune next = rune(u32(first) + 1)
# first + 1 refuses: no rune arithmetic
buffer raw = 0x68656c6c6f
# raw == "hello" refuses: bytes are
# not text until a decoder says so
C.c
const char *s = "héllo";
strlen(s);   /* 6: bytes, up to the NUL */
s[1];        /* the byte 0xC3, half an é */
/* C has no text type: char * is bytes,
   and every function in <string.h>
   counts and indexes bytes. A scalar
   count is a decode loop (or mbstowcs,
   which follows the locale), graphemes
   are not in the library, and nothing
   tells text from bytes */
C#.cs
string s = "héllo";
s.Length;              // 5 UTF-16 units
"\U0001F600".Length;   // 2, one emoji
s[1];                  // 'é', a UTF-16 char
s.EnumerateRunes().Count();   // 5 scalars
new StringInfo(s).LengthInTextElements;
                       // 5 graphemes
ReadOnlySpan<byte> b = "héllo"u8;  // 6
// bytes are their own type and scalars and graphemes are there by name, but Length
// and s[i] silently answer UTF-16 units
C++.cpp
std::string s = "héllo";
s.size();      // 6: bytes
s[1];          // '\xC3', half an é
std::u8string u = u8"héllo";
u.size();      // 6: still bytes
// std::string is text and bytes at once.
// C++20's char8_t gives UTF-8 its own
// type, and size() and [i] on it still
// answer bytes. The standard library has no scalar or grapheme count
Cutlass.cut
text word = "héllo"
text<class=ascii> code = "AB-12"
rune first = code[0]      # U+0041
i64 n = code.count()      # 5
text line = `n={n}`
rune next = rune(u32(first) + 1)
# rune is its own kind, not u32, so the next scalar goes through u32
u8[6] bytes = "héllo"     # 6 bytes
span<u16> wide = "héllo"  # UTF-16 a literal reads at its destination;
# bytes == word refuses: no conversion
# is implicit. cut.text carries the MerTekTypes text law
Dart.dart
final s = 'héllo';
s.length;              // 5 UTF-16 units
'\u{1F600}'.length;    // 2, one emoji
s[1];                  // 'é', one unit
s.runes.length;        // 5 scalars
utf8.encode(s).length; // 6 bytes
// package:characters (Dart team)
s.characters.length;   // 5 graphemes
// bytes are a Uint8List, apart from
// String; length and s[i] silently
// answer UTF-16 units
Elixir.ex
s = "héllo"
String.length(s)    # 5 graphemes
byte_size(s)        # 6 bytes
length(String.codepoints(s))   # 5
String.at(s, 1)     # "é", a grapheme
<<104, 105>> == "hi"   # true String.length is graphemes and byte_size is bytes, so no count answers
# a code unit by accident. What is missing is the type: a string is a binary, so bytes and text are
# one value
Go.go
s := "héllo"
len(s)                     // 6 bytes
s[1]                       // 0xc3, a byte
utf8.RuneCountInString(s)  // 5 runes
[]rune(s)[1]               // 'é'
b := []byte(s)             // its own type
// b == s does not compile, which is right.
// len and s[i] silently answer bytes, a
// string may hold invalid UTF-8, and graphemes are not in the std library
Haskell.hs
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import qualified Data.ByteString as B
t = T.pack "héllo"
T.length t                  -- 5 Chars
T.index t 1                 -- 'é'
B.length (E.encodeUtf8 t)   -- 6 bytes
-- Text counts Char (scalars) and ByteString is its own type. Graphemes are not in base or text, and
-- under OverloadedStrings a ByteString literal truncates each Char to 8 bits
Jai.jai
s := "héllo";
print("%\n", s.count);   // 6: bytes
c := s[1];               // 0xC3, a u8
// string is a count and a pointer to
// bytes, so .count is bytes and [i] is
// one byte, with no unit named at either. A scalar count is a decode loop over those bytes, and
// there is no grapheme count
Java.java
String s = "héllo";
s.length();              // 5 UTF-16 units
"😀".length(); // 2, one emoji
s.charAt(1);             // 'é', a char
s.codePointCount(0, s.length()); // 5
s.getBytes(UTF_8).length;        // 6
// byte[] is its own type. length() and
// charAt() silently answer UTF-16 units;
// graphemes go through BreakIterator
JavaScript.js
const s = "héllo";
s.length;             // 5 UTF-16 units
"\u{1F600}".length;   // 2, one emoji
s[1];                 // "é", one unit
[...s].length;        // 5 code points
new TextEncoder().encode(s).length; // 6
const seg = new Intl.Segmenter();
[...seg.segment(s)].length; // 5 graphemes
// Uint8Array is its own type; length and
// s[i] silently answer UTF-16 units
Julia.jl
s = "héllo"
length(s)        # 5 Chars (scalars)
ncodeunits(s)    # 6 bytes
s[2]             # 'é', starting at byte 2
s[3]             # StringIndexError
using Unicode
length(graphemes(s))   # 5
# length counts Chars, but s[i] takes a
# byte offset: s[3] lands inside the é
# and throws at run time. Bytes are a
# Vector{UInt8}, apart from String
Kotlin.kt
val s = "héllo"
s.length               // 5 UTF-16 units
"😀".length  // 2, one emoji
s[1]                   // 'é', a Char
s.codePointCount(0, s.length)  // 5
s.toByteArray().size   // 6 bytes (UTF-8)
// ByteArray is its own type. length and
// s[i] silently answer UTF-16 units, and
// graphemes need java.text.BreakIterator
Lua.lua
local s = "héllo"
print(#s)            --> 6 (bytes)
print(s:byte(2))     --> 195, half an é
print(utf8.len(s))   --> 5 (scalars)
print(s[2])          --> nil, no index
-- a Lua string is bytes: # counts them and
-- sub() cuts them. The utf8 library (5.3+)
-- counts scalars by name; there is no grapheme count and no bytes type apart from string
Luau.luau
local s = "héllo"
print(#s)                 --> 6 (bytes)
print(utf8.len(s))        --> 5 (scalars)
print(string.byte(s, 2))  --> 195
local b = buffer.fromstring(s)
print(buffer.len(b))      --> 6
-- # counts bytes silently. buffer is a real
-- bytes type, but a string is still bytes too. Roblox adds utf8.graphemes to the utf8 library
Nim.nim
import std/unicode
let s = "héllo"
echo s.len        # 6 bytes
echo s[1].ord     # 195, half an é
echo s.runeLen    # 5 runes
echo s.runeAt(1)  # é (at byte offset 1)
# len and s[i] silently answer bytes, and
# string doubles as the bytes type. std/unicode names runes by function; graphemes are left to you
Objective-C.m
NSString *s = @"héllo";
s.length;                // 5 UTF-16 units
[s characterAtIndex:1];  // 0xE9, a unichar
[s lengthOfBytesUsingEncoding:
    NSUTF8StringEncoding];   // 6 bytes
NSData *d = [s dataUsingEncoding:
    NSUTF8StringEncoding];   // own type
/* length and characterAtIndex: silently
   answer UTF-16 units. Graphemes go
   through the composed character
   sequence methods on NSString */
Perl.pl
use utf8;
my $s = "héllo";
length $s;              # 5 code points
substr $s, 1, 1;        # "é"
my @g = $s =~ /\X/g;    # 5 graphemes
use Encode;
length encode('UTF-8', $s);   # 6 bytes
# without `use utf8` the same literal is six bytes and length says 6. Text and bytes are one scalar
# type, and nothing records whether a string was decoded
PHP.php
$s = "héllo";
strlen($s);            // 6 bytes
$s[1];                 // "\xC3", one byte
mb_strlen($s);         // 5 code points
grapheme_strlen($s);   // 5 (intl)
// a PHP string is bytes, so strlen and
// $s[i] silently answer bytes, and there
// is no separate bytes type. mb_* and
// grapheme_* name the other units
Python.py
s = "héllo"
len(s)                   # 5 code points
s[1]                     # 'é'
len(s.encode("utf-8"))   # 6 bytes
len("\U0001F44D\U0001F3FD")  # 2, one
                             # grapheme
"a" + b"a"               # TypeError
"a" == b"a"              # False, no error
# bytes is its own type and never joins
# str. len and s[i] count code points;
# graphemes take the regex package's \X
Ruby.rb
s = "héllo"
s.length                    # 5 code points
s.bytesize                  # 6 bytes
s[1]                        # "é"
s.grapheme_clusters.length  # 5
raw = s.b                   # ASCII-8BIT each unit has its own name, and length is code points, not
# graphemes. Bytes are a String too, told apart by an encoding tag rather than by type
Rust.rs
let s = "héllo";
s.len();            // 6: bytes
s.chars().count();  // 5 scalars
s[1];               // does not compile
&s[1..2];           // panics: not a char boundary
s.as_bytes();       // &[u8], its own type
// no integer index and a separate &[u8].
// But len() answers bytes with no unit in its name, and a range index is in bytes and fails only at
// run time. Graphemes: the unicode-segmentation crate
Scala.scala
val s = "héllo"
s.length                     // 5 UTF-16
s(1)                         // 'é', a Char
s.codePointCount(0, s.length)  // 5
s.getBytes("UTF-8").length   // 6 bytes
// Array[Byte] is its own type. length and
// s(i) silently answer UTF-16 units, as
// java.lang.String does underneath, and graphemes need java.text.BreakIterator
Swift.swift
let s = "héllo"
s.count                  // 5 Characters
s.unicodeScalars.count   // 5 scalars
s.utf8.count             // 6 bytes
s.utf16.count            // 5 UTF-16 units
s[1]                     // does not compile
s[s.index(after: s.startIndex)]  // "é"
let b = Array(s.utf8)    // [UInt8]
// each view names its unit and String has no integer subscript. count alone names none: it answers
// Characters, which are grapheme clusters
TypeScript.ts
const s: string = "héllo";
s.length;             // 5 UTF-16 units
s[1];                 // "é", one unit
"\u{1F600}".length;   // 2, one emoji
const b: Uint8Array =
  new TextEncoder().encode(s);  // 6 bytes
b === s;   // error: types do not overlap
// the checker keeps bytes and string apart. Nothing types the unit: length is UTF-16 units, as in
// JavaScript
Verse.verse
S : string = "héllo"
N := S.Length      # 6: UTF-8 code units
if (C := S[1]):    # a char, one byte of é
    Print("in range")
# a string is []char, and char is one
# UTF-8 code unit, so Length counts bytes
# and [i] fails only past the end. char32
# holds a code point, but a string is not a sequence of them, and there is no bytes type apart from
# string
Zig.zig
const s = "héllo";
s.len;        // 6: bytes
s[1];         // 0xc3, a u8
const n = try std.unicode
    .utf8CountCodepoints(s);   // 5
// Zig has no string type: a literal is a pointer to a u8 array, so text and bytes are one type and
// every length is bytes. std.unicode counts codepoints by name; graphemes are not in std

Interpolation evaluates each hole once

Trident.tri
i32 n = 1
text line = `{n++}{n++}`
assert (line == "12", "holes run once in order")
assert (n == 3, "each hole evaluates once")

Interpolation converts each hole once

Text holes run from left to right.tri
text main(text[] argv) {
    i32 count = 3
    return `items={count}`
}
# The hole is text(count), not a format specifier.
# A literal brace is written {{ or }}.

Interpolation law.

14

Conversions

A conversion names the destination kind. It returns an exact value or faults. i32(digits) parses text under the same rule.

Loss is explicit: t.math.castRounding, castSaturating, and castWrapping name different results. Assignment never supplies one of those conversions.

Trident.tri
f64 half = 0.5
f32 single = f32(half)    # exact
f64 tenth = 0.1
# f32(tenth) faults: 0.1 is not on
# the f32 grid, so the loss is named
f32 near = t.math.castRounding(tenth, "f32")
i32 big = 300
u8 clamp = t.math.castSaturating(big, "u8")            # 255
u8 wrap = t.math.castWrapping(big, "u8")            # 44
i64 wide = big * u32(2)   # joins at i64
# i64 w = big refuses: a destination
# never widens, even exactly
text digits = "42"
i32 n = i32(digits)       # parses or faults
C.c
int big = 300;
uint8_t w = big;        /* 44, no error */
uint8_t s = big > 255 ? 255 : big;
float f = 0.1;          /* rounds */
long r = lround(2.5);   /* 3, named */
int bad = (int)1e20;    /* undefined */
/* narrowing is implicit and silent:
   -Wconversion warns, nothing checks,
   and every range test is hand-written */
C#.cs
int big = 300;
byte a = (byte)big;            // 44
byte b = checked((byte)big);   // throws
byte c = byte.CreateChecked(big); // throws
byte d = byte.CreateSaturating(big); // 255
byte e = byte.CreateTruncating(big); // 44
float f = (float)0.1;          // rounds
double g = long.MaxValue;      // rounds
// .NET 7 names every mode. The plain cast
// wraps unless checked is on (it is off by default), and long to double is implicit
C++.cpp
int big = 300;
auto w = static_cast<uint8_t>(big); // 44
uint8_t b{big};     // ill-formed: narrows
std::in_range<uint8_t>(big);       // false
std::saturate_cast<uint8_t>(big);  // 255
float f = 0.1;      // rounds
long r = std::lround(2.5);         // 3
// braces refuse narrowing at compile time; in_range is C++20, saturate_cast
// C++26. static_cast wraps, and no checked
// cast is standard (gsl::narrow throws)
Cutlass.cut
f64 half = 0.5
f32 single = f32(half)    # exact
f64 tenth = 0.1
# f32(tenth) fails with fault.convert:
# 0.1 is not on the f32 grid
i32 big = 300
u8 wrap = u8(big & 0xFF)  # 44, exact
# no rounding, saturating or wrapping conversion is named: a wrap is a mask, and a clamp is written
# out
i64 wide = big * u32(2)   # joins at i64
# i64 w = big refuses: no conversion
# is implicit, even an exact one
u32 pattern = bits<u32>(single)
# reinterprets; changes no bit
text digits = "42"
i32 n = i32(digits)  # parses, or fails fault.convert
Dart.dart
final big = 300;
big.toUnsigned(8);        // 44
big.clamp(0, 255);        // 255
2.7.toInt();              // 2, truncates
2.5.round();              // 3
double.nan.toInt();  // UnsupportedError
Uint8List(1)[0] = big;    // stores 44 one int type, so no narrowing exists to check. Each mode has
// a method, but the plain toInt() truncates, typed data wraps, and toDouble() rounds, silently
Elixir.ex
big = 300
<<big::8>>            # <<44>>, truncated
min(max(big, 0), 255)   # 255, by hand
trunc(2.7)              # 2
round(2.5)              # 3
Integer.parse("42")     # {42, ""}
# integers are unbounded, so narrowing only happens at a bitstring segment, and that truncates
# silently. A float becomes an integer only through a call that names its rounding
Go.go
big, x := 300, 2.7
w := uint8(big)            // 44, silently
t := int(x)                // 2, truncates
f := float32(x)            // rounds
r := math.Round(2.5)       // 3, a float64
s := uint8(min(big, 255))  // 255, by hand
n, err := strconv.Atoi("42")
// uint8(300) on a constant is a compile
// error and nothing converts implicitly,
// but at run time every conversion is silent. There is no checked form
Haskell.hs
import Data.Bits (toIntegralSized)
big = 300 :: Int
tenth = 0.1 :: Double
fromIntegral big :: Word8   -- 44
toIntegralSized big :: Maybe Word8
                            -- Nothing
round 2.5 :: Int            -- 2, to even
realToFrac tenth :: Float   -- rounds fromIntegral is the everyday spelling and wraps silently. The
-- checked form is in base; saturation is hand-written
Jai.jai
big: s32 = 300;
a := cast(u8) big;           // checked
b := cast,trunc(u8) big;     // 44
c := cast,no_check(u8) big;  // unchecked a narrowing cast is bounds-checked at run time, and a
// build option can turn the check off. Truncation has its own spelling; saturation does not
Java.java
long big = 3_000_000_000L;
int w = (int) big;       // -1294967296
Math.toIntExact(big);    // throws
Math.clamp(big, 0, 255); // 255, Java 21
float f = (float) 0.1;   // rounds
float g = big;   // implicit, may round
long r = Math.round(2.5);    // 3
int m = (int) 1e20;   // Integer.MAX_VALUE casts wrap integers and saturate doubles without a word;
// toIntExact is the checked form, for long to int only
JavaScript.js
const big = 300;
new Uint8Array([big])[0];         // 44
new Uint8ClampedArray([big])[0];  // 255
BigInt.asUintN(8, 300n);          // 44n
Math.fround(0.1);    // float32, rounded
BigInt(2.5);         // RangeError
Number(2n ** 64n + 1n);  // rounds
Number("42px");          // NaN, no throw the modes are named, but scattered over typed arrays, Math
// and BigInt, and the everyday Number() never refuses a loss
Julia.jl
big = 300
UInt8(big)          # InexactError
Int(2.7)            # InexactError
big % UInt8         # 0x2c (44), wraps
clamp(big, UInt8)   # 0xff, saturates
round(Int, 2.5)     # 2, to even
trunc(Int, 2.7)     # 2
Float32(0.1)        # rounds, silently integer and float-to-integer conversion is exact or throws,
# and each lossy mode is named. Narrowing a float rounds
Kotlin.kt
val big = 300
big.toByte()              // 44, silently
big.toUByte()             // 44u
big.coerceIn(0, 255)      // 255
2.7.toInt()               // 2, truncates
2.5.roundToInt()          // 3
1e20.toInt()              // Int.MAX_VALUE
Math.toIntExact(3_000_000_000L) // throws nothing converts implicitly, which is the good half.
// toByte() wraps, and the checked form is Java's, on the JVM only
Lua.lua
local big = 300
math.tointeger(2.5)   -- nil
math.tointeger(3.0)   -- 3
local x = 2.5 | 0     -- error: no integer
                      -- representation
math.floor(2.7)       -- 2
big & 0xFF            -- 44, by mask
math.min(big, 255)    -- 255, by hand
-- Lua 5.3 and later: a float becomes an integer only when exact. Integer to float rounds past 2^53
-- without a word
Luau.luau
local big = 300
bit32.band(big, 0xFF)     -- 44
math.clamp(big, 0, 255)   -- 255
math.round(2.5)           -- 3
math.floor(2.7)           -- 2
tonumber("42")            -- 42 or nil
-- every number is a double, so there is no narrower kind to convert into and nothing to check.
-- Luau's math adds clamp and round over Lua's; an integer past 2^53 still rounds silently
Nim.nim
let big = 300
let a = int8(big)        # RangeDefect
let b = uint8(big and 0xFF)   # 44
let c = uint8(clamp(big, 0, 255)) # 255
let r = round(2.5)       # 3.0, std/math
let f = float32(0.1)     # rounds a narrowing conversion is range-checked at run time unless built
# with -d:danger. Wrapping and saturating are written with a mask and clamp
Objective-C.m
int big = 300;
uint8_t w = (uint8_t)big;   // 44
NSNumber *n = @(big);
n.unsignedCharValue;        // no check
float f = 0.1;              // rounds
long r = lround(2.5);       // 3
[@"42px" integerValue];     // 42
// C's conversions, unchanged, and the Foundation accessors add no range check; every test is
// written by hand
Perl.pl
my $big = 300;
pack("C", $big);         # chr(44), warns
int(2.7);                # 2, truncates
sprintf("%.0f", 2.5);    # "2", to even
"42px" + 0;              # 42, and a warning
# a scalar holds an integer or a float and converts on demand. Nothing refuses a lossy conversion;
# use warnings is the only thing that notices
PHP.php
$big = 300;
(int) 2.7;               // 2, truncates
(int) "42px";            // 42, silently
round(2.5);              // 3, as a float
min(max($big, 0), 255);  // 255, by hand
$big & 0xFF;             // 44, by mask
PHP_INT_MAX + 1;         // a float now int and float are the only numeric types, casts never
// refuse, and an int that overflows becomes a float without a word
Python.py
big = 300
big.to_bytes(1, "big")   # OverflowError
struct.pack("B", big)    # struct.error
big & 0xFF               # 44, by mask
min(max(big, 0), 255)    # 255, by hand
int(2.7)                 # 2, truncates
round(2.5)               # 2, to even
float(2**53 + 1)         # rounds silently int is unbounded, so narrowing happens only at a byte
# boundary, and there it is checked. Leaving a float or entering one drops bits without a word
Ruby.rb
big = 300
[big].pack("C")          # ",", byte 44
big.clamp(0, 255)        # 255
2.7.to_i                 # 2, truncates
2.5.round                # 3
2.5.round(half: :even)   # 2
(2**53 + 1).to_f         # rounds
Integer("42px")          # ArgumentError integers are unbounded and the rounding modes are named,
# but no numeric conversion refuses a loss: to_i truncates, to_f rounds, pack wraps
Rust.rs
let big: i32 = 300;
let w = big as u8;               // 44
let c = u8::try_from(big);       // Err
let s = big.clamp(0, 255) as u8; // 255
let r = 2.5_f64.round() as i32;  // 3
let f = 0.1_f64 as f32;          // rounds
let x = 1e20_f64 as i32;   // saturates try_from is the checked form and Clippy's pedantic lints
// push toward it, but `as` is the everyday spelling: it wraps, saturates or rounds without a word
Scala.scala
val big = 300
big.toByte              // 44
big.isValidByte         // false
0.1.toFloat             // rounds
1e20.toInt              // Int.MaxValue
math.round(2.5)         // 3
BigDecimal(2.5).toIntExact  // throws Java's conversions under better names: toByte wraps and toInt
// saturates a Double, silently. The checks exist and are the ones you remember to call
Swift.swift
let big = 300, tenth = 0.1, x = 2.7
UInt8(big)                     // traps
UInt8(exactly: big)            // nil
UInt8(clamping: big)           // 255
UInt8(truncatingIfNeeded: big) // 44
Int(x)                  // 2, truncates
Float(tenth)            // rounds
Float(exactly: tenth)   // nil integer narrowing traps and each lossy integer mode is named.
// Converting from a float drops bits silently unless you write exactly:
TypeScript.ts
const big: number = 300;
BigInt(big);              // 300n, exact
BigInt(2.5);              // RangeError
BigInt.asIntN(8, 300n);   // 44n
Number(2n ** 64n + 1n);   // rounds
Math.fround(0.1);         // to float32 the types stop at number and bigint, with no u8 or f32 to
// convert into, so every check and mode is JavaScript's.
// Mixing them (1n + 1) is a type error
Verse.verse
Big:int = 300
AsFloat:float = Big * 1.0  # int to float
if (Down := Floor[2.7]):   # 2
    Print("{Down}")
if (Near := Round[2.5]):
    Print("{Near}")
# a float becomes an int only through
# Floor[], Ceil[] or Round[], each naming
# its rounding and each failable. Verse has no narrow int widths, so nothing wraps or saturates
Zig.zig
// big: i32 and x: f64, both run-time
const a: u8 = @intCast(big);  // checked
const b = std.math.cast(u8, big); // null
const c: u8 = @truncate(big);     // 44
const d = std.math.lossyCast(u8, big);
                                  // 255
const f: f32 = @floatCast(x);     // rounds
// @intCast panics in Debug and ReleaseSafe
// and is illegal behavior in ReleaseFast; std.math.cast checks in every mode. Narrowing a float is
// silent

Calls can take their result type from the destination

Some native signatures take an argument's complete type. Others take the declared result type from their destination. Each compiled call still has concrete types. This supplies no any value and no source-level generic function.

A runtime dimension meets a declared length.tri
text main(text[] argv) {
    quantity<amount=f64> q = t.quantity.of(2.5, "km", "f64")
    f64<family=length> distance = t.quantity.admit(q)
    return text(distance)
}
# A wrong dimension faults before publication. No destination, or an f32 destination, refuses.

Signature law and examples.

The destination supplies a complete type

  1. f64<family=length>
    The author states width and dimension.
  2. t.quantity.admit(q)
    Compile checks the result shape and amount kind.
  3. Runtime check
    The supplied dimension must fit before a value publishes.
A destination can type this call; it never silently rounds or changes a dimension.
CallType sourceBoundary
t.quantity.admitA declared rank-zero rounded scalar destination.The amount kind must match; runtime checks dimension and family.
t.data.rows.arrayField / arrayFieldsA declared array destination with one / two dynamic outer layers.Projected values must meet the concrete element type. No destination refuses.
t.draft.parseA record destination, or chest where no record governs.Record parsing and fallback follow the selected signature.

Complete signature rules; catalog ownership.

15

Records

A plain chest is a self-describing document. Each literal field states its type. A plain Chest write can add a field, so a misspelled name may create a new field.

An outline now declares an open structural record. It carries required field types, defaults, included fields, and checks. Extra fields survive a structural fit. Record names alone create no nominal barrier.

An ingot declares an exact, immutable record. Its source declarations, field reads, and write refusals work. Default construction, broader whole-value admission, Host entry admission, and typed trove columns still have separate owners.

An ingot has exact fields and no field writes.tri
ingot span {
    i64 start = 0
    i64 end = 0
    check ordered = start <= end
}
i64 main(text[] argv) {
    span held = { start = 2, end = 5 }
    return held.end - held.start
}
# held.start = 3 refuses: an ingot is immutable.

Current ingot case.

One extra field, two record rules

Outline: required shape

{name, level} fits an outline that requires name.

The value keeps level.

Ingot: exact shape

An ingot that declares only name refuses an extra level.

Every field and nested write is also refused.

Record compatibility depends on complete field types and checks. A matching name alone proves no fit.
Trident.tri
const chest defaults = {
    u16 code = 7
    text label = "ready"
}

chest sample = defaults
text label = sample.label
sample.label =
    label + " and revised"
C.c
typedef struct {
    uint16_t code;
    char label[32];
} Defaults;

Defaults sample = { .code = 7, .label = "ready" };
strcat(sample.label, " and revised");
C#.cs
record Defaults(int Code, string Label);

var d = new Defaults(7, "ready");
var sample = d with {
    Label = d.Label + " and revised" };
C++.cpp
struct Defaults {
    uint16_t code = 7;
    std::string label = "ready";
};

Defaults sample;
sample.label += " and revised";
Cutlass.cut
const chest defaults = {
    u16 code = 7
    text label = "ready"
}

chest sample = defaults
text label = sample.label
sample.label =
    label + " and revised"

struct Status {
    u16 code = 7, text label = "ready", }
Status s = {}
s.label = s.label + " and revised"
# chest carries as a kind; a struct's fields are declared, and a write to an undeclared name refuses
# at compile
Dart.dart
({int code, String label}) defaults =
    (code: 7, label: "ready");

var sample = defaults;
sample = (code: sample.code, label: "${sample.label} and revised");
// Records are immutable value types: "writing" builds a new record; a typo can't silently add a
// field the way chest does
Elixir.ex
defmodule Defaults do
  defstruct code: 7, label: "ready"
end

sample = %Defaults{}
label = sample.label
sample = %Defaults{sample |
  label: label <> " and revised"}
# update syntax only rebinds
# known keys; %{sample | oops: 1}
# raises KeyError at runtime
# — a typo can't silently
# add a field like Trident's chest write does
Go.go
type Defaults struct {
    Code  uint16
    Label string
}

sample := Defaults{Code: 7, Label: "ready"}
sample.Label += " and revised"
Haskell.hs
data Defaults = Defaults
  { code  :: Int
  , label :: String
  } deriving Show

defaults :: Defaults
defaults = Defaults
  { code = 7, label = "ready" }

sample :: Defaults
sample = defaults
  { label = label defaults
      ++ " and revised" }
-- immutable: "writing" builds a new record; a misspelled field name refuses to compile — the
-- opposite of Trident's silent define-on-write
Jai.jai
Defaults :: struct {
    code  : u16 = 7;
    label : string = "ready";
}

sample := Defaults.{};
sample.label = "revised";
// members are fixed at compile time and carry defaults. #as gives one member subtyping, and SOA
// flips the whole layout to struct-of-arrays without touching a single use site
Java.java
record Defaults(int code, String label) {}

var d = new Defaults(7, "ready");
var sample = new Defaults(d.code(), d.label() + " and revised");
// immutable: "writing" builds a new one
JavaScript.js
const defaults = { code: 7, label: "ready" };
const sample = { ...defaults };
sample.label += " and revised";
// plain object: no schema, any field, any type, any time
Julia.jl
Base.@kwdef mutable struct Defaults
    code::UInt16 = 7
    label::String = "ready"
end

sample = Defaults()
sample.label *= " and revised"
# fields are fixed and typed;
# sample.lable = "x" (typo)
# errors instead of silently creating a new field
Kotlin.kt
data class Defaults(var code: Int = 7, var label: String = "ready",)

val sample = Defaults()
sample.label += " and revised"
// mutable via `var` fields; idiomatic Kotlin favors `val` fields plus copy() instead
Lua.lua
local defaults = { code = 7, label = "ready" }
local sample = defaults
sample.label = sample.label
    .. " and revised"
-- table: any field, any type; a typo just adds a new key
Luau.luau
type Defaults = {
    code: number, label: string, }

local sample: Defaults = {
    code = 7, label = "ready", }
sample.label ..= " and revised"
-- the value is a plain table; the type is an alias the checker applies over it. In strict mode a
-- mistyped field
-- IS an error — in nonstrict it
-- silently adds a new key, as in Lua
Nim.nim
type Defaults = object
  code: uint16 = 7
  label: string = "ready"

var sample = Defaults()
sample.label &= " and revised"
# closed schema: an unknown field name refuses to compile, unlike a chest
Objective-C.m
@interface Defaults : NSObject
@property uint16_t code;
@property (copy) NSString *label;
@end

Defaults *sample = [Defaults new];
sample.code = 7;
sample.label = @"ready";
sample.label = [sample.label
    stringByAppendingString:
      @" and revised"];
// fixed @property list: a typo'd selector fails to compile
Perl.pl
my %defaults = (code  => 7, label => "ready",);
my %sample = %defaults;
$sample{label} .=
    " and revised";
# plain hash: any key, any type, any time - a typo just adds a field
PHP.php
<?php
class Defaults {
    public function __construct(public int $code = 7, public string $label = "ready",) {}
}

$sample = new Defaults();
$sample->label .= " and revised";
// unlike Trident, writing an undeclared property is refused
// (deprecated in 8.2, fatal
// later) — never silently added
Python.py
from dataclasses import dataclass

@dataclass
class Defaults:
    code: int = 7
    label: str = "ready"

sample = Defaults()
sample.label += " and revised"
Ruby.rb
require "ostruct"

defaults = OpenStruct.new(code: 7, label: "ready")
sample = defaults.dup
sample.label += " and revised"
# any attribute, any time — a typo just defines a new one, never refuses
Rust.rs
struct Defaults { code: u16, label: String }

let mut sample = Defaults {
    code: 7, label: "ready".into() };
sample.label += " and revised";
Scala.scala
case class Defaults(code: Int = 7, label: String = "ready")

val sample = Defaults()
val revised = sample.copy(label = sample.label + " and revised")
// immutable: copy() builds a new instance, like C#'s `with`
Swift.swift
struct Defaults {
    var code: UInt16 = 7
    var label = "ready"
}

var sample = Defaults()
sample.label += " and revised"
// a typo'd field name refuses to compile, never silently creates a new field
TypeScript.ts
interface Defaults {
  code: number;
  label: string;
}
const defaults: Defaults = { code: 7, label: "ready" };
const sample = { ...defaults };
sample.label += " and revised";
// shape is checked only on a literal; a typo through `any` or a spread target silently adds a new
// key
Verse.verse
defaults := struct:
    Code<public> : int = 7
    Label<public> : string = "ready"

Sample : defaults = defaults{Code := 7}

# a struct is a VALUE and immutable, so there is no field write at all. The reference form is
# `class`, and only its `var` members can be `set`
Zig.zig
const Defaults = struct {
    code: u16 = 7, label: []const u8 = "ready", };

var sample = Defaults{};
sample.label = "revised"; // real concatenation would need an allocator (allocPrint) unlike
// Trident's chest, fields are fixed at compile time: a write to an undeclared name is
// a compile error, never a
// silent new field
16

Troves and checked reads

A trove combines an outline with a stored collection. Known columns allow the compiler to reject a misspelled field name. Complete field types and structural rules determine the fit.

The current field-list expect checks presence and exact types before it binds names. Its failure arm must leave. Whole-value expect has a proved trove-contract slice.

A check row is a closed relation over declared fields. Invalid known literals refuse at compile. Runtime publication checks its candidate before it replaces the holder. New source/config parity and storage integration remain with their owning rows.

Trident.tri
trove user { text name, text email }
user u = { name = "Ada", email = "ada@example.org" }
u.email = "ada@example.com"
text n = u.name
# u.emial = "x" refuses at compile:
#   no column emial; admitted:
#   name email
# a record missing required fields refuses

text describe(chest doc) {
    expect { text name, i64 age } = doc else {
        return "incomplete"
    }
    return name + " is " + text(age)
}
# an absent or mistyped field takes the else arm, which must leave
C.c
/* jansson; the C library has no JSON */
json_t *doc = json_loads(text, 0, &err);
const char *name; int age;
if (!doc || json_unpack(doc, "{s:s, s:i}", "name", &name, "age", &age) != 0)
    return "incomplete";
/* the format names each key's type, and a
   missing or mistyped key returns -1. A
   typo in a key string is a run-time
   miss, not a compile error */
C#.cs
class User {
  public required string Name { get; init; }
  public required int Age { get; init; }
}
User u;
try { u = JsonSerializer.Deserialize<User>(json, opts)!; }
catch (JsonException) { return "incomplete"; }
// System.Text.Json is in the BCL: a missing required key or a string for the int throws. A JSON
// null still binds to Name
C++.cpp
// nlohmann/json; the standard has none
struct User { std::string name; int age; };
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, name, age)
try {
  auto u = json::parse(text).get<User>();
} catch (const json::exception&) {
  return "incomplete";
}
// the macro reads fields with at(): a missing key or wrong type throws, and a misspelled field
// refuses at compile
Cutlass.cut
struct User { text name, text email }
User u = { name = "Ada", email = "ada@example.org" }
u.email = "ada@example.com"
text n = u.name
# u.emial = "x" refuses at compile:
#   User declares no field emial
# trove refuses at parse: write
#   struct Name { ... }, or a chest
# no expect and no check rows: the
#   docs decide no checked read of a
#   chest at declared types.
#   cut.mem.read<T> validates bytes
#   at a declared-layout struct, or
#   fails with fault.convert
Dart.dart
final doc = jsonDecode(text);
if (doc case {'name': String name, 'age': int age}) {
  return '$name is $age';
}
return 'incomplete';
// Dart 3 map patterns test every key and type before binding anything. The keys are strings, so a
// typo is a failed
// match at run time, not a compile error
Elixir.ex
case JSON.decode(text) do
  {:ok, %{"name" => name, "age" => age}}
  when is_binary(name) and is_integer(age) ->
    "#{name} is #{age}"
  _ ->
    "incomplete"
end
# JSON is in the standard library since
# 1.18. The map pattern and guards test
# every key and type before anything binds; a typo'd key fails to match
Go.go
type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
var u User
if err := json.Unmarshal(b, &u); err != nil {
    return "incomplete"
}
// a missing "age" is no error: u.Age is 0.
// A mistyped field errors, but Unmarshal may have filled the others by then
Haskell.hs
data User = User
  { name :: Text, age :: Int }
  deriving Generic
instance FromJSON User

describe b = case eitherDecode b of
  Left _  -> "incomplete"
  Right u -> name u <> " is "
               <> T.pack (show (age u))
-- aeson, from Hackage, not base: a missing or mistyped key is a Left naming the path. A Maybe field
-- may be absent
Jai.jai
User :: struct { name: string; age: int; }

describe :: (text: string) -> string {
    u, ok := parse_user(text);  // yours
    if !ok { return "incomplete"; }
    return tprint("% is %", u.name, u.age);
}
// reading JSON is not a language feature: parse_user is code you write or a community module.
// type_info(User) lists the members at compile time, so such a reader can be generic
Java.java
// Jackson, third party
record User(String name, int age) {}
var m = new ObjectMapper().enable(FAIL_ON_MISSING_CREATOR_PROPERTIES);
try {
    User u = m.readValue(json, User.class);
    return u.name() + " is " + u.age();
} catch (JsonProcessingException e) {
    return "incomplete";
}
// no JSON in the JDK. Without the flag a missing age is 0; "36" coerces to 36
JavaScript.js
const doc = JSON.parse(text);
const { name, age } = doc ?? {};
if (typeof name !== "string" ||
    !Number.isInteger(age)) {
  return "incomplete";
}
// JSON.parse checks syntax only; a missing or typo'd key destructures to undefined, and the checks
// are yours. zod and ajv, on npm, check a shape
Julia.jl
using JSON           # a package, not Base
doc = JSON.parse(text)
name = get(doc, "name", nothing)
age  = get(doc, "age", nothing)
if !(name isa AbstractString &&
     age isa Int)
    return "incomplete"
end
# the isa tests are the failure path, and they are yours; a typo'd key is nothing. JSON3.jl can read
# straight into a struct
Kotlin.kt
@Serializable
data class User(val name: String, val age: Int)
val u = try {
    Json.decodeFromString<User>(text)
} catch (e: SerializationException) {
    return "incomplete"
}
// kotlinx.serialization: a missing or an unknown key throws; a property with a default is optional.
// JetBrains ships it beside the stdlib, not in it
Lua.lua
local cjson = require("cjson") -- lua-cjson
local ok, doc = pcall(cjson.decode, text)
if not ok or type(doc) ~= "table"
    or type(doc.name) ~= "string"
    or type(doc.age) ~= "number" then
  return "incomplete"
end
-- Lua ships no JSON; lua-cjson parses and every check after it is yours. doc.nmae
-- is nil, not an error
Luau.luau
--!strict
local Http = game:GetService("HttpService")
type User = { name: string, age: number }

local u = Http:JSONDecode(text) :: User
print(u.nmae)  -- type error: no such key
-- the :: cast is unchecked, so a missing
-- age is nil at run time with no error.
-- Safety means type() tests you write
Nim.nim
import std/json
type User = object
  name: string
  age: int
try:
  let u = parseJson(text).to(User)
  result = u.name & " is " & $u.age
except KeyError, JsonKindError:
  result = "incomplete"
# std/json: a missing field raises KeyError, a wrong kind JsonKindError, and u.nmae refuses at
# compile
Objective-C.m
NSDictionary *doc = [NSJSONSerialization
    JSONObjectWithData:data options:0
                 error:NULL];
NSString *name = doc[@"name"];
NSNumber *age = doc[@"age"];
if (![name isKindOfClass:NSString.class] ||
    ![age isKindOfClass:NSNumber.class])
    return @"incomplete";
// the declared types are claims only: the isKindOfClass tests are yours, and a JSON true passes as
// an NSNumber
Perl.pl
use JSON::PP;
my $doc = eval { decode_json($text) };
return "incomplete"
    unless ref $doc eq 'HASH'
    && defined $doc->{name}
    && defined $doc->{age}
    && $doc->{age} =~ /\A-?\d+\z/;
# JSON::PP is core since 5.14 and checks
# syntax only. A missing or typo'd key is undef, and the regex passes a string
# "36" as readily as the number 36
PHP.php
$doc = json_decode($text, true, 512, JSON_THROW_ON_ERROR);
$name = $doc['name'] ?? null;
$age  = $doc['age'] ?? null;
if (!is_string($name) || !is_int($age)) {
    return 'incomplete';
}
// json_decode checks syntax only; every key and type test is one you write, and a typo'd key is one
// more null
Python.py
import json
match json.loads(text):
    case {"name": str(name), "age": int(age)}:
        return f"{name} is {age}"
    case _:
        return "incomplete"
# match tests each key and type, in the language. But a JSON true passes
# int(age), a failed case may leave names
# bound, and TypedDict checks nothing. pydantic, a package, validates a class
Ruby.rb
require "json"
case JSON.parse(text, symbolize_names: true)
in {name: String => name, age: Integer => age}
  "#{name} is #{age}"
else
  "incomplete"
end
# json ships with Ruby, and a hash pattern tests each key's presence and class; a typo'd key takes
# the else arm
Rust.rs
// serde and serde_json, from crates.io
#[derive(Deserialize)]
struct User { name: String, age: i64 }

let Ok(u) =
    serde_json::from_str::<User>(text)
else {
    return "incomplete".into();
};
// a missing or mistyped field is an Err and the else must diverge; u.nmae refuses at compile. An
// Option field may be absent
Scala.scala
// circe, third party
import io.circe.Decoder
import io.circe.parser.decode
case class User(name: String, age: Int)
    derives Decoder

decode[User](text) match
  case Right(u) => s"${u.name} is ${u.age}"
  case Left(_)  => "incomplete"
// a missing or mistyped field is a Left; u.nmae refuses at compile. The Scala standard library has
// no JSON
Swift.swift
struct User: Decodable {
    let name: String
    let age: Int
}
guard let u = try? JSONDecoder().decode(User.self, from: data) else {
    return "incomplete"
}
// a missing key, a null or a wrong type throws, and guard's else must leave. u.nmae refuses at
// compile; declaring a field Optional makes it optional
TypeScript.ts
// zod, from npm
const User = z.object({
  name: z.string(), age: z.number().int(), });
type User = z.infer<typeof User>;
const r = User.safeParse(JSON.parse(text));
if (!r.success) return "incomplete";
r.data.nmae;   // refuses at compile without zod, `JSON.parse(text) as User` is a cast nothing
// checks: a missing age is undefined typed as number
Verse.verse
Describe(Doc:[string]string):string =
    if (N := Doc["name"], A := Doc["age"]):
        "{N} is {A}"
    else:
        "incomplete"
# a failed lookup fails the whole if, and nothing from it stays bound. Verse has no JSON reader and
# no untyped document; a map stands in, with one value type
Zig.zig
const User = struct {
    name: []const u8, age: i64, };
const p = std.json.parseFromSlice(User, gpa, text, .{}) catch
    return "incomplete";
defer p.deinit();
// std.json: a missing field, an unknown key, or a value the field's type cannot
// hold is an error; p.value.nmae refuses
// at compile
17

Arrays and typed dictionaries

A foreach walks a snapshot. Changing the original collection does not replace the values that snapshot holds. Fixed arity and element nullability remain part of the type.

A mutation does not extend the current walk

  1. Before the loop
    Original: [1, 2]
  2. Loop snapshot
    Visit 1, then 2.
  3. Append inside the loop
    Original can grow to [1, 2, 3, 3].
  4. Loop ends
    The two appended values were never in this snapshot.
For foreach (value in values) { values.add(3) }, starting with two values still gives exactly two loop passes.

Typed dictionaries support literals, keyed reads and writes, members, and ascending-key snapshot iteration. An absent keyed read faults; get supplies a fallback. A present null is distinct from an absent key.

t.data.dict.keys, values, and fromArrays are now catalog members. fromArrays refuses unequal counts and duplicate keys. Record-valued dictionary facts still have active verifier work.

Trident.tri
i32[] values = [1, 2, 3]
values.add(4)
i32[2] pair = [10, 20]

i32 sum = 0
foreach (value in values) {
    sum = sum + value
}
C.c
int values[4] = {1, 2, 3, 0};
int n = 3;
values[n++] = 4; // manual growth, no bounds check
int pair[2] = {10, 20};

int total = 0;
for (int i = 0; i < n; i++)
    total += values[i];
C#.cs
var values = new List<int> {1, 2, 3};
values.Add(4);
int[] pair = {10, 20};

int total = 0;
foreach (var v in values) total += v;
// mutate mid-loop → throws
C++.cpp
std::vector<int> values = {1, 2, 3};
values.push_back(4);
std::array<int, 2> pair = {10, 20};

int total = 0;
for (int v : values) total += v;
Cutlass.cut
i32[] values = [1, 2, 3]
values.add(4)
i32[2] pair = [10, 20]

i32 sum = 0
foreach (value in values) {
    sum = sum + value
}
# foreach walks in place, not a snapshot: values.add(v) in the body refuses at compile, even through
# a call, and an element write is seen by later passes. sum + value fails with fault.overflow, never
# wraps
Dart.dart
List<int> values = [1, 2, 3];
values.add(4);
final pair =
    List<int>.unmodifiable([10, 20]);

int total = 0;
for (final v in values) {
  total += v;
}
// mutate mid-loop → throws ConcurrentModificationError
Elixir.ex
values = [1, 2, 3]
values = values ++ [4]   # rebinds; doesn't mutate the original list
pair = {10, 20}            # tuple: fixed size, not enforced by the type system
total = Enum.reduce(values, 0, fn v, acc -> acc + v end)
# lists are immutable, so
# "mutating during iteration"
# can't happen at all: each step folds over the same snapshot by construction
Go.go
values := []int{1, 2, 3}
values = append(values, 4)
pair := [2]int{10, 20} // fixed array type
total := 0
for _, v := range values {
    total += v
}
Haskell.hs
values :: [Int]
values = [1, 2, 3] ++ [4]
pair :: (Int, Int)
pair = (10, 20)    -- fixed arity, enforced by the tuple type

total :: Int
total = sum values
-- a list is an immutable, singly linked value: there's no way to mutate it mid-walk at all, so a
-- fold always sees exactly the value it started with
Jai.jai
values : [..] s32;
defer array_free(values);
array_add(*values, 1);
array_add(*values, 2);

pair : [2] s32 = .[10, 20];

sum : s32 = 0;
for value : values  sum += value;
// [..] resizable, [2] fixed, [] a
// view with its own count. Bounds are checked at runtime unless a scope opts out with #no_abc
Java.java
var values = new ArrayList<>(List.of(1, 2, 3));
values.add(4);
int[] pair = {10, 20}; // fixed size

int total = 0;
for (int v : values) total += v;
// mutate mid-loop → throws
JavaScript.js
const values = [1, 2, 3];
values.push(4);
const pair = [10, 20]; // still a plain array
let total = 0;
for (const v of values) {
  total += v; // push mid-loop extends the walk
}
Julia.jl
values = [1, 2, 3]
push!(values, 4)
pair = (10, 20)  # Tuple: fixed length is part of its type

total = 0
for v in values
    total += v
end
# Base warns: don't change a Vector's length while iterating it — unlike Trident's snapshot
# semantics
Kotlin.kt
val values = mutableListOf(1, 2, 3)
values.add(4)
val pair = arrayOf(10, 20) // fixed
                             // -size Array

var total = 0
for (v in values) {
    total += v
}
// mutate mid-loop → ConcurrentModificationException
Lua.lua
local values = {1, 2, 3}
values[#values + 1] = 4
local pair = {10, 20} -- still a plain table
local total = 0
for _, v in ipairs(values) do
    total = total + v
end
Luau.luau
local values: {number} = {1, 2, 3}
table.insert(values, 4)
local pair = {10, 20}

local sum = 0
for _, v in values do
    sum += v
end
-- compound assignment and generalized
-- iteration (no ipairs) are Luau's.
-- Indexes are still 1-based, a hole still ends #t, and table.freeze is the only way to seal one
Nim.nim
var values = @[1, 2, 3]
values.add(4)
let pair: array[2, int] = [10, 20]

var total = 0
for v in values:
  total += v
# a live seq: appending inside the loop body DOES extend the walk, unlike Trident
Objective-C.m
NSMutableArray<NSNumber *> *values =
    [@[@1, @2, @3] mutableCopy];
[values addObject:@4];
int32_t pair[2] = {10, 20}; // fixed, plain C array

int32_t sum = 0;
for (NSNumber *v in values) {
    sum += v.intValue;
}
// mutating `values` mid-loop throws "mutated while being enumerated", like Java/C#
Perl.pl
my @values = (1, 2, 3);
push @values, 4;
my @pair = (10, 20);
# still a plain array; no fixed-size type exists

my $total = 0;
for my $v (@values) {
    $total += $v;
}
# perlsyn: pushing to the array you're iterating is "unsupported" and may confuse the walk
PHP.php
<?php
$values = [1, 2, 3];
$values[] = 4;
$pair = [10, 20]; // still a plain array; no fixed-size type exists
$total = 0;
foreach ($values as $v) {
    $total += $v;
}
// foreach (by value) iterates a
// snapshot taken when the loop began; appending mid-loop does NOT extend the walk
Python.py
values = [1, 2, 3]
values.append(4)
pair = (10, 20)  # tuple: length not enforced by the type system
total = sum(values)
for v in values:
    total += v  # live list: mutating mid-loop DOES extend the walk
Ruby.rb
values = [1, 2, 3]
values << 4
pair = [10, 20] # still a plain Array; no fixed-size array type

total = 0
values.each do |v|
  total += v # push mid-loop
end            # DOES extend the walk
Rust.rs
let mut values = vec![1, 2, 3];
values.push(4);
let pair: [i32; 2] = [10, 20];

let total: i32 = values.iter().sum();
// borrow checker refuses a push while `values` is borrowed
Scala.scala
import scala.collection.mutable.ArrayBuffer

val values = ArrayBuffer(1, 2, 3)
values += 4
val pair: Array[Int] = Array(10, 20)

var total = 0
for (v <- values) total += v
// mutating mid-loop is unspecified, unlike Java's throw
Swift.swift
var values = [1, 2, 3]
values.append(4)
let pair: InlineArray<2, Int> = [10, 20]

var total = 0
for v in values { total += v }
// value semantics mean the loop walks the snapshot values held when it began, same as Trident
TypeScript.ts
const values: number[] = [1, 2, 3];
values.push(4);
const pair: [number, number] = [10, 20];
// tuple length is checked only at compile time, never enforced on the emitted array

let total = 0;
for (const v of values) {
  total += v; // push mid-loop DOES extend the
               // walk (live array)
}
Verse.verse
Values : []int = array{1, 2, 3}

var Growing : []int = array{1, 2, 3}
set Growing += array{4}

var Sum : int = 0
for (V : Values):
    set Sum += V

if (First := Values[0]):
    # indexing is FAILABLE: a read only type-checks inside a context that handles the miss, so there
    # is no out-of-bounds path to take
Zig.zig
var values = std.ArrayList(i32).init(alloc);
try values.appendSlice(&.{1, 2, 3});
try values.append(4);
const pair = [2]i32{ 10, 20 }; // fixed, compile-known length

var total: i32 = 0;
for (values.items) |v| {
    total += v;
}
// for captures values.items once, like Trident's foreach snapshot
// — but append() inside the
// loop can reallocate the backing buffer, leaving that captured slice dangling: a hazard Trident's
// arena model avoids

Declared collection guarantees

Trident.tri
text[]<distinct> tags = ["b"]
tags += "a"
tags.add("a")          # still ["b", "a"]
bool present = tags.has("b")
tags -= "b"           # ["a"]

i32[]<sort> samples = [1, 3, 5, 7]
samples[0] = 6        # [3, 5, 6, 7]

Typed dictionaries and stable snapshots

Trident.tri
dict<text,i32> scores = {"b" = 2, "a" = 1}
dict<text,i32> saved = scores
scores["a"] += 4
scores["c"] = 3
i32 old = saved["a"]        # 1
i32 fallback = scores.get("missing", 0)
bool removed = scores.remove("b")
i64 count = scores.count()  # 2
i32 total = 0
foreach (key, value in scores) {
    total += value         # "a", then "c"
}

Provably invalid indices refuse before execution

Trident.tri
i32[2] pair = [10, 20]
i32 first = pair[0]
i32 invalid = pair[2]  # refuses
18

Nullability

Each nullable depth is part of the type. i32[]? and i32?[] mean different things. A null test must prove presence before a binding is read as non-nullable.

An unproved read refuses at compile. Trident has no force-unwrap operator. A program that wants a catchable failure writes an explicit null branch and fail.

Trident.tri
i32? absent = null
i32? present = 7

if (absent != null) {
    # reached only when it holds
}
i32 taken = present   # proved by its write
on error (reading) {
    return "handled a null read"
}
if (absent == null) { fail }
i32 forced = absent   # proved
C.c
int *absent = NULL; // no null
int present = 7;       // for value types at all
if (absent != NULL) { /* ... */ }
int taken = present;
int forced = *absent; // undefined behavior
C#.cs
int? absent = null;
int? present = 7;

if (absent != null) { /* ... */ }
int taken = present.Value;
int forced = absent.Value;
// -> InvalidOperationException
C++.cpp
std::optional<int> absent;
std::optional<int> present = 7;

if (absent.has_value()) { /* ... */ }
int taken = present.value();
int forced = absent.value();
// throws bad_optional_access
Cutlass.cut
enum ReadError { missing = 1 }

i32? absent = null
i32? present = 7

if (absent != null) {
    # reached only when it holds
}
i32 taken = present   # proved by its write
if (absent == null) {
    fail ReadError.missing
}
i32 forced = absent   # proved
# no on error: the error is a value
# the caller's arm handles,
#   i32 v = read() else use 0
# i32[]? and i32?[] are two types
Dart.dart
int? absent;
int? present = 7;

if (absent != null) { /* ... */ }
if (present != null) {
  int taken = present; // flow-
}                        // promoted
int forced = absent!; // sound null safety: only `!` forces it
// -> catchable "Null check operator
// used on a null value" at runtime
Elixir.ex
absent = nil
present = 7

if absent != nil do
  # ...
end
taken = present    # no static narrowing; nil is just one atom among many
forced = absent + 1
# -> ** (ArithmeticError) bad
# argument in arithmetic expression no depth-tracked nullability at all: a value either can be nil,
# by convention, nothing more is tracked
Go.go
var absent *int // no null for
present := 7      // value types; nil is pointer-only
if absent != nil { /* ... */ }
taken := present
forced := *absent // nil deref
                     // → panic
Haskell.hs
import Data.Maybe (fromJust)

absent :: Maybe Int
absent = Nothing
present :: Maybe Int
present = Just 7

taken :: Int
taken = case present of
    Just v  -> v    -- narrows
    Nothing -> 0    -- compiler forces both arms to exist
forced :: Int
forced = fromJust absent
-- throws (catchable via
-- Control.Exception) but idiomatic code pattern-matches instead of ever calling fromJust
Jai.jai
absent  : *s32 = null;
present := *count;

if absent != null {
    // reached only when it holds
}
taken  := <<present;
forced := <<absent;
// a null dereference is a hardware fault, never a catchable one. There is no option type: absence
// is a null pointer, and nothing in a pointer's type says whether it may be one
Java.java
Integer absent = null;
Integer present = 7;

if (absent != null) { /* ... */ }
int taken = present; // auto-unboxed
int forced = absent; // NPE at runtime
JavaScript.js
let absent = null;
let present = 7;

if (absent !== null) { /* ... */ }
const taken = present;
const forced = absent + 1;
// -> NaN, not even an error
// (undefined is a second,
// distinct "empty" state)
Julia.jl
absent::Union{Int,Nothing} = nothing
present::Union{Int,Nothing} = 7
# no i32?[] vs i32[]? depth
# distinction — one Union wraps the whole binding

if absent !== nothing
    # ...
end
taken = present
try
    forced = absent + 1  # MethodError
catch e
    println("handled a null read")
end
Kotlin.kt
var absent: Int? = null
var present: Int? = 7

if (absent != null) { /* ... */ }
val taken: Int = present!! // smart-cast or !! needed
val forced: Int = absent!! // -> NPE
                             // at runtime
// nullability lives in the type
// (Int vs Int?), checked at
// compile time — but nesting it per depth (Array<Int?>?) is spelled by hand each time,
// not a dedicated T?[] / T[]?
// grammar like Trident's
Lua.lua
local absent = nil
local present = 7

if absent ~= nil then --[[...]] end
local taken = present
local forced = absent + 1
-- "attempt to perform arithmetic
-- on a nil value" at runtime
Luau.luau
local absent: number? = nil
local present: number? = 7

if absent ~= nil then
    -- the checker NARROWS here: inside this branch the type is `number`
end
local taken = present :: number
local forced = (absent :: any) + 1
-- strict mode refuses `absent + 1` outright; nonstrict passes it to
-- the same runtime error Lua gives
Nim.nim
import std/options

let absent = none(int32)
let present = some(7'i32)

if absent.isSome: discard
let taken = present.get()

try:
  let forced = absent.get()
except UnpackDefect:
  echo "handled a null read"
# get() raises a Defect, and
# Nim's own convention says a Defect shouldn't be caught
Objective-C.m
NSNumber *absent = nil;
NSNumber *present = @7;

if (absent != nil) { /* ... */ }
int32_t taken = present.intValue;
int32_t forced = absent.intValue;
// → 0, not a fault: sending
// any message to nil silently returns a zeroed value
Perl.pl
my $absent = undef;
my $present = 7;

if (defined $absent) {
    # ...
}
my $taken = $present;
my $forced = $absent + 1;
# -> 0, plus a "Use of
# uninitialized value"
# warning; one flat undef
# for every type and depth
PHP.php
<?php
$absent = null;
$present = 7;

if ($absent !== null) { /* ... */ }
$taken = $present;
$forced = $absent + 1;
// null coerces to 0 in
// arithmetic — no error at
// all; only typed params or properties can even write
// "?int", a plain variable
// carries no type to narrow
Python.py
absent: int | None = None
present: int | None = 7

if absent is not None:
    ...
taken = present  # no static narrowing enforced
forced = absent + 1  # TypeError,
                       # uncaught by the type system
Ruby.rb
absent = nil
present = 7

unless absent.nil? then end
taken = present  # no static narrowing enforced
forced = absent + 1
# NoMethodError: undefined method '+' for nil, raised only when this line runs
Rust.rs
let absent: Option<i32> = None;
let present: Option<i32> = Some(7);

if absent.is_some() { /* ... */ }
let taken = present.unwrap();
let forced = absent.unwrap();
// panics; compiler forces you to handle None at every site
Scala.scala
val absent: Option[Int] = None
val present: Option[Int] = Some(7)

if (absent.isDefined) { /* ... */ }
val taken = present.get
val forced = absent.get
// throws NoSuchElementException; null still exists via Java interop
Swift.swift
let absent: Int32? = nil
let present: Int32? = 7

if absent != nil { /* ... */ }
let taken = present! // narrows
let forced = absent!
// force-unwrapping nil is a
// fatal trap that kills the
// process — not a catchable fault like Trident's

// [Int32?] vs [Int32]? vs
// [Int32?]? compose per depth,
// same shape as Trident's i32?[]
TypeScript.ts
let absent: number | null = null;
let present: number | null = 7;

if (absent !== null) { /* ... */ }
const taken: number = present!; // asserts, unchecked at runtime
const forced = absent + 1;
// refused at compile time under strictNullChecks; without it
// (or via `any`) this reads as
// runtime NaN, same as plain JS
Verse.verse
Absent : ?int = false
Present : ?int = option{7}

if (Taken := Present?):
    # ? is the failable read, so the
    # only route to the int runs through a context that handles the miss

# `false` IS the none value: option's empty case and the logic false are one representation
Zig.zig
var absent: ?i32 = null;
var present: ?i32 = 7;

if (absent != null) { /* ... */ }
const taken = present.?; // narrows

// Zig nests nullability by depth
// too: ?[]i32, []?i32, ?[]?i32
// are three distinct types, same as Trident — but forcing a null with .? is an uncatchable panic,
// not a catchable fault
const forced = absent.?; // traps
19

Branches & loops

A condition must be bool. Trident uses if, elseif, else, repeat, and foreach. A loop count is explicit.

A break leaves the loop. A continue starts the next pass. The compiler rejects another language's loop spelling and names the admitted form.

Trident.tri
i32 value = -3
text sign = ""
if (value < 0) {
    sign = "negative"
} elseif (value == 0) {
    sign = "zero"
} else {
    sign = "positive"
}

i32 seen = 0
repeat (3) { seen++ }

repeat (10, i32 step = 1) {
    if (step == 2) { continue }
    if (step > 4) { break }
}
C.c
if (value < 0) return "negative";
else if (value == 0) return "zero";
else return "positive";

int seen = 0;
for (int i = 0; i < 3; i++) seen++;

int step = 0;
while (step < 10) {
    step++;
    if (step == 2) continue;
    if (step > 4) break;
}
C#.cs
if (value < 0) return "negative";
else if (value == 0) return "zero";
else return "positive";

int seen = 0;
for (int i = 0; i < 3; i++) seen++;

int step = 0;
while (step < 10) {
    step++;
    if (step == 2) continue;
    if (step > 4) break;
}
C++.cpp
if (value < 0) return "negative";
else if (value == 0) return "zero";
return "positive";

int seen = 0;
for (int i = 0; i < 3; i++) seen++;

int step = 0;
while (step < 10) {
    step++;
    if (step == 2) continue;
    if (step > 4) break;
}
Cutlass.cut
i32 value = -3
text sign = ""
if (value < 0) {
    sign = "negative"
} elseif (value == 0) {
    sign = "zero"
} else {
    sign = "positive"
}

i32 seen = 0
repeat (3) { seen++ }

repeat (10, i32 step = 1) {
    if (step == 2) { continue }
    if (step > 4) { break }
}
# Trident's forms as-is, and no while: `while` refuses and names loop,
# repeat, and foreach. `loop { }` is
# added for code that runs until the machine stops, such as a scheduler
Dart.dart
if (value < 0) {
  return "negative";
} else if (value == 0) {
  return "zero";
} else {
  return "positive";
}

int seen = 0;
for (var i = 0; i < 3; i++) seen++;
// no fixed-count repeat; a counted for-loop is the idiom

int step = 0;
while (step < 10) {
  step++;
  if (step == 2) continue;
  if (step > 4) break;
}
Elixir.ex
shape =
  cond do
    value < 0 -> "negative"
    value == 0 -> "zero"
    true -> "positive"
  end

seen = Enum.reduce(1..3, 0, fn _, acc -> acc + 1 end)

# no while/break/continue at all — recursion (or Enum/Stream) replaces loops; reduce_while is the
# closest thing to a break
step = Enum.reduce_while(0..9, 0, fn _, step ->
    step = step + 1
    cond do
      step == 2 -> {:cont, step}
      step > 4 -> {:halt, step}
      true -> {:cont, step}
    end
  end)
Go.go
if value < 0 {
    return "negative"
} else if value == 0 {
    return "zero"
}
return "positive"

seen := 0
for i := 0; i < 3; i++ { seen++ }

step := 0
for step < 10 { // Go's only loop form
    step++
    if step == 2 { continue }
    if step > 4 { break }
}
Haskell.hs
classify :: Int -> String
classify value
  | value < 0  = "negative"
  | value == 0 = "zero"
  | otherwise  = "positive"

seen :: Int
seen = length (replicate 3 ())

loop :: Int -> Int
loop step
  | next == 2  = loop next  -- "continue"
  | next > 4   = next       -- "break"
  | next >= 10 = next
  | otherwise  = loop next
  where next = step + 1
-- no while/break/continue kwds; recursion plus guards is the idiomatic stand-in, same as the State
-- monad or an IORef loop for imperative-style code
Jai.jai
if value < 0 {
    return "negative";
} else if value == 0 {
    return "zero";
} else {
    return "positive";
}

seen := 0;
for 1..3  seen += 1;     // inclusive

step := 0;
while step < 10 {
    step += 1;
    if step == 2 continue;
    if step > 4  break;
}
// no parentheses on a condition, and `ifx a then b else c` is the expression form
Java.java
if (value < 0) return "negative";
else if (value == 0) return "zero";
else return "positive";

int seen = 0;
for (int i = 0; i < 3; i++) seen++;

int step = 0;
while (step < 10) {
    step++;
    if (step == 2) continue;
    if (step > 4) break;
}
JavaScript.js
if (value < 0) return "negative";
else if (value === 0) return "zero";
else return "positive";

let seen = 0;
for (let i = 0; i < 3; i++) seen++;

let step = 0;
while (step < 10) {
  step++;
  if (step === 2) continue;
  if (step > 4) break;
}
Julia.jl
if value < 0
    return "negative"
elseif value == 0
    return "zero"
else
    return "positive"
end
# condition must be Bool, like Trident — never merely truthy

seen = 0
for _ in 1:3
    seen += 1
end
# no repeat(count) form; a range loop is the idiom

step = 0
while step < 10
    step += 1
    if step == 2
        continue
    end
    if step > 4
        break
    end
end
Kotlin.kt
val result = if (value < 0) {
    "negative"
} else if (value == 0) {
    "zero"
} else {
    "positive"
}

var seen = 0
repeat(3) { seen++ } // stdlib fun, not a keyword

var step = 0
while (step < 10) {
    step++
    if (step == 2) continue
    if (step > 4) break
}
Lua.lua
if value < 0 then return "negative"
elseif value == 0 then return "zero"
else return "positive" end

local seen = 0
for i = 1, 3 do seen = seen + 1 end

local step = 0
while step < 10 do
    step = step + 1
    if step > 4 then break end
    -- no `continue` keyword; goto is the workaround
end
Luau.luau
if value < 0 then return "negative"
elseif value == 0 then return "zero"
else return "positive" end

local seen = 0
for i = 1, 3 do seen += 1 end

local step = 0
while step < 10 do
    step += 1
    if step == 2 then continue end
    if step > 4 then break end
end
-- `continue` is Luau's addition, and so is `if c then a else b` used as an EXPRESSION rather than a
-- statement
Nim.nim
if value < 0:
  return "negative"
elif value == 0:
  return "zero"
else:
  return "positive"

var seen = 0
for _ in 0 ..< 3: seen.inc
# no counter-less repeat; `_` discards the loop var

var step = 0
while step < 10:
  step.inc
  if step == 2: continue
  if step > 4: break
Objective-C.m
if (value < 0) {
    return @"negative";
} else if (value == 0) {
    return @"zero";
} else {
    return @"positive";
}
// any nonzero scalar (not just BOOL) is a valid condition here, unlike Trident

int32_t seen = 0;
for (int32_t i = 0; i < 3; i++) seen++;

int32_t step = 0;
while (step < 10) {
    step++;
    if (step == 2) continue;
    if (step > 4) break;
}
Perl.pl
if ($value < 0) {
    return "negative";
} elsif ($value == 0) {
    return "zero";
} else {
    return "positive";
}

my $seen = 0;
$seen++ for 1..3;

my $step = 0;
while ($step < 10) {
    $step++;
    next if $step == 2;
    last if $step > 4;
}
# next/last, not continue/break
PHP.php
<?php
if ($value < 0) {
    return "negative";
} elseif ($value === 0) {
    return "zero";
} else {
    return "positive";
}
// the condition must be truthy,
// not strictly bool: 0, "", [],
// and null all count as false

$seen = 0;
for ($i = 0; $i < 3; $i++) { $seen++; }
// no repeat(count) form; a counted for loop is idiomatic

$step = 0;
while ($step < 10) {
    $step++;
    if ($step === 2) { continue; }
    if ($step > 4) { break; }
}
Python.py
if value < 0:
    return "negative"
elif value == 0:
    return "zero"
else:
    return "positive"

seen = 0
for _ in range(3):
    seen += 1

step = 0
while step < 10:
    step += 1
    if step == 2:
        continue
    if step > 4:
        break
Ruby.rb
if value < 0
  return "negative"
elsif value == 0
  return "zero"
else
  return "positive"
end
# condition only needs to be truthy — nil/false are the only falsy values, no real bool type is
# enforced

seen = 0
3.times { seen += 1 }

step = 0
while step < 10
  step += 1
  next if step == 2   # Ruby's continue
  break if step > 4
end
Rust.rs
if value < 0 { "negative" }
else if value == 0 { "zero" }
else { "positive" }

let mut seen = 0;
for _ in 0..3 { seen += 1; }

let mut step = 0;
while step < 10 {
    step += 1;
    if step == 2 { continue; }
    if step > 4 { break; }
}
Scala.scala
import scala.util.control.Breaks.{break, breakable}
if (value < 0) "negative"
else if (value == 0) "zero"
else "positive"

var seen = 0
for (_ <- 1 to 3) seen += 1

var step = 0
breakable {
  while (step < 10) {
    step += 1
    if (step != 2) {
      if (step > 4) break()
    } // no continue keyword; restructure the body
  }
}
Swift.swift
if value < 0 {
    return "negative"
} else if value == 0 {
    return "zero"
} else {
    return "positive"
}

var seen = 0
for _ in 0..<3 { seen += 1 }
// Swift's `repeat {} while` is
// do-while, not a fixed count

var step = 0
while step < 10 {
    step += 1
    if step == 2 { continue }
    if step > 4 { break }
}
TypeScript.ts
if (value < 0) {
  return "negative";
} else if (value === 0) {
  return "zero";
} else {
  return "positive";
}

let seen = 0;
for (let i = 0; i < 3; i++) seen++;

let step = 0;
while (step < 10) {
  step++;
  if (step === 2) continue;
  if (step > 4) break;
}
Verse.verse
if (Value < 0):
    return "negative"
else if (Value = 0):
    return "zero"
else:
    return "positive"

var Seen : int = 0
for (I := 1..3):
    set Seen += 1

# = inside a condition is a COMPARISON,
# not an assignment. There is no `continue` at all: `loop` is the unbounded form and only break
# leaves
Zig.zig
if (value < 0) {
    return "negative";
} else if (value == 0) {
    return "zero";
} else {
    return "positive";
}
// if/else always takes a bool, same rule as Trident — no truthy-int shortcut here either

var seen: u8 = 0;
for (0..3) |_| seen += 1;
// no counterless repeat(n) form; a range-for is the idiom

var step: u8 = 0;
while (step < 10) {
    step += 1;
    if (step == 2) continue;
    if (step > 4) break;
}

Conditional values with use

Trident.tri
i32 value = -3
text sign = if (value < 0) {
    use "negative"
} elseif (value == 0) {
    use "zero"
} else {
    use "positive"
}
assert (sign == "negative", "selected arm supplies the value")
20

Multi-way select

when chooses an is arm with no fallthrough. An enum selection covers every declared member exactly once. Missing and duplicate members refuse at compile.

An ordinary scalar selection can use else. An enum uses when unguarded for a partial selection. Almanac selections are ordinary selections, not exhaustive enum selections.

Trident.tri
enum Status { Ok = 0, Warn = 1, Error = 2 }
i32 seen = 3
Status s = Status.Warn

text shape = ""
when (seen) {
    is 1, 2 { shape = "few" }
    is 3 { shape = "three" }
    else { shape = "many" }
}

# over an enum: every member exactly once, and no else is admitted
text label = ""
when (s) {
    is Status.Ok { label = "ok" }
    is Status.Warn { label = "warn" }
    is Status.Error { label = "err" }
}
# a new member reds this when; when unguarded (s) opts out, and only then admits an else
C.c
char *shape;
switch (seen) {
    case 1:
    case 2:
        shape = "few"; break;
    case 3:
        shape = "three"; break;
    default:
        shape = "many";
}
C#.cs
string shape = seen switch {
    1 or 2 => "few", 3 => "three", _ => "many", };
C++.cpp
std::string shape;
switch (seen) {
    case 1: case 2:
        shape = "few"; break;
    case 3:
        shape = "three"; break;
    default:
        shape = "many";
}
Cutlass.cut
enum Status { Ok = 0, Warn = 1, Error = 2 }
i32 seen = 3
Status s = Status.Warn

text shape = ""
when (seen) {
    is 1, 2 { shape = "few" }
    is 3 { shape = "three" }
    else { shape = "many" }
}

# over an enum: every member exactly once, and no else is admitted
text label = ""
when (s) {
    is Status.Ok { label = "ok" }
    is Status.Warn { label = "warn" }
    is Status.Error { label = "err" }
}
# a new member reds this when. The grammar has no `when unguarded` opt-out; it adds an arm that
# binds a variant's payload,
# `is integer (n) { ... }`
Dart.dart
final shape = switch (seen) {
  1 || 2 => "few", 3 => "three", _ => "many", };
// switch expressions (Dart 3) never fall through; a classic switch statement still can, but only
// through an empty case
Elixir.ex
shape =
  case seen do
    n when n in [1, 2] -> "few"
    3 -> "three"
    _ -> "many"
  end
# `_` must catch the rest, or case raises CaseClauseError; no fallthrough exists at all, in any form
Go.go
var shape string
switch seen {
case 1, 2:
    shape = "few"
case 3:
    shape = "three"
default:
    shape = "many"
}
// no fallthrough by default
Haskell.hs
shape :: String
shape = case seen of
    1 -> "few"
    2 -> "few"
    3 -> "three"
    _ -> "many"
-- no comma-list per arm; a guard expresses "several values":
-- | seen `elem` [1,2] = "few"
-- a non-exhaustive case is only
-- a warning (a runtime PatternMatchFail
-- if actually hit), not a refusal, unless -Werror is turned on
Jai.jai
shape : string;
if seen == {
    case 1; #through;
    case 2;
        shape = "few";
    case 3;
        shape = "three";
    case;
        shape = "many";
}
// `if x == { case ... }` is the whole
// switch. Nothing falls through unless
// #through asks it to, and #complete
// on an enum makes a missing member a
// compile error
Java.java
String shape = switch (seen) {
    case 1, 2 -> "few";
    case 3 -> "three";
    default -> "many";
};
// arrow form has no fallthrough at all
JavaScript.js
let shape;
switch (seen) {
  case 1:
  case 2:
    shape = "few"; break;
  case 3:
    shape = "three"; break;
  default:
    shape = "many";
}
// a missing `break` falls through to the next case
Julia.jl
shape = if seen in (1, 2)
    "few"
elseif seen == 3
    "three"
else
    "many"
end
# Base has no switch/match at all; if-elseif (or a Dict lookup) is the idiom — Match.jl adds real
# patterns
Kotlin.kt
val shape = when (seen) {
    1, 2 -> "few"
    3 -> "three"
    else -> "many"
}
// no fallthrough at all; even the keyword `when` matches Trident's own
Lua.lua
local shape
if seen == 1 or seen == 2 then
    shape = "few"
elseif seen == 3 then
    shape = "three"
else
    shape = "many"
end
-- no switch statement at all
Luau.luau
local shape
if seen == 1 or seen == 2 then
    shape = "few"
elseif seen == 3 then
    shape = "three"
else
    shape = "many"
end
-- no switch statement in Luau either. A table of functions keyed by the value is the other idiom,
-- and it pays an allocation per arm to get what a when block gives for nothing
Nim.nim
var shape: string
case seen
of 1, 2: shape = "few"
of 3: shape = "three"
else: shape = "many"
# no fallthrough exists; case must be exhaustive, so else is required unless every value is listed
Objective-C.m
NSString *shape;
switch (seen) {
    case 1:
    case 2:
        shape = @"few"; break;
    case 3:
        shape = @"three"; break;
    default:
        shape = @"many";
}
// a missing `break` falls through, like C; a switch can select only on integers, never directly on
// NSString
Perl.pl
my $shape;
if ($seen == 1 || $seen == 2) {
    $shape = "few";
} elsif ($seen == 3) {
    $shape = "three";
} else {
    $shape = "many";
}
# given/when existed but is deprecated since 5.38 and gone in 5.42 - no switch remains in core
PHP.php
<?php
$shape = match ($seen) {
    1, 2 => "few", 3 => "three", default => "many", };
// match compares with === and
// never falls through; the older switch statement still falls through like C
Python.py
match seen:
    case 1 | 2:
        shape = "few"
    case 3:
        shape = "three"
    case _:
        shape = "many"
Ruby.rb
shape = ""
case seen
when 1, 2
  shape = "few"
when 3
  shape = "three"
else
  shape = "many"
end
# case/when matches with
# ===; no fallthrough to
# opt out of
Rust.rs
let shape = match seen {
    1 | 2 => "few", 3 => "three", _ => "many", };
// match must be exhaustive; `_` is required here
Scala.scala
val shape = seen match {
  case 1 | 2 => "few"
  case 3     => "three"
  case _     => "many"
}
// match also deconstructs cases and takes `if` guards too
Swift.swift
var shape = ""
switch seen {
case 1, 2:
    shape = "few"
case 3:
    shape = "three"
default:
    shape = "many"
}
// exhaustive, no fallthrough by default; `fallthrough` opts in explicitly
TypeScript.ts
let shape: string;
switch (seen) {
  case 1:
  case 2:
    shape = "few"; break;
  case 3:
    shape = "three"; break;
  default:
    shape = "many";
}
// a missing `break` falls through, same as JS; the compiler never requires exhaustiveness here
Verse.verse
Shape := case (Seen):
    1 => "few"
    2 => "few"
    3 => "three"
    _ => "many"

# case is an EXPRESSION whose arms are values, so the result binds directly. One literal pattern per
# arm — there is no multi-value arm to write and no fallthrough to suppress
Zig.zig
const shape: []const u8 = switch (seen) {
    1, 2 => "few", 3 => "three", else => "many", };
// exhaustive and, like Trident's when, a matched arm never runs into the next one — but Zig
// requires an else (or full coverage) just to compile
21

Fault handling

Only an execute-stage fault is catchable. Lex, parse, compile, and install refusals stop the program before a runtime handler can catch them.

on error guards the following statements. retry repeats the protected block, resume continues after the faulting statement, and fail raises or passes on a fault.

Trident.tri
i32 divide(i32 n, i32 d) {
    return n / d
}

i32 attempts = 0
i32 answer = 0

on error (caught) {
    if (attempts < 2) {
        retry
    }
    answer = -1
    resume
}

attempts++
answer = divide(6, 0)
C.c
int attempts = 0, answer = 0;
for (;;) {
    attempts++;
    if (divisor == 0) {
        if (attempts < 2) continue;
        answer = -1; break;
    }
    answer = 6 / divisor;
    break;
}
/* division by zero is undefined
   behavior in C, not a fault */
C#.cs
int attempts = 0, answer = 0;
while (true) {
    try {
        attempts++;
        answer = 6 / 0;
        break;
    } catch (DivideByZeroException) {
        if (attempts < 2) continue;
        answer = -1; break;
    }
}
C++.cpp
int attempts = 0, answer = 0;
while (true) {
    try {
        attempts++;
        answer = divide(6, 0);
        break;
    } catch (const std::exception&) {
        if (attempts < 2) continue;
        answer = -1; break;
    }
}
Cutlass.cut
i32 divide(i32 n, i32 d) {
    return n / d
}
# no handler: a zero divisor fails
# divide with fault.divide, an error
# value the call must take an arm for

i32 answer = 0
repeat (2, i32 attempt = 1) {
    answer = divide(6, 0) else (e) {
        if (attempt < 2) { continue }
        use -1
    }
    break
}
# on error, retry, and resume refuse:
# the arm sits at the call, and the author's repeat bounds the retries
Dart.dart
int attempts = 0, answer = 0;
while (true) {
  try {
    attempts++;
    answer = 6 ~/ 0; // throws UnsupportedError
    break;
  } catch (e) {
    if (attempts < 2) continue;
    answer = -1;
    break;
  }
}
// no distinct retry/resume verbs
Elixir.ex
defmodule Divider do
  def attempt(n) when n < 2 do
    try do
      6 / 0
    rescue
      ArithmeticError -> attempt(n + 1)
    end
  end
  def attempt(_), do: -1
end

answer = Divider.attempt(0)
# try/rescue/after covers the ordinary case, but idiomatic Elixir mostly skips local retry logic:
# let the process crash and have a supervisor restart it fresh instead
# — isolation by process
# boundary, not by catch verbs
Go.go
attempts, answer := 0, 0
for {
    attempts++
    v, err := divide(6, 0)
    if err == nil { answer = v; break }
    if attempts < 2 { continue }
    answer = -1
    break
}
// errors are values, not exceptions; panic/recover is for truly exceptional faults
Haskell.hs
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Exception

run :: Int -> IO Int
run attempts = do
    let a = attempts + 1
    r <- try (evaluate (6 `div` 0))
    case r of
        Right v -> pure v
        Left (_ :: ArithException)
          | a < 2     -> run a
          | otherwise -> pure (-1)
-- no distinct retry/resume verbs; exceptions are an IO concept, so a pure div-by-zero only throws
-- once `evaluate` forces it
Jai.jai
divide :: (a: s32, b: s32) -> s32, bool {
    if b == 0  return 0, false;
    return a / b, true;
}

attempts := 0;
answer : s32 = 0;
while true {
    attempts += 1;
    v, ok := divide(6, 0);
    if ok { answer = v; break; }
    if attempts < 2  continue;
    answer = -1;
    break;
}
// no exceptions anywhere: a second
// return value is the error, and
// assert() ends the program outright
Java.java
int attempts = 0, answer = 0;
while (true) {
    try {
        attempts++;
        answer = 6 / 0;
        break;
    } catch (ArithmeticException e) {
        if (attempts < 2) continue;
        answer = -1; break;
    }
}
JavaScript.js
let attempts = 0, answer = 0;
for (;;) {
  try {
    attempts++;
    answer = 6 / 0; // Infinity, never throws
    break;
  } catch (e) {
    if (attempts < 2) continue;
    answer = -1; break;
  }
}
Julia.jl
attempts, answer = 0, 0
while true
    try
        attempts += 1
        answer = div(6, 0)  # DivideError;
                              # 6/0 (float)
                              # gives Inf, never throws
        break
    catch e
        if attempts < 2
            continue
        end
        answer = -1
        break
    end
end
# no distinct retry/resume verbs;
# rethrow() re-raises by hand
Kotlin.kt
var attempts = 0
var answer = 0

while (true) {
    try {
        attempts++
        answer = 6 / 0
        break
    } catch (e: ArithmeticException) {
        if (attempts < 2) continue
        answer = -1
        break
    }
}
// no distinct retry/resume verbs; try is an expression, but a catch can't re-enter the block
Lua.lua
local attempts, answer = 0, 0
while true do
    attempts = attempts + 1
    local ok, r = pcall(divide, 6, 0)
    if ok then answer = r; break end
    if attempts < 2 then goto cont end
    answer = -1; break
    ::cont::
end
-- pcall/xpcall is the whole story
Luau.luau
local attempts, answer = 0, 0
while true do
    attempts += 1
    local ok, r = pcall(divide, 6, 0)
    if ok then answer = r break end
    if attempts >= 2 then
        answer = -1 break
    end
end
-- pcall/xpcall, exactly as in Lua.
-- Luau adds no error type to the
-- checker: pcall's value comes back `any`, so the happy path is unchecked too
Nim.nim
var attempts = 0
var answer = 0
while true:
  inc attempts
  try:
    answer = 6 div 0
    break
  except DivByZeroDefect:
    if attempts < 2: continue
    answer = -1
    break
# division raises a Defect, not a plain exception; see the nullability card
Objective-C.m
int32_t attempts = 0, answer = 0;
for (;;) {
    attempts++;
    @try {
        answer = 6 / divisor; // SIGFPE on zero, not an NSException
        break;
    } @catch (NSException *e) {
        if (attempts < 2) continue;
        answer = -1; break;
    }
}
// integer divide-by-zero traps the process; @try/@catch only sees NSException, not signals
Perl.pl
use feature 'try';
no warnings
    'experimental::try';

my ($attempts, $answer) =
    (0, 0);
ATTEMPT: while (1) {
    $attempts++;
    try {
        $answer = 6 / 0;
        last ATTEMPT;
    } catch ($e) {
        if ($attempts < 2) {
            next ATTEMPT;
        }
        $answer = -1;
        last ATTEMPT;
    }
}
# try/catch stable since
# 5.40; still one verb -
# no distinct retry/resume
PHP.php
<?php
$attempts = 0;
$answer = 0;

while (true) {
    $attempts++;
    try {
        $answer = 6 / 0;
        break;
    } catch (\DivisionByZeroError $e) {
        if ($attempts < 2) {
            continue;
        }
        $answer = -1;
        break;
    }
}
// no distinct retry/resume verbs; continue/break drive the loop by hand, same as Java or C#
Python.py
attempts, answer = 0, 0
while True:
    try:
        attempts += 1
        answer = 6 // 0
        break
    except ZeroDivisionError:
        if attempts < 2:
            continue
        answer = -1
        break
# no distinct retry/resume verbs
Ruby.rb
attempts = 0
answer = 0

begin
  attempts += 1
  answer = 6 / 0
rescue ZeroDivisionError
  retry if attempts < 2
  answer = -1
end
# retry re-runs the begin block, like Trident's; no resume — once rescued, execution can't return to
# the faulting statement
Rust.rs
let mut attempts = 0;
let mut answer = 0;
loop {
    attempts += 1;
    match divide(6, 0) {
        Ok(v) => { answer = v; break; }
        Err(_) if attempts < 2 => continue, Err(_) => { answer = -1; break; }
    }
}
Scala.scala
var attempts = 0
var answer = 0

var done = false
while (!done) {
  try {
    attempts += 1
    answer = 6 / 0
    done = true
  } catch {
    case _: ArithmeticException =>
      if (attempts < 2) ()
      else { answer = -1; done = true }
  }
}
// try/catch only; no distinct retry/resume verbs
Swift.swift
var attempts = 0
var answer = 0

while true {
    attempts += 1
    do {
        answer = try divide(6, 0)
        break
    } catch {
        if attempts < 2 { continue }
        answer = -1
        break
    }
}
// no distinct retry/resume verbs; do/catch is the whole story
TypeScript.ts
let attempts = 0;
let answer = 0;
for (;;) {
  try {
    attempts++;
    answer = 6 / 0; // Infinity, never throws
    break;
  } catch (e) {
    if (attempts < 2) continue;
    answer = -1; break;
  }
}
// `catch (e)` binds `unknown` by default; no distinct retry/resume verbs at all
Verse.verse
Divide(A : int, B : int)<decides><transacts> : rational =
    B <> 0
    A / B

var Attempts : int = 0
loop:
    set Attempts += 1
    if (V := Divide[6, 0]):
        return V
    else if (Attempts >= 2):
        return 0

# <decides> marks a function that may
# FAIL, [] is how you call one, and a
# failed call rolls back every effect it had before it failed
Zig.zig
fn divide(a: i32, b: i32) !i32 {
    if (b == 0) return error.DivByZero;
    return @divTrunc(a, b);
}

var attempts: u8 = 0;
var answer: i32 = 0;
while (true) {
    attempts += 1;
    if (divide(6, 0)) |v| {
        answer = v;
        break;
    } else |_| {
        if (attempts < 2) continue;
        answer = -1;
        break;
    }
}
// if/else unpacks an error union;
// no separate retry/resume verbs. A real panic (array out-of-bounds, raw a / 0) is never catchable
// at all, unlike here
22

Isolated task regions

task { } else (e) { } runs a block in its own fault region. Success skips the arm. A catchable fault runs the arm once.

A child can narrow inherited memory and time limits, never widen them. Memory is charged at allocation. Deadlines and cancellation are polled coarsely. No runtime work counter or deterministic operation budget exists.

Trident.tri
text trail = "no fault"

task {
    i32 zero = 0
    i32 lost = 1 / zero
} else (raised) {
    trail = "the block faulted"
}
C.c
/* nearest analog is
   setjmp/longjmp; no scoped
   fault region exists at all */
if (setjmp(env) == 0) {
    int zero = 0;
    int lost = 1 / zero;
} else {
    trail = "the block faulted";
}
C#.cs
string trail = "no fault";
try {
    int zero = 0;
    int lost = 1 / zero;
} catch (DivideByZeroException) {
    trail = "the block faulted";
}
C++.cpp
std::string trail = "no fault";
try {
    int zero = 0;
    int lost = 1 / zero;
} catch (...) {
    trail = "the block faulted";
}
Cutlass.cut
i32 divideOne(i32 zero) {
    return 1 / zero
}

text trail = "no fault"
region (bytes = 4KiB) {
    i32 lost = divideOne(0) else (e) {
        trail = "the block faulted"
        use 0
    }
}
# task refuses and names region. A region's bytes are an exact limit inside its context's memory,
# but
# it is no fault boundary: an error
# is a value, so the arm sits at the one call that can fail. Limits bound memory, never time
Dart.dart
String trail = "no fault";
try {
  int zero = 0;
  int lost = 1 ~/ zero;
} catch (e) {
  trail = "the block faulted";
}
// no resource-budget region; an Isolate is the nearest real fault-isolation boundary
Elixir.ex
trail = "no fault"

task = Task.async(fn ->
  zero = 0
  1 / zero
end)

trail =
  case Task.yield(task, 100) ||
       Task.shutdown(task) do
    {:ok, _value} -> trail
    nil -> "the block faulted"
    {:exit, _reason} -> "the block faulted"
  end
# the real analog isn't this local catch: an OTP Supervisor restarts a crashed child in a fresh
# state, isolated from its siblings — "let it crash" replaces a tightened resource/time region with
# a process boundary and a restart policy
Go.go
trail := "no fault"
func() {
    defer func() {
        if recover() != nil {
            trail = "the block faulted"
        }
    }()
    zero := 0
    _ = 1 / zero
}()
Haskell.hs
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Exception

trail :: IO String
trail = do
    r <- try (evaluate
                (1 `div` (0 :: Int)))
    pure $ case r of
        Right _ -> "no fault"
        Left (_ :: ArithException) ->
            "the block faulted"
-- no scoped resource/time budget region, and no inherited-limit tightening rule; just an ordinary
-- try/catch pair
Jai.jai
trail := "no fault";
zero : s32 = 0;
if zero != 0 {
    lost := 1 / zero;
} else {
    trail = "guarded, not caught";
}
// there is no isolated failure region, because there is nothing to isolate: a divide by zero is a
// hardware trap and Jai does not unwind. The nearest real feature is push_context, which scopes the
// allocator and logger for a block rather than its faults
Java.java
String trail = "no fault";
try {
    int zero = 0;
    int lost = 1 / zero;
} catch (ArithmeticException e) {
    trail = "the block faulted";
}
JavaScript.js
let trail = "no fault";
try {
  const zero = 0;
  const lost = 1 / zero; // Infinity, never throws
} catch (e) {
  trail = "the block faulted";
}
Julia.jl
trail = "no fault"
try
    zero = 0
    lost = div(1, zero)
catch e
    trail = "the block faulted"
end
# nearest true isolation is a
# Task (Threads.@spawn / @async):
# fetch(t) rethrows the task's
# own exception, but nothing tightens a resource budget
Kotlin.kt
var trail = "no fault"
try {
    val zero = 0
    val lost = 1 / zero
} catch (e: ArithmeticException) {
    trail = "the block faulted"
}
// no resource-budget region; just an ordinary try, same as Java/C#
Lua.lua
local trail = "no fault"
local ok = pcall(function()
    local zero = 0
    local lost = 1 / zero
end)
if not ok then
    trail = "the block faulted"
end
Luau.luau
local trail = "no fault"
local ok = pcall(function()
    error("faulted")
end)
if not ok then
    trail = "the block faulted"
end
-- pcall, plus Roblox's task library. task.spawn runs a body on its own
-- coroutine, and an error there is
-- printed to the log WITHOUT ever reaching the code that spawned it
Nim.nim
var trail = "no fault"

try:
  let zero = 0
  let lost = 1 div zero
except DivByZeroDefect:
  trail = "the block faulted"
# no resource-budget region; just an ordinary try over a Defect
Objective-C.m
NSString *trail = @"no fault";
@try {
    int32_t zero = 0;
    if (zero == 0) {
        @throw [NSException
            exceptionWithName:@"Fault"
            reason:@"div by zero"
            userInfo:nil];
    }
} @catch (NSException *e) {
    trail = @"the block faulted";
}
// integer division itself traps
// (SIGFPE); @throw is how code
// raises a catchable fault here
Perl.pl
my $trail = "no fault";
eval {
    my $zero = 0;
    my $lost = 1 / $zero;
    1;
} or do {
    $trail =
        "the block faulted";
};
# eval{} plus $@ is the
# whole story; no resource-budget region
PHP.php
<?php
$trail = "no fault";
try {
    $zero = 0;
    $lost = 1 / $zero;
} catch (\DivisionByZeroError $e) {
    $trail = "the block faulted";
}
// no resource-budget region; just an ordinary try, same as most languages here
Python.py
trail = "no fault"
try:
    zero = 0
    lost = 1 / zero
except Exception:
    trail = "the block faulted"
# no resource-budget region; just an ordinary try
Ruby.rb
trail = "no fault"

begin
  zero = 0
  lost = 1 / zero
rescue StandardError
  trail = "the block faulted"
end
# no resource-budget region; just an ordinary begin/rescue
Rust.rs
let mut trail = "no fault";
let r = std::panic::catch_unwind(|| {
    let zero = 0;
    1 / zero
});
if r.is_err() {
    trail = "the block faulted";
}
Scala.scala
import scala.util.Try

var trail = "no fault"
Try {
  val zero = 0
  1 / zero
} match {
  case scala.util.Failure(_) =>
    trail = "the block faulted"
  case _ => ()
}
// Try is a value, not just control flow — no budget region
Swift.swift
var trail = "no fault"

do {
    trail = try attemptDivide(6, 0)
} catch {
    trail = "the block faulted"
}
// plain `1 / 0` traps the process instead of throwing — only a `throws` function can land in
// `catch` at all Swift's own `Task` names a concurrency unit, not this
TypeScript.ts
let trail = "no fault";
try {
  const zero = 0;
  const lost = 1 / zero; // Infinity, never throws
} catch (e) {
  trail = "the block faulted";
}
// no resource-budget region or scope-tightening rule; an ordinary try, same as JS
Verse.verse
var Trail : string = "no fault"

if (V := Divide[1, 0]):
    UseIt(V)
else:
    set Trail = "the block faulted"

# the failure context IS the region,
# and <transacts> makes it isolated in
# the strong sense: every write the failed branch made is rolled back before the else arm runs
Zig.zig
var trail: []const u8 = "no fault";

fn attemptDivide(zero: i32) !i32 {
    if (zero == 0) return error.DivByZero;
    return @divTrunc(1, zero);
}

if (attemptDivide(0)) |_| {} else |_| {
    trail = "the block faulted";
}
// nearest idiom: isolate the risky code in its own function returning !T, then catch it — Zig has
// no closures, so it must be a real, named function. A raw
// panic (e.g. 1 / zero without the
// check) is never catchable at all
23

Deferred cleanup

on exit runs cleanup last-declared first, once on each leave. Cleanup precedes the error handler. A return value is fixed before cleanup runs.

Cleanup cannot redirect control with return, break, continue, retry, or resume. A nested on exit also refuses.

Trident.tri
text trail = "start"
on error (caught) {
    trail = trail + "|handler"
    resume
}
on exit {
    trail = trail + "|exit"
}
trail = trail + "|body"
i32 lost = 1 / 0
# -> start|body|exit|handler
C.c
/* no defer/finally in C;
   cleanup is hand-written at
   every exit, often via
   goto cleanup; */
strcat(trail, "|body");
goto cleanup;
cleanup:
strcat(trail, "|exit");
C#.cs
try {
    trail += "|body";
} finally {
    trail += "|exit";
}
// `using` sugars this for IDisposable values
C++.cpp
struct Guard {
    ~Guard() { /* runs on
                  scope exit */ }
};
{
    Guard g; // destructor is LIFO, like on exit
    trail += "|body";
}
Cutlass.cut
alias host = cut.host

i32 body(i32 zero) {
    on exit {
        host.write("|exit") else use 0
    }
    host.write("start|body") else use 0
    return 1 / zero
}

i32 lost = body(0) else (e) {
    host.write("|handler") else use 0
    use 0
}
# -> start|body|exit|handler
# on exit runs last-declared first on every leave, fail included, and a
# fail inside it refuses. on error
# and resume refuse: the handler is the caller's else arm
Dart.dart
String trail = "start";
try {
  trail += "|body";
} finally {
  trail += "|exit";
}
// no try-with-resources sugar; a closeable resource is closed by hand inside finally
Elixir.ex
trail = "start"

result =
  try do
    trail = trail <> "|body"
    _ = 1 / 0
    trail
  rescue
    _ -> trail <> "|handler"
  after
    IO.puts("cleanup ran")
  end
# -> "start|body|handler"; no
# "|exit" segment is possible:
# do/rescue/after each open their own scope, so `after` can't feed the trail back without an Agent,
# ETS, or a message — but like on exit, after's own return value is always discarded, so cleanup
# can't rewrite the answer either
Go.go
func run() (trail string) {
    trail = "start"
    defer func() {
        trail += "|exit"
    }()
    trail += "|body"
    return trail
}
// LIFO like on exit, but no retry to abandon
Haskell.hs
import Control.Exception (finally)
import Data.IORef

run :: IO String
run = do
    trailRef <- newIORef "start"
    ( do
        modifyIORef trailRef
            (++ "|body")
        _ <- evaluate (1 `div` (0 :: Int))
        pure ()
      ) `finally`
        modifyIORef trailRef (++ "|exit")
    readIORef trailRef
-- `finally` runs on both an ordinary return and an exception unwind, like on exit; no "abandoned by
-- a retry" case exists to cover at all
Jai.jai
trail := "start";
{
    defer print("|exit\n");
    trail = "start|body";
}
// defer fires LIFO at the end of the enclosing SCOPE, not just the function, so a bare block is
// enough. There is no errdefer and no handler form, because there is no exception for one to run on
Java.java
try {
    trail += "|body";
} finally {
    trail += "|exit";
}
// try-with-resources for AutoCloseable values
JavaScript.js
let trail = "start";
try {
  trail += "|body";
} finally {
  trail += "|exit";
}
Julia.jl
trail = "start"
try
    trail *= "|body"
    lost = div(1, 0)
catch e
    trail *= "|handler"
finally
    trail *= "|exit"
end
# -> start|body|handler|exit:
# catch runs before finally in Julia, the opposite order from Trident's on exit firing before the
# handler sees it
Kotlin.kt
var trail = "start"
try {
    trail += "|body"
} finally {
    trail += "|exit"
}
// `Closeable.use { }` sugars
// this for AutoCloseable values, like C#'s `using`
Lua.lua
local trail = "start"
local ok = pcall(function()
    trail = trail .. "|body"
end)
trail = trail .. "|exit"
-- no native finally; this pattern is hand-rolled
Luau.luau
local trail = "start"
local ok = pcall(function()
    trail ..= "|body"
end)
trail ..= "|exit"
-- no defer and no finally. The Roblox answer is a hand-rolled cleanup
-- object (Maid, Trove, Janitor) you
-- remember to :Destroy(), and the one you forget is the standard connection leak
Nim.nim
proc run(): string =
  var trail = "start"
  defer: trail = trail & "|exit"
  trail = trail & "|body"
  result = trail
# LIFO like on exit, but no retry to abandon; defer only works inside a proc body
Objective-C.m
NSMutableString *trail =
    [@"start" mutableCopy];
@try {
    [trail appendString:@"|body"];
} @finally {
    [trail appendString:@"|exit"];
}
// @finally runs on every path,
// like on exit; no built-in
// "retry" concept to abandon
Perl.pl
use feature 'defer';
no warnings
    'experimental::defer';

sub run {
    my $trail = "start";
    defer {
        $trail .= "|exit";
    }
    $trail .= "|body";
    return $trail;
}
# defer is experimental
# (5.36+); fires LIFO on
# any scope exit, but return/last/next also refuse inside it
PHP.php
<?php
$trail = "start";
try {
    $trail .= "|body";
} finally {
    $trail .= "|exit";
}
// finally always runs, LIFO for nested blocks; no notion of
// "abandoned by a retry"
Python.py
trail = "start"
try:
    trail += "|body"
    zero = 0
    lost = 1 / zero
finally:
    trail += "|exit"
# no notion of "abandoned by a retry"
Ruby.rb
trail = "start"

begin
  trail += "|body"
  zero = 0
  lost = 1 / zero
ensure
  trail += "|exit"
end
# ensure runs LIFO across nested begin blocks; no notion of "abandoned by a retry"
Rust.rs
struct Guard;
impl Drop for Guard {
    fn drop(&mut self) { /* ... */ }
}
{
    let _g = Guard; // fires on scope exit
    trail += "|body";
}
Scala.scala
var trail = "start"
try {
  trail += "|body"
} finally {
  trail += "|exit"
}
// scala.util.Using sugars this for AutoCloseable, like Java's
Swift.swift
func run() -> String {
    var trail = "start"
    defer { trail += "|exit" }
    trail += "|body"
    return trail
}
// defer runs LIFO on every exit, and the returned value is fixed before it runs — the same two
// guarantees as on exit; return/break/throw all refuse inside a defer body too
TypeScript.ts
let trail = "start";
try {
  trail += "|body";
} finally {
  trail += "|exit";
}
// `using`/`await using` (TS 5.2, Symbol.dispose) sugars this for disposable resources, same idea as
// C#'s `using`
Verse.verse
Run()<transacts> : void =
    defer:
        Print("|exit")
    Print("|body")

# defer runs at the end of the enclosing block, bottom-up. A failure inside the block still runs it
# and then rolls the block's writes back — the cleanup and the rollback are two separate mechanisms
# doing two jobs
Zig.zig
const std = @import("std");

fn run(fail: bool) ![]const u8 {
    var trail: []const u8 = "start";
    errdefer trail = "start|handler";
    defer std.debug.print("|exit\n", .{});

    trail = "start|body";
    if (fail) return error.Oops;
    return trail;
}
// defer/errdefer fire LIFO on any return, including a try-propagated
// error — but a real panic
// (e.g. an out-of-bounds index)
// traps without unwinding, so nothing deferred runs there
24

Locking

lock holds a key under an explicit t.sync policy. It releases the key however the block ends. A wait failure faults.

lock? uses an else arm for failure. The program may compile without a sync grant; execution checks the granted authority.

Trident.tri
text key = "ledger"
chest policy =
    t.sync.policy.exclusive(0ms, 1s, false)

lock (key, policy) {
    # held; released however it ends
}

lock? (key, policy) {
    # held
} else {
    # busy: no fault, this arm runs
}
C.c
pthread_mutex_lock(&mu);
/* ... */
pthread_mutex_unlock(&mu);

if (pthread_mutex_trylock(&mu) == 0) {
    /* ... */
    pthread_mutex_unlock(&mu);
}
C#.cs
private readonly object gate = new();
lock (gate) {
    // ...
}
// sugar for Monitor.Enter/Exit wrapped in try/finally
C++.cpp
std::mutex mu;
{
    std::lock_guard<std::mutex> g(mu);
    // ...
} // released on scope exit

if (mu.try_lock()) { mu.unlock(); }
Cutlass.cut
# lock (key, policy) { }
# refuses: Cutlass has no lock, and cut and cutrun refuse one. Shared state is declared instead:
channel<u64, 64> inbox
static u64 ledger with { owner = keeper }
thread keeper with { core = any, stack = 16KiB, memory = 64KiB } runs keep

void keep() {
    loop {
        u64 n = inbox.receive(within = 1s)
            else (e) { continue }
        ledger = ledger + n
    }
}
# only keeper writes ledger, and a second writer refuses at compile. In place of lock?, a full send
# fails with fault.full and a wait past within with fault.timeout
Dart.dart
// no built-in lock — Dart is single-threaded per isolate event loop, and isolates share no memory.
// Within one isolate, interleaving needs a package such as synchronized:
final lock = Lock();
await lock.synchronized(() async {
  // ...
});
Elixir.ex
# BEAM avoids shared mutable state: a GenServer already serializes access to its own state one
# message at a time, so that call site alone often needs no lock verb at all
GenServer.call(Registry, {:update, key})

# the rare true mutex is a library, the "mutex" hex package wrapping a GenServer:
case Mutex.lock(mu, key) do
  {:ok, lock} -> Mutex.release(mu, lock)
  {:error, :busy} -> :timed_out
end
Go.go
var mu sync.Mutex
mu.Lock()
defer mu.Unlock()
// ...

if mu.TryLock() { // 1.18+
    defer mu.Unlock()
}
Haskell.hs
import Control.Concurrent.MVar

run :: MVar () -> IO ()
run lock =
    withMVar lock $ \_ -> do
        pure ()   -- runs while held

tryRun :: MVar () -> IO Bool
tryRun lock = do
    held <- tryTakeMVar lock
    case held of
        Just _  -> do
            putMVar lock ()
            pure True
        Nothing -> pure False  -- busy
-- no built-in wait-with-timeout; STM's `retry` plus `registerDelay` is the idiomatic escape hatch
Jai.jai
#import "Thread";

mutex : Mutex;
init(*mutex);

lock(*mutex);
defer unlock(*mutex);
// ...

// Mutex and Thread_Group are library types, not syntax. There is no try-lock-with-timeout form to
// match lock?, and nothing gates which code is allowed to take which lock
Java.java
Lock lock = new ReentrantLock();
lock.lock();
try {
    // ...
} finally {
    lock.unlock();
}
JavaScript.js
// no built-in lock — JS is single-threaded per event loop. Workers need a library such as
// async-mutex:
await mutex.runExclusive(async () => {
  // ...
});
Julia.jl
gate = ReentrantLock()
lock(gate) do
    # ...
end

if trylock(gate)
    try
        # held
    finally
        unlock(gate)
    end
else
    # wait ran out immediately; trylock never actually waits — no timeout arg
end
Kotlin.kt
val lock = Any()
synchronized(lock) {
    // ...
}
// stdlib inline fun over the JVM monitor — no try-lock. Coroutine code reaches for
// kotlinx.coroutines.sync.Mutex, which does have tryLock()
Lua.lua
-- stock Lua has no threads and no lock primitive at all; coroutines are cooperative, so mutual
-- exclusion isn't a language-level concern
Luau.luau
-- no locks, and nothing to lock: one VM, one thread, and task.spawn /task.defer schedule coroutines
-- that only switch at a yield.

-- Parallel Luau adds real threads as Actors and answers the problem by forbidding it: Actors share
-- no memory at all and talk by message
Nim.nim
import std/locks

var lock: Lock
initLock(lock)

withLock lock:
  discard # ...

if tryAcquire(lock):
  discard # held
  release(lock)
else:
  discard # wait ran out
# tryAcquire polls once with no timeout; no bounded-wait form like lock?'s policy
Objective-C.m
@synchronized (self) {
    // ...
}
// sugar for objc_sync_enter/exit, a recursive mutex keyed by the object pointer

NSLock *lock = [NSLock new];
NSDate *deadline = [NSDate
    dateWithTimeIntervalSinceNow:5];
if ([lock lockBeforeDate:deadline]) {
    // held
    [lock unlock];
} else {
    // wait ran out
}
Perl.pl
use threads;
use threads::shared;

my $key : shared;
{
    lock($key); # blocks until free; releases at block exit
    # ...
}

use Thread::Semaphore;
my $sem =
    Thread::Semaphore->new;
if ($sem->down_timed(5)) {
    # held
    $sem->up;
} else {
    # wait ran out
}
# threads are heavyweight -each is a full interpreter, not an OS-thin thread
PHP.php
<?php
$fp = fopen($path, 'c');
flock($fp, LOCK_EX); // blocks until held
// ...
flock($fp, LOCK_UN);

if (flock($fp, LOCK_EX | LOCK_NB)) {
    // held
    flock($fp, LOCK_UN);
} else {
    // wait ran out immediately; only blocking or non-blocking, no true timeout
}
// stock PHP has no in-process mutex — each request is its own process/thread
Python.py
lock = threading.Lock()
with lock:
    ...

if lock.acquire(timeout=5):
    try:
        ...
    finally:
        lock.release()
Ruby.rb
mutex = Mutex.new
mutex.synchronize do
  # ...
end

if mutex.try_lock
  begin
    # held
  ensure
    mutex.unlock
  end
else
  # wait ran out
end
# try_lock never actually waits — it's an immediate try, not a timed one
Rust.rs
let data = Mutex::new(0);
{
    let mut g = data.lock().unwrap();
    // ...
} // released when g drops

if let Ok(g) = data.try_lock() { }
Scala.scala
val gate = new Object
gate.synchronized {
  // ...
}
// sugars the JVM monitor, same as Java's `lock` block

import java.util.concurrent.locks.ReentrantLock
val lk = new ReentrantLock()
if (lk.tryLock()) {
  try { /* ... */ }
  finally { lk.unlock() }
}
Swift.swift
let lock = NSLock()
lock.lock()
defer { lock.unlock() }
// ...

if lock.lock(before: .now + 5) {
    defer { lock.unlock() }
    // ...
}
// idiomatic Swift favors `actor` isolation over an explicit lock statement like this one
TypeScript.ts
// no built-in lock — single-threaded per event loop, same as JS. Worker threads need a library such
// as async-mutex:
await mutex.runExclusive(async () => {
  // ...
});
Verse.verse
# there is no mutex and no lock expression, because Verse's concurrency is deterministic and
# cooperative: a <suspends> function yields only at points you can read.

# what stands in for exclusion is rollback — a <transacts> expression that fails undoes its writes,
# so a half-applied update is never a state anyone observes
Zig.zig
const std = @import("std");

var mu: std.Thread.Mutex = .{};

mu.lock();
defer mu.unlock();
// ...

if (mu.tryLock()) {
    defer mu.unlock();
    // held
} else {
    // tryLock just fails once — no wait-then-give-up policy like lock?'s timeout
}
// std.Thread.RwLock and Semaphore cover .shared/.semaphore; no grant system gates any of it
25

Memory management

Trident source has no pointer, free, or delete. The runtime checks retained memory against its lent limits. A scope returns its reservation when that storage ends.

The launch starts without a ceiling unless a person narrows it. Host, application, and runtime settings can only narrow inherited authority and capacity. Limits count resources, never runtime work.

TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
Modelbudgeted ledger, no tracing GCmanualtracing GCmanual / RAIIregions per function body and loop pass, no GCtracing GC, generational, per-isolate heapper-process generational GC (BEAM)tracing GCtracing GC (generational, copying)manual, allocator-explicit (no GC)tracing GCtracing GCtracing GC (generational, partly concurrent)tracing GC (JVM)tracing GCincremental mark-and-sweep GCARC/ORC — deterministic reference counting plus a cycle collectorreference counting (ARC)refcount only, no cycle collectorrefcount + cyclic GCrefcount + cyclic GCtracing GC (generational, incremental mark)ownership & borrowingtracing GC (JVM)ARC (reference counting); value types copied, no tracing GCtracing GC (host engine, e.g. V8) — types are erased before any of this runstracing GC (the Unreal runtime's)manual, allocator-explicit (no GC)
Who frees memorynobody — no free existsthe programmerruntimeprogrammer, or RAII destructornobody — a region closes with its scope; no free existsruntimeruntime (each process's own collector)runtimeruntimethe programmer, through the context's allocatorruntimeruntimeruntimeruntimeruntimeruntimecompiler-inserted refcount ops (no free call)compiler-inserted retain/release (ARC)runtimeruntimeruntimeruntimecompiler-inserted dropruntime (JVM GC)compiler-inserted retain/release (ARC)runtime (same engine as JS)runtimethe programmer, via an explicit allocator
Stop-the-world pausesnone — nothing ever collectsnoneyes (tunable)none by defaultnone — nothing ever collectsbrief (young-gen scavenge; old-gen mostly concurrent)no global pause (per-process only)brief (concurrent)yes (young-gen frequent, old-gen larger)noneyes (tunable)yesyes (partly concurrent since 1.10)yes (tunable)yes (tunable)none by design — incremental, on a per-step budgetnone — ORC's cycle collector is incremental, not tracingnonenone (deterministic refcounting)rare (cycle collector only)rare (cyclic GC only)yes (incremental marking shortens them)noneyes (tunable, GC-dependent)no collector pauses; a deep release cascade can still blockyes (mostly incremental/concurrent in modern V8)yes (runtime-managed)none
Use-after-free reachable?no — no pointer type existsyesno (yes in unsafe)yes (mitigated by RAII if used)no (yes in raw, as a defined stale read)no (yes via dart:ffi pointers)nonono (unsafe FFI/Foreign.Ptr allows it explicitly)yes — raw pointers and an explicit freenonono (yes via Ptr/ccall)nononono (yes via raw ptr)rare — reachable via unsafe_unretained or manual dealloc bugsnono (yes via FFI)nonono (compiler refuses it)nono (yes via Unsafe*/Unmanaged)nono — no pointer type existsyes, unless the safety allocator catches it (Debug/ReleaseSafe only)
Unbounded growth possible?no — every domain has a declared ceilingyes, until OOMyes, until OOMyes, until OOMno — every context declares its memoryyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until the server's memory capyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until OOMyes, until the session's memory budgetyes, until OOM
Compile/parse-time storagedeclared, growable arena with its own ceilingimplementation detailimplementation detailimplementation detailthe compiler's own; no stated ceilingimplementation detailimplementation detailimplementation detailimplementation detailthe compiler's own heap — #run allocates in itimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detailimplementation detail (tsc's own heap)implementation detailthe comptime interpreter's own memory space; no published ceiling

On the three rows that carry a scale: the strongest answer , a weaker one , the weakest , and the answer that costs you . The descriptive rows are left uncolored on purpose — a collector and an allocator are two designs, not two grades.

How reclaim actually happens: reusable rows and retained byte storage

Follow one value through its lifetime

Adjust the workload and ceilings, then play or step through the call. Two slots can hold one block without copying its bytes.

Step 1 of 9

Reusable slot rows

Scope region: free rowNo stale handle yet

Call-scratch byte domain

Live charge0 / 1,024 B
No retained block

Result byte domain

Live charge0 / 512 B
No result block
Payload bytesRetained-block descriptor bytes
Ready to enter a scope

All rows are reusable. No payload bytes are reserved.

Illustrative retained-span model, not live telemetry. The model assumes 144 bytes per descriptor; this is an illustration, not an ABI claim. Table storage and other byte domains are omitted. A byte domain is an accounting category, not a physical arena. The host allocator controls physical placement and can use malloc.

Slots & regionsgenerational free list
Operand slots and regions live
in bounded tables. Release or
region reclaim returns a row to
a free list. Sequential scopes
reuse those rows; table extent
follows peak simultaneous use.

A region handle is 64 bits:
the low 32 name the row, the
high 32 stamp its generation.
A slot instead has a row id and
a separate lease generation.
Both reject a stale reference
after the row is reused.
Byte storageretained blocks
An owned span has one retained
block with a reservation for
its payload and descriptor.
Sharing adds a reference, not
another allocation or charge.

The first holder's release
keeps the block alive. The last
holder returns the whole block
and refunds its bundle once.

An explicit copy reserves the
destination while the source
is live. A refused reservation
leaves the source intact.
Byte domains meter ownership;
they are not separate heaps.
The host supplies the allocator.
Reclaim triggersevery exit path, once each
Release, replacement, region
reclaim, unwind, reset, and exec
close release the references
they own. A surviving holder
keeps its block's reservation.

Only the last holder returns
the block and refunds its whole
normalized bundle exactly once.
There is no tracing collection.

Trident exposes no pointer or
manual free to the program.
The native allocator still
allocates and releases storage;
this is not a promise that the
host heap cannot fragment.
26

Endpoints & events

The current on request handler checks route parameters against declared types. on configure runs before the Store opens. A false start publishes no partial running deployment.

stop requests a launch stop after the call returns. Normal returns and cleanup still run.

on request — a typed HTTP endpoint

Trident.tri
chest render(u64 id) {
    chest out = {}
    out.id = id
    return out
}

on request({
    method = "GET"
    pattern = "/items/{id}"
    parameters = [{ name = "id", source = "path", typeSpelling = "u64" }]
}, const p) {
    u64 id = p.id
    return render(id)
}
# the construct fixes every field type and the binding's chest, so writing `text method` or `const
# chest p` refuses and names the spelling
C.c
/* no ecosystem convention;
   raw sockets or a library
   such as libmicrohttpd, with
   request parsing by hand */
C#.cs
// framework-level (ASP.NET Core)
app.MapGet("/items/{id}", (long id) => Render(id));
C++.cpp
// library-level (e.g. Crow)
CROW_ROUTE(app, "/items/<int>")
([](int id){ return render(id); });
Cutlass.cut
# no endpoint form: an `on` header at module level refuses at parse, and the only handlers Cutlass
# declares are vector and device ones
#
# on request({ ... }, const p) {
#     ...
# }
#
# cut.host holds files, not sockets, and cut's admission law keeps a protocol out of the library:
# HTTP routing belongs to a platform node above cut, and none is designed
Dart.dart
// framework-level (shelf_router)
router.get('/items/<id>', (Request req, String id) {
  return Response.ok(render(id));
});
// a routing mistake surfaces at startup, not compile time
Elixir.ex
# framework-level (Phoenix)
get "/items/:id", ItemController, :show

def show(conn, %{"id" => id}) do
  id = String.to_integer(id) # a plain string; cast it yourself
  render(conn, id: id)
end
# a routing mistake surfaces at compile time only if the controller function itself is missing; a
# type mismatch surfaces at request time
Go.go
// framework-level (net/http)
http.HandleFunc("/items/{id}", func(w http.ResponseWriter, r *http.Request) {
        id := r.PathValue("id")
        // string; parse it yourself
    })
Haskell.hs
-- framework-level (Servant)
type Api =
  "items" :> Capture "id" Word64
          :> Get '[JSON] Item

getItem :: Word64 -> Handler Item
getItem itemId = render itemId
-- Servant checks the route shape at compile time through types, closer in spirit to on request than
-- most frameworks — but it's still a library, not grammar the compiler itself owns
Jai.jai
// nothing in the standard modules: no HTTP server, no route table, no request type. The Socket
// module gives you a TCP socket and stops there, so a route is text you matched by hand and a
// routing mistake surfaces only when a request hits it. Community modules exist, and none of them
// is the language's answer the way t.http is Trident's
Java.java
// framework-level (Spring)
@GetMapping("/items/{id}")
String getItem(@PathVariable long id) {
    return render(id);
}
JavaScript.js
// framework-level (Express)
app.get("/items/:id", (req, res) => {
  const id = req.params.id; // an untyped string
  res.json(render(id));
});
Julia.jl
using HTTP  # framework-level

function get_item(req)
    id = parse(UInt64, HTTP.getparams(req)["id"])
    render(id)
end

r = HTTP.Router()
HTTP.register!(r, "GET", "/items/{id}", get_item)
# a routing mistake surfaces at first request, not compile time
Kotlin.kt
// framework-level (Ktor)
routing {
    get("/items/{id}") {
        val id = call.parameters
            .getValue("id").toLong()
        call.respond(render(id))
    }
}
Lua.lua
-- framework-level (Lapis)
app:get("/items/:id", function(self)
    return render(self.params.id)
end)
Luau.luau
local Http = game:GetService("HttpService")
local res = Http:RequestAsync({
    Url = url, Method = "GET", })
-- outbound only, gated by a per-place setting an owner flips. There is no inbound server and no
-- route table: the engine calls YOU through its own events instead
Nim.nim
import jester

routes:
  get "/items/@id":
    let id = parseBiggestUInt(@"id")
    resp render(id)
# a bad pattern or a name clash surfaces when the routes block runs, not at compile time
Objective-C.m
// library-level (e.g. RoutingHTTPServer)
[server get:@"/items/:id"
    withBlock:^(RouteRequest *req, RouteResponse *res) {
    NSString *idStr =
        [req param:@"id"];
    // an untyped string; parse and validate it yourself
    [res respondWithString:
        render(idStr)];
}];
Perl.pl
# framework-level (Mojolicious::
# Lite)
get '/items/:id' => sub {
    my $c  = shift;
    my $id = $c->param('id');
    # untyped string; parse it yourself
    $c->render(text =>
        render($id));
};
PHP.php
<?php
#[Route('/items/{id}', methods: ['GET'])]
function getItem(int $id): Response {
    return render($id);
}
// framework-level (Symfony); a routing mistake surfaces at the first request, not at compile time
Python.py
# framework-level (FastAPI)
@app.get("/items/{id}")
def get_item(id: int):
    return render(id)
# a routing mistake surfaces at startup, not compile time
Ruby.rb
# framework-level (Sinatra)
get "/items/:id" do
  id = params[:id] # an untyped string
  render(id)
end
Rust.rs
// framework-level (Axum)
async fn get_item(Path(id): Path<u64>,) -> String {
    render(id)
}
// .route("/items/:id", get(get_item))
Scala.scala
// library-level (Tapir)
val getItem =
  endpoint.get
    .in("items" / path[Long]("id"))
    .out(stringBody)
    .serverLogic(id =>
      Right(render(id)))
// Tapir types query/path params at compile time — closest fit
Swift.swift
// framework-level (Vapor)
app.get("items", ":id") { req in
    let id = try req.parameters
        .require("id", as: UInt64.self)
    return render(id)
}
// a routing mistake surfaces at the first request, not compile
TypeScript.ts
// framework-level (Express + types)
app.get("/items/:id", (req: Request<{ id: string }>, res: Response) => {
  const id = Number(req.params.id); // parsed by hand
  res.json(render(id));
});
// param types are declared but not runtime-checked; a typo in the pattern still surfaces only at
// the first request
Verse.verse
# UEFN exposes no network surface at all: no server, no client, no socket, no route. A Verse program
# cannot reach outside its session by design — the platform's answer to the question Trident answers
# with a grant, decided once for every program instead of per program
Zig.zig
const std = @import("std");
// std.http.Server is a real stdlib
// primitive (unlike C/C++), but
// there is no route table — you read the request target as plain text and match it by hand
fn handle(target: []const u8) !u64 {
    const prefix = "/items/";
    if (!std.mem.startsWith(u8, target, prefix))
        return error.NotFound;
    return std.fmt.parseInt(u64, target[prefix.len..], 10);
}
// a routing mistake surfaces only when a request actually hits it

on event — a named handler a host delivers to

Trident.tri
void applyDamage(i32 amount) {
    t.log.info("damage " + text(amount))
}

on event("Damage", const i32 amount) {
    applyDamage(amount)
}
# one handler per name; a second declaration for "Damage" refuses at compile
C.c
/* a function-pointer table
   keyed by name, maintained
   entirely by hand */
register_handler("Damage", damage_handler);
C#.cs
public event Action<int> Damage;
Damage += amount => ApplyDamage(amount);
// a language feature, but never validated the way on request is
C++.cpp
// library-level (e.g. Qt signals,
// or std::function in a map)
bus.on("Damage", [](int amount) {
    applyDamage(amount);
});
Cutlass.cut
alias host = cut.host

variant Event with { width = u8 } {
    damage = 1 (i32 amount)
}

void applyDamage(i32 amount) {
    host.write(`damage {amount}`)
        else use 0
}

void deliver(Event e) {
    when (e) {
        is damage (amount) {
            applyDamage(amount)
        }
    }
}
# no `on event`: the program names its events as a variant, and the exhaustive when routes each one;
# a member with no arm, or with two, refuses at compile. Nothing delivers events: the program reads
# them from a channel or a device
Dart.dart
final damage =
    StreamController<int>.broadcast();
damage.stream.listen((amount) {
  applyDamage(amount);
});
// Streams are a language feature, but the payload type is only checked where you declare the
// controller's generic
Elixir.ex
:telemetry.attach("damage-handler", [:game, :damage], fn _event, %{amount: amount},
     _meta, _config ->
    apply_damage(amount)
  end, nil)
# :telemetry (the ecosystem's de
# facto event bus) allows many handlers on one name, and re-attaching the same handler id just
# replaces the old one silently — no compile-time refusal for a duplicate the way on event enforces
Go.go
// no built-in event system; a channel is the idiomatic stand-in
go func() {
    for amount := range damageCh {
        applyDamage(amount)
    }
}()
Haskell.hs
import Data.IORef

type Handler = Int -> IO ()

onDamage :: IORef [Handler]
            -> Handler -> IO ()
onDamage reg h =
    modifyIORef reg (h :)

fire :: IORef [Handler] -> Int -> IO ()
fire reg amount = do
    hs <- readIORef reg
    mapM_ ($ amount) hs
-- hand-rolled dispatch table, or an FRP library such as reactive-banana; a second registration for
-- "Damage" is never refused, just appended
Jai.jai
Handler :: #type (amount: s32);

handlers : Table(string, Handler);
table_set(*handlers, "Damage", apply_damage);
// no event or signal type exists. A procedure pointer in a table is the whole mechanism, and
// nothing checks that a name has a handler, or that it has only one
Java.java
// framework-level (Guava EventBus)
@Subscribe
void onDamage(DamageEvent e) {
    applyDamage(e.amount);
}
JavaScript.js
emitter.on("Damage", (amount) => {
  applyDamage(amount);
});
// EventEmitter/DOM events; payload type is unchecked
Julia.jl
# no built-in event system; a hand-rolled dispatch Dict, or the Observables.jl package, is the
# idiomatic stand-in
handlers = Dict{String,Function}("Damage" => amount ->
        applyDamage(amount),)
handlers["Damage"](amount)
Kotlin.kt
var onDamage: ((Int) -> Unit)? = null
onDamage = { amount ->
    applyDamage(amount)
}
// function types are first-class; nothing checks for a duplicate handler at compile time the way on
// event does
Lua.lua
-- hand-rolled dispatch table
handlers["Damage"] = function(amount)
    applyDamage(amount)
end
Luau.luau
part.Touched:Connect(function(hit)
    applyDamage(hit)
end)

local e = Instance.new("BindableEvent")
e.Event:Connect(onDamage)
e:Fire(10)
-- RBXScriptSignal is an engine type, not a language one: :Connect, :Once and :Wait, any number of
-- listeners per signal, and a connection you must :Disconnect or leak
Nim.nim
import std/tables

var handlers = initTable[string, proc (amount: int32)]()
handlers["Damage"] =
    proc (amount: int32) =
      applyDamage(amount)
# a hand-rolled dispatch table; the value is a real closure, not a bare function pointer like C's
Objective-C.m
[[NSNotificationCenter defaultCenter]
    addObserverForName:@"Damage"
    object:nil queue:nil
    usingBlock:^(NSNotification *n) {
        NSNumber *amount =
            n.userInfo[@"amount"];
        applyDamage(amount.intValue);
    }];
// built into Foundation, but the payload arrives as an untyped userInfo dict, unchecked like JS's
// EventEmitter
Perl.pl
# no core event system; a hand-rolled dispatch table
# (or Mojo::EventEmitter,
# bundled with Mojolicious)
my %handlers;
$handlers{Damage} = sub {
    my ($amount) = @_;
    apply_damage($amount);
};
$handlers{Damage}->($amount);
PHP.php
<?php
$dispatcher->addListener('Damage', function (DamageEvent $e) {
        applyDamage($e->amount);
    });
// framework-level (Symfony EventDispatcher); the payload type isn't checked against the event name
// at all
Python.py
# framework-level (e.g. blinker)
signal("Damage").connect(lambda amount: apply_damage(amount))
Ruby.rb
handlers = Hash.new { |h, k| h[k] = [] }
handlers["Damage"] << lambda do |amount|
  apply_damage(amount)
end
# no built-in event system; a Hash of callback lists, or a gem such as 'wisper', is the idiomatic
# stand-in
Rust.rs
// crate-level pub/sub, or a hand-rolled callback Vec
bus.subscribe("Damage", |amount: i32| apply_damage(amount));
Scala.scala
val handlers =
  scala.collection.mutable.Map
    .empty[String, Int => Unit]

handlers("Damage") = amount =>
  applyDamage(amount)
// hand-rolled dispatch table; Akka actors are the typed alt
Swift.swift
NotificationCenter.default.addObserver(forName: .damage, object: nil, queue: nil) { note in
    let amount = note.userInfo?["amount"]
        as? Int32 ?? 0
    applyDamage(amount)
}
// payload arrives boxed in userInfo, type unchecked; a second observer for the same name is never
// refused
TypeScript.ts
type Handlers =
    { Damage: (amount: number) => void };
const bus = new EventEmitter() as
    TypedEmitter<Handlers>;
bus.on("Damage", (amount) => {
  applyDamage(amount);
});
// plain EventEmitter is untyped; a wrapper (e.g. tiny-typed-emitter) is what adds this
Verse.verse
DamageEvent : event(int) = event(int){}

OnDamage()<suspends> : void =
    loop:
        Amount := DamageEvent.Await()
        ApplyDamage(Amount)

DamageEvent.Signal(10)

# event(t) is a real language type with
# Await and Signal. A device's own signal is a `listenable` instead: Button.InteractedWithEvent
#     .Subscribe(OnInteracted)
Zig.zig
const std = @import("std");

const Handler = *const fn (amount: i32) void;

fn onDamage(amount: i32) void {
    applyDamage(amount);
}

var handlers = std.StringHashMap(Handler)
    .init(allocator);
try handlers.put("Damage", &onDamage);
// no closures: a handler is always a plain, typed function pointer
// — and a second put() for
// "Damage" just overwrites the
// first, no compile-time refusal

on configure: a launch computes its own configuration

This entry-module fragment belongs to an application with a selected target and profile. It adds no configuration documents. A one-file script has no such selection and refuses this handler at the configure stage.

Application configure fragment.tri
on configure(const cfg) {
    chest result = { chest[] entries = [] }
    return result
}

Configure input and result carrier.

A program can request launch stop

Trident.tri
void closeLaunch() {
    stop
}

text main(text[] argv) {
    closeLaunch()
    return "the current call still finishes"
}

Launch handlers, scene folds, stop and crash

Launch handlers decide whether an application starts and observe its normal or faulted end. Scene handlers transform retained state. Each delivery is a bounded call, not a callback value.

A successful launch

  1. Configure
    Application computes documents before stores open.
  2. Start
    The handler accepts, refuses, or faults.
  3. Calls
    Entries and declared deliveries run.
  4. Stop
    Normal cleanup and on stop run.
A refused start never runs the entry. An immediate crash skips the remaining path, including on stop.
Start accepts; stop observes the exit.tri
on start(const launch) {
    # End of the block accepts this launch.
}
on stop(const details) {
    text cause = details.cause
    # Observe the exit; this handler cannot veto it.
}
text main(text[] argv) {
    return "finished"
}

Launch handler law.

Scene folds return the next retained state.tri
# Entry-module fragments, delivered by the scene pump.
# The host supplies state with u64 steps = 0.
on tick(const chest state, const chest tick) {
    chest next = state
    u64 steps = state.steps
    next.steps = steps + 1
    return next
}
on timer("flash", const chest state, const chest notice) {
    chest next = state
    next.flash = true
    return next
}

Tick law; scene timer law.

The scene pump runs due ticks and scene timer folds in order. Each successful result becomes the next step's state. A fault keeps the previous state. Monotonic and session timers use on event. A fold reaches only replayable libraries.

FormScopeWhat runs afterward
failA catchable language fault.The applicable fault handler and cleanup.
stopA cooperative request to end this launch after its current call.The rest of the call, cleanup, and on stop.
crashAn immediate exit of the process and every runtime it holds.No later statement, fault handler, deferred cleanup, or on stop.
Immediate exit with a fixed reason.tri
text main(text[] argv) {
    crash "state integrity lost"
}
# No result is returned. The process exits with 79. A reason is one nonempty plain text literal. A
# computed or interpolated reason refuses.

Crash law and refusal boundaries.

These handlers and statements have source and runtime evidence.

27

Built-in systems

The linked t.* catalog provides typed services across documents, storage, math, graphics, IO, security, and Host operations. A catalog entry proves its declared source route.

A program uses only lent authority. A linked library does not grant that authority by itself. Backend availability and product integration still need their own proof.

SystemTridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
Typed databaselinked lib, grant-gated (t.db)nonenonenonenone (cut.chest reads and writes documents)noneEcto (ecosystem-standard, not stdlib)nonethird-party (Hackage: persistent, sqlite-simple)nonenonenonenonelibrary (Exposed, Room)noneengine service (DataStoreService) — untyped keys and JSON valuesnimble pkg (db_connector, moved out of stdlib at 2.0)framework (Core Data / SQLite via C API)none (DBI + DBD::SQLite — CPAN)bundled ext (PDO_SQLite)stdlib (sqlite3)gem (sqlite3, ActiveRecord)nonenone in std (Slick, Doobie)none (third-party, e.g. GRDB)no stdlib; typed ORMs are idiomatic (Prisma, Drizzle)nonenone in std (C interop for SQLite)
3D scene rendererlinked lib, grant-gated (t.gfx + Thalassa)nonenonenonenone; the renderer is a later platform nodenonenone (Scenic is 2D-only)nonenonefirst-party modules (Simp, GL, Window_Creation)nonenonenonenonenonethe host engine (Roblox) — not a library the language linksnoneOS framework (SceneKit), Apple platforms onlynonenonenonenonenonenonenone (SceneKit/RealityKit are OS frameworks, not stdlib)nonethe host engine (UEFN) — not a library the language linksnone
Background jobs & timerslinked libs, grant-gated (t.timer, t.job) over a durable ledgernonestdlib (System.Threading.Timer)nonedeclared threads and cutrun.clock.sleep; no timer or job ledgerstdlib (dart:async Timer)stdlib (Process.send_after, :timer)stdlib (time.AfterFunc)stdlib (Control.Concurrent threadDelay loop)module (Thread)stdlib (ScheduledExecutorService)runtime-builtin (setTimeout)stdlib (Base.Timer)coroutines (delay, launch)noneengine-builtin (task.delay, task.wait)partial stdlib (std/asyncdispatch.addTimer)stdlib (NSTimer, GCD dispatch_after)partial: core builtin (alarm)partial (pcntl_alarm, Unix only)partial stdlib (sched)gem (Sidekiq, ActiveJob); no timer in stdlibnone in stdstdlib via JVM (ScheduledExecutorService)stdlib-adjacent (Foundation Timer / DispatchQueue)runtime-builtin (setTimeout), typed via @types/nodelanguage-builtin (Sleep, loop, spawn)none in std
Outbound HTTP clientlinked lib, operator-profile-gated (t.net.http)nonestdlib (HttpClient)nonenone (no socket in cut.host)stdlib (dart:io HttpClient; web needs package:http)stdlib (:httpc via OTP inets)stdlib (net/http)none in base (Hackage: http-client)none (Socket only)stdlib (HttpClient)runtime-builtin (fetch)partial stdlib (Downloads.jl)library (Ktor) / JVM interopnoneengine service, owner-gated (HttpService)stdlib (std/httpclient)stdlib (NSURLSession)stdlib (HTTP::Tiny)bundled ext (cURL) / streamsstdlib (urllib)stdlib (net/http)none in stdstdlib via JVM (HttpClient)stdlib-adjacent (Foundation URLSession)runtime-builtin (fetch), typed via lib.dom.d.tsnone — no network surface at allstdlib (std.http.Client)
Authenticated crypto & secretslinked lib, provider-gated (t.security.*)nonestdlib (System.Security.Cryptography)nonesecret compiled (erasure, constant-time ==); no crypto librarynone in stdstdlib (:crypto, OTP/OpenSSL)stdlib (crypto/*)none in base (Hackage: crypton)partial modules (Hash, Crypto)stdlib (JCE)runtime-builtin (Web Crypto)partial stdlib (SHA.jl)JVM interop (JCE)nonenonepartial stdlib (std/sha1, std/sysrand); full via nimcryptostdlib (Security framework, CommonCrypto)partial stdlib (Digest::SHA, hashing only)bundled ext (sodium, openssl)partial stdlib (hashlib)stdlib (openssl)none in stdstdlib via JVM (JCE)third-party (swift-crypto); CryptoKit on Apple OSesruntime-builtin (Web Crypto), typed via lib.dom.d.tsnonestdlib (std.crypto)
Deterministic telemetrylinked lib, pure (t.telemetry)nonenonenonedebug-build probe records only; none in releasenone:telemetry is ecosystem-standard, but event-based, not pure/deterministicnonenonenonenonenonenonenonenoneengine service (AnalyticsService) — sampled, not deterministicnonenonenonenonenonenonenonenonenonenonenonenone
Package distributiontripkg: pre-launch, digest-pinnednone (OS pkg mgr only)NuGetvcpkg / Conan (third-party)none: platform nodes live in one repositorypub + pub.devHex + Mix (hex.pm)go get + module proxyCabal + Hackage (or Stack + Stackage)a modules folder, copied in by handMaven/Gradle + Centralnpm + registryPkg.jl + General registryGradle/Maven + Maven CentralLuaRocksWally/Rojo — community, plus the Creator Storenimble + nimble.directoryCocoaPods / Swift Package Manager (third-party)CPAN + cpanmComposer + Packagistpip + PyPIRubyGems + Bundlercargo + crates.iosbt/Mill + Maven CentralSwift Package Manager + Swift Package Indexnpm + registry; untyped packages get types from DefinitelyTyped (@types/*)Epic's published module paths onlyzig build + build.zig.zon (hash-pinned, no central registry)
Runtime code compilationcompiles caller bytes; can't freely run the resultnone (dlopen a build artifact)Roslyn scripting — runs immediatelynone (dlopen a build artifact)none (no function value exists at run time)none (no eval)Code.eval_string/1 — runs immediatelynone (no eval)none in base; GHC API/hint compiles & runs immediately#run at COMPILE time; no runtime eval at allcompiler API / classloading — runs immediatelyeval()/Function() — runs immediatelyeval/include — runs immediatelyscripting API (kotlin-scripting-jsr223) — runs immediatelyload() — runs immediatelynone — loadstring is disabled on Robloxnone (dynlib.loadLib a build artifact)none (dlopen a build artifact, like C/C++)eval STRING — runs immediatelyeval() — runs immediatelycompile()/eval() — runs immediatelyeval() — runs immediatelynone (no eval)compiler API / classloading → runs immediatelynone (dlopen a build artifact, like C/C++)eval()/Function() on emitted JS — types are already gone, so eval'd code is uncheckednone — no eval of any kindnone (dlopen a build artifact)
Editor/authoring toolingAuthoring and debug backends exist. Wheelhouse is the IDE host.none (any IDE — third-party)none (Visual Studio — third-party)none (any IDE — third-party)none (the compiler's linter only)none (VS Code, DartPad — third-party)none (ElixirLS, Livebook — third-party)none (VSCode + gopls — third-party)none (HLS, VSCode — third-party)none (community plugins — the compiler ships no language server)none (IntelliJ, Eclipse — third-party)none (VSCode, WebStorm — third-party)none (VSCode + Julia extension — third-party)none (IntelliJ IDEA — separate product)none (ZeroBrane, VSCode — third-party)the language ships its own type checker and linter, which Studio consumes; Studio itself is Roblox's productnone (VSCode + nimlsp — third-party)none (Xcode — Apple's tool, not language-level)none (Padre discontinued; VSCode — third-party)none (PhpStorm, VSCode — third-party)none (VSCode, PyCharm — third-party)none (RubyMine, VSCode — third-party)none (VSCode + rust-analyzer — third-party)none (Metals, IntelliJ — third-party)none (Xcode, VSCode + LSP — vendor/third-party)the compiler's own language service (tsserver) powers editor tooling directly; the editor itself (VS Code) is still third-partynone (UEFN is the editor — Epic's tool, not language-level)none (VSCode + ZLS — third-party)

Linked into the language and gated , in the standard library or the runtime , partial or third-party , absent . Package distribution is left uncolored: every row of it is a real answer, and which one is better is the argument this page is making rather than a fact it can score.

28

What Trident deliberately omits

Trident deliberately omits function values, lambdas, local type inference, textual macros, and ambient source imports. Calls and declared events keep executable behavior visible to the compiler.

Explicit types replace inferred bindings. Named library calls replace callbacks. A declared bundle fixes the source set before execution.

Has this?TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
Closures / function valuesNoPartialYesYesNoYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesPartial
Untyped dict / boxed valueNoNoPartialPartialNoPartialYesPartialPartialPartialPartialYesYesPartialYesYesPartialPartialYesYesYesYesPartialPartialPartialPartial — any opts out, never the defaultPartialNo
Source loader / pkg managerNoNoYesNoNoYesYesYesYesNoYesYesYesYesYesYesYesNoYesYesYesYesYesYesYesYesNoYes
Reflection over runtime stateNoNoYesPartialNoPartialYesPartialYes, but only inside IO — a pure function's type excludes itYesYesYesYesYesYesYesPartialYesYesYesYesYesNoYesPartialYes on erased JS values (Reflect, typeof) — none of it sees TS's own typesNoNo
Implicit float-width conversionNoYesYesYesNoPartialN/ANoNoYesYesN/AYesPartialN/AN/ANoYesN/AN/AN/AN/ANoYesPartialN/A — one number type, same as JSN/ANo
Implicit signed/unsigned conversionPartialYesNoYesPartialN/AN/ANoNoNoN/AN/ANoNoN/AN/ANoYesN/AN/AN/AN/ANoNoNoN/A — same numeric type as JSN/ANo
Ambient env/clock read anywhereNoYesYesYesPartialYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesNoYes

This table codes presence, not quality: present , partly present , absent , not applicable . Every “No” in the Trident column is a decision, and the same “No” elsewhere may be an accident — the color cannot tell you which, and does not try.

29

Where Trident falls short

This section lists choices and gaps that affect an author. Trident has no function value or generic function. Every binding needs its complete type.

Typed dictionaries and their column helpers work. Some registered kinds still lack source constructors, operators, or binding families. The capability table separates those gaps.

The page gives no speed ranking.

Has this?TridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
Nominal types the program declares, with methods or interfacesNo nominal method/interface type. Named value type declarations work, but their values still refuse. Open outline declarations and exact ingot declarations work. A trove combines an outline with storage.PartialYesYesPartial: struct and variant, with structural contracts; no methodsYesYesYesYesPartialYesYesYesYesPartialPartialYesYesPartialYesYesYesYesYesYesYesYesPartial
One function the program writes that works over many typesNo generic function. An outline parameter accepts records that fit its required fields, but each declared field keeps its exact type.PartialYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYes
A map or dictionary in the language or its standard libraryYes: typed literals, reads, writes, members and snapshot iteration; t.data.dict.keys, values, and fromArrays are library functionsNoYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYes
Pattern matching that destructures a valuePartial: expect binds record fieldsNoYesPartialPartial: a when arm binds a payloadYesYesNoYesNoYesPartialPartialPartialNoNoPartialNoNoPartialYesYesYesYesYesPartialPartialPartial
Local type inferenceNo: every binding states its typePartialYesYesNo: every binding states its typeYesN/AYesYesYesYesN/AN/AYesN/AYesYesPartialN/AN/AN/AN/AYesYesYesYesYesYes
Values interpolated into a text literalPartial: `…{x}…`, each hole text(x); no format specifier, since a presentation is a named call in the holePartialYesPartialPartialYesYesPartialPartialPartialPartialYesYesYesPartialYesYesPartialYesYesYesYesYesYesYesYesYesPartial
Threads, async tasks or actorsPartial: an on job handler runs on its own worker runtime and host thread, sharing nothing; task isolates and runs nothing in parallel.YesYesYesYes: declared, never spawnedYesYesYesYesYesYesYesYesYesPartialPartialYesYesYesPartialYesYesYesYesYesYesYesYes
Calling a C library from the programNo: only host-linked t.* librariesYesYesYesNo: Cutlass links no other languageYesPartialYesYesYesYesPartialYesYesYesPartialYesYesYesYesYesYesYesYesYesPartialNoYes
Compile-time metaprogrammingNoPartialYesYesYesPartialYesPartialYesYesPartialNoYesPartialNoNoYesPartialPartialNoNoNoYesYesYesPartialNoYes
References to shared mutable dataNo: every copy is a valueYesYesYesYesYesPartialYesPartialYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYesYes
A public registry of third-party packagesNo: tripkg is prelaunchNoYesPartialNoYesYesYesYesNoYesYesYesYesYesPartialYesYesYesYesYesYesYesYesYesYesNoPartial

This table codes presence: present , partly present , absent , not applicable to a dynamically typed language . Rows retained here expose a cost or a remaining boundary; a completed feature is marked present. A Partial elsewhere names a narrower form: C's function pointers and printf, Lua's coroutines without parallelism, Go's fmt.Sprintf in place of interpolation, Elixir's immutable data with state held in processes.

Kind or constructSupported operationsRefused by design
exacta literal at every destination; the native symbolic scalar, canonical codec, exact arithmetic and explicit native numerical bridges
decimal<scale>exact + - * /, negation and ordering; a product derives the summed scale and pinned dimensionsa quotient that cannot be represented at its declared result scale faults; there is no silent rounding
measurementa validated literal, ==, named estimate, standardUncertainty and degreesOfFreedom reads, and t.math.uncertaintygeneric + and ordering deliberately refuse; combination is a named call that states correlation
color<space>a literal in its space, ==
rotor, bivector, multivectora literal under its signature, ==, and the t.math.geometricAlgebra members: the geometric product, wedge and contractions, exp and log, and applying a rotor or a motoran operator over a blade tuple, which deliberately refuses, because every geometric operation is a named member
dualforward-mode + - * /, negation, value-lane ordering and t.math.dual compositionimplicit conversion to a vector or a plain real; these deliberately refuse
cellid<grid>, fieldgrid/axis literals and the t.geo.grid cell-id members; co-registered fields take samplewise + - * /, scalar scaling and negation with derived dimensionstwo fields whose axes differ deliberately fault.
frameida bare literal such as builtin:itrs, ==, a document round trip, and a static frame parameter on the rigid carriers, the spatial and planar roles, a Euclidean bivector, the affine maps, the states, the coordinates, the bounds and every shapeorder, arithmetic and relabeling deliberately refuse.
pose, twist, wrench and their binary64 twinsqualified literals, canonical pose quaternion sign, ==, document round trips and typed named group reads
point3, dir3, normal3 and their binary64 twinsa literal under built-in and application frames, with a point's unit or a direction's family and unit, a zero normal refused, ==, a document round trip
point2, dir2, normal2 and their binary64 twinsa literal under an application frame, a built-in frame refused, a zero normal refused, ==, a document round trip
ray, plane, sphere, capsule, obb and their binary64 twinsqualified types, whole-value validation, document round trips and typed named group reads such as ray.origin and sphere.radius
segment2, ray2, line2, triangle2 and their binary64 twinsqualified types under an application frame, a built-in frame refused, whole-value validation and document round trips
segment3, line3, triangle3 and their binary64 twinsqualified types under built-in and application frames, whole-value validation and document round trips
circle, rect, obb2, ellipse, capsule2 and their binary64 twinsqualified region values under an application frame, fixed named fields, whole-value validation and runtime-basis record images
state3, dstate3a bare nested-tuple literal under built-in and application frames at each time domain, the epoch keeping its own scale, == reading the epoch as an instant, a document round trip
ip<version>a bare literal such as 2001:db8::1, ==, a document round trip, and the Host endpoints, the peer plane's datagram source check and the t.net.peer address cells that carry itorder, arithmetic and conversion deliberately refuse, and a dual-stack consumer converts an IPv4-mapped address in a member of its own.
ipprefixa bare literal such as 192.0.2.0/24, ==, a document round trip, an ordered index by family, network address and length, and native containment and field reads
almanac<domain>Declared members, typed properties, module reaches, order by permanent id, dictionary keys, and retired-member restrictions. A bare numeric destination and id projection refuse.
ciphertexta quoted literal of its value text, the null test, a document round trip, and every refusal the kind is built on: ==, ordering and text(v) refuse at compile
outline, ingotNative structural images, Chest fit, independent static checks, and outline and ingot source declarations with include/check rows and module-path reaches; no statement writes into an ingot.
pwhasha quoted PHC literal, the null test, a document round trip, and hashing and comparison through t.security.crypto.passwordHash and passwordCompare==, ordering and text(v), which deliberately refuse at compile, because a verifier is compared only through passwordCompare
timepoint<domain>+ and - with a duration, the duration between two timepoints, ordering, all within one domain; the t.time.<domain> verbs; domain mapsan operator across two domains, which refuses until a domain map converts one side
civil, civildate, civiltimea literal, ==, field ordering, conversion to and from timepoint<unix>an order between two civil labels whose offsets differ, which faults at execute because no timeline authority places them
duration, money+, -, negation, ordering, and * by an i64; a duration also divides by onea factor typed at another integer width, which deliberately refuses: step * n over an i32 n refuses where step * i64(n) runs, and a bare step * 2 reads its factor as i64
matrices, quat, tensormatrix composition/application, quaternion composition/rotation, tensor @, elementwise arithmetic and typed dimension derivationa quaternion lane-wise product or vector @; these deliberately refuse
a value-producing if or whendestination-typed arms, use, exhaustive coverage and selected-arm-only executiona reaching arm without a result or an implicit join of different complete types; these refuse
the debuggerruntime stops and Host debug-association pause, step, statement breakpoints and capture points
30

One language, every place it runs

The Host selects a composition root and its linked libraries. The language and grant rules stay the same across those roots. A listed root does not prove a released or tested product.

Native CLI and runtime paths exist.

ReachTridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
Server / serviceruntime live in tricli, on request is language law.yes, and most of the servers below are written in ityes (ASP.NET Core)yesPartial: hosted x64 ELF/PE artifacts are built.yes (dart:io, shelf)yes — OTP and Phoenix are its home domainyes — its home domainyes (Servant, Yesod)no server story; the Socket module and your own loopyes (Spring, Jakarta)yes (Node, Deno, Bun)possible, uncommonyes (Ktor, Spring)embedded in one (OpenResty), rarely the server itselfRoblox servers only — you do not deploy oneyes (Jester, Prologue)rare, and Apple-platform onlyyes, historically (mod_perl, Mojolicious)yes — its home domainyes (Django, FastAPI)yes (Rails)yes (axum, actix)yes (Akka, Play)yes (Vapor), Apple-centric in practiceyes, through Nodeno — a UEFN session is not a server you deployyes (std.http.Server)
Native desktop clienttri is live; the desktop and xr assemblies compose the same runtime over Thalassayes, with an OS toolkit you pickyes (WPF, WinUI, MAUI) and Unityyes (Qt, native SDKs)Partial: the compiler writes PE images.yes (Flutter) — one of its strongest claimsrare (Scenic, 2D only)no first-party GUI; third-party bindingsrare (GTK bindings)yes — the point of the language, with Simp, GL and Window_Creation in the modulesyes (Swing, JavaFX)through Electron — a second runtime shipped with the apprareyes (Compose Multiplatform, Android)embedded in a framework (LÖVE)the Roblox client — same source both sides, inside Robloxthird-party bindingsyes (AppKit, UIKit) — its home domainrare (Tk)noyes (Tk, Qt), rarely how a product shipsrare (Shoes, bindings)third-party (egui, Tauri, winit)JVM toolkitsyes (SwiftUI, AppKit) — its home domainthrough Electron, as JSthe Fortnite client — you ship an island, not an appno first-party GUI
In the browserwasm is one of six declared backends: webtricli runs Trident in the browser, host_browser supplies its host services, and MerDB stores data in browser storagethrough EmscriptenBlazor WebAssemblythrough Emscriptenwasm32 is an optional IL target, last in the back-end order; no browser hostcompiles to JS and to Wasm, first-classno — LiveView renders on the serverGOOS=js, and the binaries are largethe GHC JS backend, youngno Wasm backendno (applets retired); TeaVM is third-partynative — its home domainnoKotlin/JS and Kotlin/Wasmthird-party ports (Fengari)no — Roblox is the only targetcompiles to JS, first-classnonoexperimental Wasm portsPyodide / PyScript — a whole CPython in Wasmruby.wasm, youngthe wasm32 target — its strongest non-native storyScala.js, matureSwiftWasm, community-maintainednative — it compiles to JSnothe wasm32 target, first-class
Command-line scriptingtricli script mode, one source unit, and #!/usr/bin/env tricli is an ordinary comment rather than a loader lineno — compile firsttop-level statements, dotnet scriptno — compile firstno — compile first; a tool is one static executableyes (dart run)yes (.exs)go run compiles on every invocationrunghc, stack scriptno — compile first, and a shebang needs a polyglot hacksingle-file source launch (11+)yes — one of its home domainsyes.kts, with a slow startyes — its home domainthe luau CLI exists; on Roblox there is no shellnim r compiles firstnoyes — its home domainyesyes — its home domainyesno; cargo-script is youngscala-cliyes (swift file.swift)through tsx, Deno or Bunnozig run compiles first
Embedded in a host applicationthe embedding boundary is declared: restricted execution is a VM boundary, and a compile runs under its own ledger and ceilingsit usually is the hostUnity, and CLR hostingit usually is the hostthe Flutter engine embeds it; embedding it yourself is rarenono — the runtime is hostile to itnonoJNI, awkwardyes (V8, QuickJS, JavaScriptCore) — very commonembeddable, and heavyJVM embeddingyes — the reference answer, and the reason it existsyes — a hardened Lua built for exactly thisit compiles to C, so it embeds where C doesnoyes, historicallynoyes (the CPython C API), and heavymrubyno — rhai and mlua are other languagesJVM embeddingnothrough a JS engineno — UEFN is the only host there isno
Device OS / bare metalyes — the reference answerno (nanoFramework is a niche)yesNo complete bare-metal runtime is claimed.noNerves, on top of LinuxTinyGo, a restricted subsetnono runtime to depend on, but no bare-metal story eithernononoKotlin/Native, not bare metaleLua, NodeMCU — nichenoyes — a real embedded storynononoMicroPython — a dialect, not the languagemrubyyes (no_std) — a real strengthnoEmbedded Swift, youngnonoyes (freestanding targets) — a real strength
The same language, or a dialect per domain?one language and one source; what changes is which archives the composition root links and which grants it lendsone language, a different standard library per targetone language, a different runtime per domainone language, a different toolchain per targetone language and one cut; a target description selects the hostone language across all of themone language, one domainone language, with build tagsone language, plus a young second backendone language, one domainone language, plus Android's own platformone language, a different runtime AND a different API surface per domainone language, one domainone language, three backends that do not share librariesone language, plus a dialect per embeddera dialect of Lua, and one domainone language, three backendsone language, one vendorone languageone languageone language, plus MicroPython as a dialectone language, plus mrubyone language across all of themone language, two backendsone language, plus Embedded Swift's subsetone language, erased to JS wherever it goesone language, one domainone language across all of them

First-class in the language's own toolchain , supported and standard , reachable through a second toolchain, a dialect or work a plan row owes , and absent .

31

Dimensional derivation

A pinned amount carries its dimension in its type. A mass times an acceleration derives force from the SI exponents. A sum across incompatible dimensions refuses.

The numeric carrier stays explicit. A unit mismatch cannot silently cross an assignment or a call that requires another complete type.

Trident.tri
f64<mass> load = 5kg
f64<acceleration> g = 9.81m/s^2

f64<unit=kg*m/s^2> push = load * g
# the product composed a force dimension at its coherent unit, and no family is guessed for it

f64<mass> bad = load + push  # refuses at compile: a sum reads one dimension, and these are two
C.c
typedef struct { double kg;   } Mass;
typedef struct { double mps2; } Accel;
typedef struct { double n;    } Force;

Force push(Mass m, Accel a) {
    Force f = { m.kg * a.mps2 };
    return f;
}
/* nothing stops m.kg + f.n — both are a
   double wearing a different struct name */
C#.cs
// UnitsNet
Mass load = Mass.FromKilograms(5);
Acceleration g =
    Acceleration.FromMetersPerSecondSquared(9.81);
Force push = load * g;
// real compile-time safety — but from an
// operator* hand-written for THIS exact pair.
// A product nobody enumerated has no operator at all, so the library grows one class and one
// overload per quantity it supports
C++.cpp
// mp-units (or boost::units)
using namespace mp_units;
quantity load = 5 * si::kilogram;
quantity g = 9.81 * si::metre
             / square(si::second);
auto push = load * g;   // si::newton, derived
// load + push;
// ^ static_assert: several screens of
//   template text that name neither kg nor N
Cutlass.cut
f64<mass> load = 5kg
f64<acceleration> g = 9.81m/s^2

f64<unit=kg*m/s^2> push = load * g
# the product composes a force at its coherent unit, by the MerTekTypes result law the kinds carry;
# push is still one eight-byte f64

f64<mass> bad = load + push  # refuses at compile: a sum reads one dimension, and these are two
Dart.dart
// package:quantity
final load = Mass(kg: 5);
final g = Acceleration(metersPerSecondSquared: 9.81);
final push = load * g;
// a MiscQuantity whose Dimensions were multiplied at RUNTIME; asking whether it is a force is an
// `if` you remember to write
Elixir.ex
# ex_cldr_units
{:ok, load} = Cldr.Unit.new(:kilogram, 5)
{:ok, g} =
  Cldr.Unit.new(:meter_per_square_second, 9.81)
Cldr.Unit.Math.mult(load, g)
# every answer is a runtime {:ok, _} or
# {:error, _} tuple; the library converts and
# formats units, it does not do dimensional algebra over them
Go.go
type Mass struct{ Kg float64 }
type Accel struct{ Mps2 float64 }
type Force struct{ N float64 }

func Push(m Mass, a Accel) Force {
    return Force{m.Kg * a.Mps2}
}
// no operator overloading, so every product is a function you name by hand; nothing derives
// kg*m/s^2 from kg and m/s^2
Haskell.hs
-- dimensional package
load = 5.0 *~ kilo gram
g    = 9.81 *~ (meter / second ^ pos2)
push = load * g   -- Force, exponents carried as type-level integers
-- load + push
-- ^ compile error, and a readable one: the
--   dimension appears in the type signature
Jai.jai
Mass  :: struct { kg: float64; }
Accel :: struct { mps2: float64; }
Force :: struct { n: float64; }

operator * :: (m: Mass, a: Accel)
    -> Force {
    return .{ n = m.kg * a.mps2 };
}
// Jai has operator overloading and polymorphic structs, so a real dimension type is writable — one
// operator per pair somebody enumerated. Nothing in the modules does it
Java.java
// JSR-385 / Indriya
Quantity<Mass> load = getQuantity(5, KILOGRAM);
Quantity<Acceleration> g =
    getQuantity(9.81, METRE_PER_SQUARE_SECOND);
Quantity<?> push = load.multiply(g);
// the product erases to `?`; recovering Force is push.asType(Force.class), checked at runtime.
// load.add(push) throws, not fails to compile
JavaScript.js
import { unit, multiply, add } from "mathjs";
const load = unit(5, "kg");
const g = unit(9.81, "m/s^2");
const push = multiply(load, g);   // 49.05 N
add(load, push);   // throws "Units do not match" — at runtime
// `load * g` is not an error, it is NaN
Julia.jl
# Unitful.jl
using Unitful
load = 5u"kg"
g = 9.81u"m/s^2"
push = load * g        # 49.05 kg m s^-2
load + push
# DimensionError: kg and kg m s^-2 are not compatible — at runtime, though the units do live in the
# parametric type
Kotlin.kt
@JvmInline value class Mass(val kg: Double)
@JvmInline value class Accel(val mps2: Double)
@JvmInline value class Force(val n: Double)

operator fun Mass.times(a: Accel) =
    Force(kg * a.mps2)
// one operator per pair, written by hand. The compiler checks the pairs you wrote and knows nothing
// about dimensions
Lua.lua
local Unit = {}
Unit.__index = Unit
Unit.__mul = function(a, b)
  return setmetatable({ v = a.v * b.v, d = a.d .. "*" .. b.d }, Unit)
end
-- the dimension is a string you concatenate, nothing compares it, and there is no compile step that
-- could
Luau.luau
local Mass = {}
Mass.__mul = function(a, b)
    return setmetatable({v = a.v * b.v}, Force)
end
-- a metatable restores the operator and nothing checks the dimension. The Luau checker cannot
-- express it either: __mul is not part of a table type, so the product is `any`
Nim.nim
type
  Mass  = distinct float
  Accel = distinct float
  Force = distinct float

proc `*`(m: Mass, a: Accel): Force =
  Force(m.float * a.float)
# distinct types plus one operator per pair. Unit packages exist on nimble; none is standard, so
# this is the common answer
Objective-C.m
NSMeasurement *load = [[NSMeasurement alloc]
    initWithDoubleValue:5
    unit:NSUnitMass.kilograms];
NSMeasurement *g = [[NSMeasurement alloc]
    initWithDoubleValue:9.81
    unit:NSUnitAcceleration
         .metersPerSecondSquared];
// same Foundation surface, same gap: no product, no NSUnitForce. The unit is lost at the multiply
Perl.pl
my $load = 5;      # kg, by convention
my $g    = 9.81;   # m/s^2, by convention
my $push = $load * $g;
my $bad  = $load + $push;   # runs fine
# CPAN's Physics::Unit parses and converts
# unit strings, but a Perl scalar carries no type at all, so every check is a runtime string
# comparison you opted into
PHP.php
// php-units-of-measure
use PhpUnitsOfMeasure\PhysicalQuantity\Mass;
$load = new Mass(5, 'kg');
// the package models each quantity in its own class and defines no product between two of them, so
// a force is
// $load->toUnit('kg') * 9.81 — a float, with
// the unit dropped at the multiply
Python.py
from pint import UnitRegistry
u = UnitRegistry()
load = 5 * u.kilogram
g = 9.81 * u.meter / u.second ** 2
push = load * g          # 49.05 kg*m/s**2
load + push
# DimensionalityError — raised at runtime, on the line that ran, in production
Ruby.rb
require "ruby-units"
load = Unit.new("5 kg")
g    = Unit.new("9.81 m/s^2")
push = load * g       # 49.05 kg*m/s^2
load + push
# ArgumentError: incompatible units — at runtime, from a string parse
Rust.rs
// uom crate
use uom::si::f64::{Acceleration, Force, Mass};
let load = Mass::new::<kilogram>(5.0);
let g = Acceleration::new::
    <meter_per_second_squared>(9.81);
let push: Force = load * g;
// genuinely compile-checked, via type-level
// integers (typenum) standing in for the
// seven exponents
// let bad = load + push;  <- won't compile
Scala.scala
// squants
import squants.mass.Kilograms
import squants.motion.MetersPerSecondSquared
val load = Kilograms(5)
val g = MetersPerSecondSquared(9.81)
val push: Force = load * g
// compile-checked — from a `*` overload written for this pair, one per combination the library
// chose to support
Swift.swift
let load = Measurement(value: 5, unit: UnitMass.kilograms)
let g = Measurement(value: 9.81, unit: UnitAcceleration.metersPerSecondSquared)
// Foundation is native and dimension-tagged, but defines no product between two Measurements and
// ships no force unit: you take .value off each, multiply the raw doubles, and re-label by hand
TypeScript.ts
// safe-units / ts-units
const load = Mass.kilograms(5);
const g = Acceleration.metersPerSecondSquared(9.81);
const push = load.times(g);   // Force checked at compile, by a phantom exponent tuple in the type.
// All of it erases before the JS runs, so a value crossing a JSON boundary is an unlabeled number
// again
Verse.verse
mass := struct:
    Kilograms<public> : float = 0.0

# no unit, no dimension, and no operator overloading to build one with: a user cannot define * in
# Verse. So every combination is a named function you wrote, and nothing stops a caller passing
# seconds to the one that wanted kilograms
Zig.zig
const Mass  = struct { kg: f64 };
const Accel = struct { mps2: f64 };
const Force = struct { n: f64 };

fn push(m: Mass, a: Accel) Force {
    return .{ .n = m.kg * a.mps2 };
}
// comptime is strong enough to build a real dimension type; nothing in std does, so every project
// that needs one writes it
32

The amount representation is part of the type

quantity<f64> carries a family and unit with a binary64 amount. The numeric representation is part of its complete type. Mixed float widths require explicit conversion.

Trident.tri
quantity<amount=f64> d = 2.5m:length
quantity<amount=f64> sum = d + d
# the family rides in the value, and the sum clears its unit

quantity<amount=f64> area = d * d
# a product composes the dimension

quantity<amount=f32> f32d = 2.5m:length
quantity<amount=f64> bad = d + f32d  # refuses
C.c
float narrow = 2.5f;
double wide = 2.5;
double sum = narrow + wide;
/* the usual arithmetic conversions promote
   narrow to double with no diagnostic at
   any warning level that is on by default */
C#.cs
float narrow = 2.5f;
double wide = 2.5;
double sum = narrow + wide;  // implicit widening, silent, same as Java. A `readonly record struct
// Length(double M)` gets you a name; the representation and the unit are still just documentation
C++.cpp
float narrow = 2.5f;
double wide = 2.5;
auto sum = narrow + wide;   // double a strong typedef needs a class, a constructor, and one
// operator per arithmetic you want back; the standard
// library offers std::chrono for time and
// nothing at all for anything else
Cutlass.cut
quantity<f64> d = 2.5m:length
quantity<f64> sum = d + d
# the family rides in the value, and the sum clears its unit

quantity<f64> area = d * d
# a product composes the dimension

quantity<f32> f32d = 2.5m:length
quantity<f64> bad = d + f32d  # refuses no conversion is implicit: the one widening is two integer
# widths at their join, so a bare f32 + f64 refuses as well
Dart.dart
double d = 2.5;
// Dart has one double (binary64). Float32 is reachable only through Float32List, a typed-array
// element, not a type you can declare a variable or a parameter at — so the representation cannot
// be part of any signature
Elixir.ex
d = 2.5
# Erlang floats are binary64 and there is exactly one of them. Nx tensors carry a
# :f32/:f64 type tag, but that is a tensor
# property read at runtime, not a type on a scalar binding or a function head
Go.go
var narrow float32 = 2.5
var wide float64 = 2.5
// _ = narrow + wide  <- Go DOES refuse this
sum := float64(narrow) + wide
// Go is the one language here that refuses mixed float widths outright. It still has no family, no
// unit, and no way to keep the two apart once you have written the cast
Haskell.hs
narrow :: Float
wide   :: Double
-- narrow + wide  <- type error, Haskell is
-- strict here, and realToFrac is explicit. Family and unit need the `dimensional` package on top,
-- which carries the representation as its own type parameter
Jai.jai
narrow : float32 = 2.5;
wide   : float64 = 2.5;
sum := narrow + wide;    // float64 the widening is implicit and silent, unlike Zig's. The
// representation is in the type and the family is not, so two float32s that mean entirely different
// things still add
Java.java
float narrow = 2.5f;
double wide = 2.5;
double sum = narrow + wide;  // compiles, widening primitive conversion, silent. Java has no way to
// declare "this API takes a float and refuses a double", and no family or unit anywhere in the type
JavaScript.js
const d = 2.5;   // meters, by convention JavaScript has exactly one numeric type. There is no f32,
// no declared precision, no family, no unit — a length, a mass and a count are the same `number`,
// and the only check available is one you write
Julia.jl
narrow = Float32(2.5)
wide = Float64(2.5)
sum = narrow + wide     # Float64 Julia's promotion rules widen silently and by design. Unitful adds
# family and unit as type parameters, so Julia comes closest here — the representation still
# promotes
Kotlin.kt
val narrow: Float = 2.5f
val wide: Double = 2.5
val sum = narrow + wide      // Double Kotlin defines plus(Double) on Float, so the widening is an
// overload rather than a conversion — but it is just as silent, and the domain is absent either way
Lua.lua
local d = 2.5
-- Lua numbers are one f64 lane (integers since 5.3). There is no narrow float, no declared
-- representation, no family. A metatable can wrap a table, but nothing checks it before the program
-- runs
Luau.luau
local narrow: number = 2.5
local wide: number = 2.5
local sum = narrow + wide
-- one `number`, and none of Luau's real types is narrower than binary64.
-- `type Meters = number` is a synonym
-- the checker ERASES, so meters and seconds add without a word
Nim.nim
let narrow: float32 = 2.5
let wide: float64 = 2.5
let sum = narrow.float64 + wide
# Nim keeps the widths apart and converts explicitly. `distinct float64` gives a family name at the
# cost of borrowing or rewriting every operator you want back
Objective-C.m
float narrow = 2.5f;
double wide = 2.5;
double sum = narrow + wide;   /* silent */
// NSMeasurement stores a double and a unit; the representation is fixed and invisible, and C's
// promotions apply underneath it
Perl.pl
my $d = 2.5;
# a Perl scalar holds an NV — one C double. There is no narrow float, no declared width, no family,
# and no static check of any of it; `use types` is not a thing
PHP.php
$d = 2.5;
// PHP has one float (binary64) and no declared width. A typed property can say
// `public float $d`, which admits every
// float there is; there is nothing narrower to declare and nothing to attach a family or a unit to
Python.py
d = 2.5            # meters, by convention
narrow = numpy.float32(2.5)
d + narrow         # numpy.float64(5.0)
# the f32 was widened silently and the result claims f64 precision it does not have. Python has one
# float type and no way to say "this one and only this one"
Ruby.rb
d = 2.5
# Ruby has Float (binary64) and Rational and BigDecimal, and no declared-width float at all. 2.5 +
# Rational(1,2) silently returns a Float, discarding the exactness — coercion picks the wider type
# and never asks
Rust.rs
let narrow: f32 = 2.5;
let wide: f64 = 2.5;
// narrow + wide  <- Rust refuses this too
let sum = f64::from(narrow) + wide;
// same story: widths are kept apart, the domain is not. A newtype gives you the family, at the cost
// of impl Add, impl Sub, impl Mul, impl Display, one per type
Scala.scala
val narrow: Float = 2.5f
val wide: Double = 2.5
val sum = narrow + wide      // Double numeric widening is an implicit conversion in Predef, applied
// silently. squants adds family and unit over Double only, so the representation axis stays empty
Swift.swift
let narrow: Float = 2.5
let wide: Double = 2.5
// narrow + wide  <- Swift refuses this
let sum = Double(narrow) + wide
// Swift is strict about widths. Measurement covers unit and family, but only over Double — there is
// no Measurement<Float>, so the two axes never meet
TypeScript.ts
const d: number = 2.5;
type Meters = number & { __brand: "m" };
// branding gets a family name past tsc, and nothing else: both operands are `number` at runtime,
// `f32` has no type to brand, and `a + b` on two brands yields plain `number`, quietly dropping the
// brand
Verse.verse
Narrow : float = 2.5
Wide : float = 2.5
Sum := Narrow + Wide

# there is nothing to declare: one
# float (binary64) and one int
# (arbitrary precision). A program
# cannot state a representation, a family or a unit — and so cannot get any of them wrong either
Zig.zig
const narrow: f32 = 2.5;
const wide: f64 = 2.5;
// narrow + wide  <- Zig refuses this
const sum = @as(f64, narrow) + wide;
// Zig refuses the implicit widening and makes you write @floatCast/@as. No family, no unit, and no
// place in std to put one

Roots preserve compile-time dimensions

Trident.tri
f64<unit=m3/kg/s2> G = t.quantity.fromUnit(6.674e-11, "m3/kg/s2", "")
f64<family=mass> mass = 5.972e24kg
f64<family=length> radius = 6771000.0m
f64<unit=m/s> speed =
    t.quantity.root(G * mass / radius, 2)
f64 metersPerSecond = t.quantity.inUnit(speed, "m/s")
33

Exact where exact exists, and a refusal where it doesn't

Exact amounts preserve rational values and integer powers of pi. A quarter turn can remain exactly pi divided by two. An operation the carrier cannot represent refuses rather than rounds.

The native symbolic exact kind and its codecs work. A runtime-dimensional exact amount remains measure.

Trident.tri
measure quarter = 90deg:angle
measure inRad =
    t.measure.to(quarter, "rad")
# exactly pi/2 — a VALUE here, not an approximation of one

f64 approx = t.math.pi() / 2.0
# 1.5707963267948966, a different number

f64 bad = t.math.sqrt(-1.0)   # faults: no NaN is published in its place
C.c
M_PI / 2.0;        /* double */
sqrt(-1.0);        /* NaN; errno is EDOM if
                      you remembered to check
                      it, and math_errhandling
                      says whether you can */
/* the domain error is reported out of band
   and almost nobody reads it */
C#.cs
Math.PI / 2;      // double
Math.Sqrt(-1);    // double.NaN, silently `decimal` is a real 128-bit decimal type and a genuine
// strength — but it is decimal only: it cannot hold pi/2, and Math.Sqrt has no decimal overload at
// all
C++.cpp
std::numbers::pi_v<double> / 2;
std::sqrt(-1.0);     // NaN, silently
// boost::multiprecision brings exact
// rationals and arbitrary-precision floats; an exact rational multiple of pi needs a computer
// algebra system, which is a different kind of dependency entirely
Cutlass.cut
measure quarter = 90deg:angle
# the cut.exact carrier is a rational times a power of pi, so a quarter turn holds pi/2 exactly; no
# record names the cut member that re-points it to rad yet

f64 approx = 3.141592653589793 / 2.0
# cut.math has no pi: a binary64 value

f64 x = -1.0
f64 n = cut.math.sqrt(x)
# IEEE law: a canonical NaN, a value
f64<finite=true> bad = cut.math.sqrt(x)
# fails with fault.intent: Trident's finite law, declared per binding
Dart.dart
math.pi / 2;      // double
math.sqrt(-1);    // NaN, silently Dart has one double and one int and no exact fractional type in
// the SDK at all — `package:rational` and `package:decimal` are the usual answers, both third party
Elixir.ex
:math.pi() / 2      # 1.5707963267948966
:math.sqrt(-1)      # ArithmeticError — Elixir refuses too
Decimal.new("3.14") # hex package, decimal the BEAM's number tower is integer and float; exactness
# past that is a library, and pi is not in any of them
Go.go
math.Pi / 2        // float64
math.Sqrt(-1)      // NaN, silently
big.NewRat(1, 2)   // exact rationals, in the standard library — and still no pi. The exact tower
// and the transcendental one do not meet anywhere in Go
Haskell.hs
pi / 2 :: Double   -- 1.5707963267948966
sqrt (-1) :: Double -- NaN, silently
1 % 2 :: Rational   -- exact, in base Data.Ratio is exact and native, and `pi` is a Floating class
-- method, so the exact type and the transcendental class have no member in common
Jai.jai
PI_HALF :: 3.14159265358979 / 2;
sqrt(-1.0);              // NaN float64 throughout and no exact type of any kind: no rational, no
// decimal, no symbolic constant. What
// #run buys is a constant computed
// while the compiler runs — in the same binary64 it would have used
Java.java
Math.PI / 2;         // double
Math.sqrt(-1);       // NaN, silently
new BigDecimal("3.14159...");  // you supply the digits; BigDecimal has no pi, no symbolic exponent,
// and no way to say
// "exactly a quarter turn"
JavaScript.js
Math.PI / 2;     // 1.5707963267948966
Math.sqrt(-1);   // NaN — no throw, no flag NaN now flows through every arithmetic it touches. The
// failure surfaces wherever the value is finally printed or compared, which is never where it was
// produced
Julia.jl
pi / 2          # 1.5707963267948966
pi              # π = 3.14159... (Irrational)
sqrt(-1.0)      # DomainError — Julia throws
1//2            # exact Rational, native Julia is the closest thing here: pi is a
# real Irrational{:π} type that keeps its
# identity until a precision is demanded. Ask for arithmetic and it collapses to Float64 immediately
# — pi/2 is already one
Kotlin.kt
Math.PI / 2          // Double
Math.sqrt(-1.0)      // NaN, silently
java.math.BigDecimal("3.14159")
// the JVM's exact types are decimal and integer; a symbolic pi exponent has no home in either, and
// nothing refuses an inexact root
Lua.lua
math.pi / 2         -- 1.5707963267948966
math.sqrt(-1)       -- -nan
-- Lua has one numeric tower, binary64, and no exact type of any kind. There is no rational, no
-- decimal, and no way to say a value is exact
Luau.luau
math.pi / 2     -- 1.5707963267948966
math.sqrt(-1)   -- nan, silently one binary64 lane, exactly as in Lua. No rational, no decimal, no
-- exact anything, and no way for a program to say that a value is exact
Nim.nim
import std/math
PI / 2            # 1.5707963267948966
sqrt(-1.0)        # nan, silently std/rationals gives exact fractions and std/math gives PI, and no
# type in the standard library holds both at once
Objective-C.m
M_PI / 2.0;              /* double */
sqrt(-1.0);              /* NaN */
[NSDecimalNumber decimalNumberWithString:
    @"3.14159265358979"];
/* NSDecimalNumber is 38 digits of decimal
   and raises on overflow, which is better
   than most — it still has no pi */
Perl.pl
use POSIX;
4 * atan2(1, 1) / 2;   # no pi constant in core at all
sqrt(-1);              # dies: "Can't take sqrt of -1"
# Math::BigRat is core and exact; pi is a
# transcendental it cannot hold
PHP.php
M_PI / 2;        // float
sqrt(-1);        // NAN, silently
bcsqrt('2', 50); // bcmath: 50 decimal places of a root, rounded, with no way to ask whether the
// root was exact. The approximation is the only answer offered
Python.py
import math, sympy
math.pi / 2      # 1.5707963267948966
math.sqrt(-1)    # ValueError — Python does refuse this one
sympy.pi / 2     # pi/2, exact — but only inside SymPy's own expression tree, a whole second numeric
# tower you opt into, with its own types, its own printing and a large performance cliff at the
# boundary
Ruby.rb
Math::PI / 2      # 1.5707963267948966
Math.sqrt(-1)     # Math::DomainError —
                  # Ruby refuses, not NaN
Rational(1, 2)    # exact, built in two of the three pieces are here and native; the piece that
# joins them, an exact multiple of pi, is not
Rust.rs
std::f64::consts::PI / 2.0;
(-1.0f64).sqrt();     // NaN, silently `num-rational` gives exact fractions and `rug` binds
// GMP/MPFR, but nothing in std or in the common crates carries a rational multiple of pi as one
// exact value
Scala.scala
// spire
math.Pi / 2          // Double
math.sqrt(-1)        // NaN, silently
spire.math.Algebraic(2).sqrt
// spire has Rational, Real and Algebraic — the richest numeric tower on this list outside a CAS —
// and it is a third-party library, not the standard one
Swift.swift
Double.pi / 2        // 1.5707963267948966
(-1.0).squareRoot()  // nan, silently
Foundation.Decimal   // exact decimal, no pi Swift's Decimal is a 38-digit decimal; like every
// decimal here it cannot hold a rational multiple of pi
TypeScript.ts
Math.PI / 2;        // number
Math.sqrt(-1);      // NaN TypeScript adds no numeric types at all — it types the JavaScript tower
// and stops. `NaN` is typed `number`, so the type system cannot even flag its arrival
Verse.verse
Quarter := 1 / 4        # rational, exact
Half := Quarter + 1 / 4 # 1/2, exact

# int / int is FAILABLE and its result is an exact rational, so Verse has a real exact tower sitting
# under the division operator. What it has no answer for is a unit, an angle, or a pi that is not
# binary64
Zig.zig
std.math.pi / 2.0;
@sqrt(@as(f64, -1.0));   // nan Zig's std.math has no exact tower: no rational, no decimal, no
// symbolic constant. What it has is comptime float evaluation, which is still binary64
34

Exact fractions with no second spelling

A rational stores its fraction in lowest terms. Arithmetic and order use exact integers. Equal fractions have one canonical payload.

2//4 is a literal; 2 // 4 is an operator expression. Both produce one half. A known zero denominator refuses before execution; a runtime zero denominator faults.

Trident.tri
rational oneHalf = 1//2
rational third   = 1//3

rational sixth = oneHalf * third   # 1/6
rational sum   = oneHalf + third   # 5/6

bool ordered = third < oneHalf
# one cross product, never a binary64 image

rational same = rational(2//4)   # shared literal grammar reduces it

i32 two = 2
i32 none = 0
rational late = two // none   # faults
rational zero = 2//0          # refuses
rational odd = 1.0 // 2       # refuses:
# // takes integers; float division is /
C.c
typedef struct { long n, d; } Rat;

Rat rat_mul(Rat a, Rat b) {
    long n = a.n * b.n, d = a.d * b.d;
    long g = gcd(n < 0 ? -n : n, d);
    Rat r = { n / g, d / g };
    return r;   /* and the overflow in that
                   first multiply is yours */
}
C#.cs
// a NuGet package; the BCL has none
var half = new Rational(1, 2);
var third = new Rational(1, 3);
half * third;    // 1/6 — C# does let the
                 // library define operator*
// System.Numerics has BigInteger and Complex, and stops short of Rational; `decimal` cannot hold
// 1/3
C++.cpp
// boost::rational
boost::rational<long> half(1, 2), third(1, 3);
half * third;      // 1/6
half + third;      // 5/6
// operators, comparison and stream output, all reduced on construction. Not in the standard
// library, and the overflow policy is the underlying integer type's
Cutlass.cut
rational oneHalf = 1//2
rational third   = 1//3

rational sixth = oneHalf * third  # 1/6
rational sum   = oneHalf + third  # 5/6

bool ordered = third < oneHalf
# one cross product, never a binary64 image

rational same = rational(2//4)
# every spelling reduces to one payload

i32 two = 2
i32 none = 0
rational late = two // none
# fails with fault.divide
rational zero = 2//0     # refuses
rational odd = 1.0 // 2  # refuses:
# // takes integers; float division is /
Dart.dart
// package:rational
final half = Rational(BigInt.one, BigInt.two);
final third = Rational.parse('1/3');
half * third;    // 1/6 — Dart allows
half + third;    // 5/6   operator overloads
// built on BigInt so it does not overflow. Third party; the SDK has int and double and nothing
// between them
Elixir.ex
# the Ratio hex package
half = Ratio.new(1, 2)
third = Ratio.new(1, 3)
Ratio.mult(half, third)   # 1 <|> 6
# Elixir's own tower is integer and float only. Operators can be imported from the package,
# replacing Kernel's, which is a large thing to do for one type
Go.go
half := big.NewRat(1, 2)
third := big.NewRat(1, 3)
new(big.Rat).Mul(half, third)   // 1/6
new(big.Rat).Add(half, third)   // 5/6
// math/big.Rat is standard library, always in lowest terms, and exact. Every operation is a
// three-argument method call into a destination, because Go has no operator overloading
Haskell.hs
import Data.Ratio
half  = 1 % 2 :: Rational
third = 1 % 3 :: Rational
half * third     -- 1 % 6
half + third     -- 5 % 6
2 % 4            -- 1 % 2, reduced by %
-- in base, exact, with the ordinary Num operators via a typeclass instance and Integer underneath,
-- so it never overflows
Jai.jai
Rat :: struct { n: s64; d: s64; }

operator * :: (a: Rat, b: Rat) -> Rat {
    n := a.n * b.n;
    d := a.d * b.d;
    g := gcd(n, d);
    return .{ n / g, d / g };
}
// operator overloading makes it read correctly. Nothing in the modules provides it, and the
// reduction is yours to remember at every constructor
Java.java
// Apache Commons Math
Fraction half = new Fraction(1, 2);
Fraction third = new Fraction(1, 3);
half.multiply(third);   // 1/6
half.add(third);        // 5/6
// nothing in the JDK: BigDecimal is decimal, so 1/3 has no exact BigDecimal at all and
// divide() throws ArithmeticException unless
// you hand it a rounding mode
JavaScript.js
// fraction.js
const half = new Fraction(1, 2);
const third = new Fraction(1, 3);
half.mul(third).toFraction();   // "1/6"
half.add(third).toFraction();   // "5/6"
// method calls only — JavaScript has no operator overloading, so `half * third`
// silently gives NaN rather than an error
Julia.jl
half  = 1//2
third = 1//3
half * third     # 1//6
half + third     # 5//6
2//4             # 1//2, reduced by //
# a native literal, native operators, native reduction, and promotion into the rest of the tower.
# Julia is the one language here whose exact fraction feels like Trident's
Kotlin.kt
data class Rat(val n: Long, val d: Long) {
    operator fun times(o: Rat): Rat {
        val g = gcd(n * o.n, d * o.d)
        return Rat(n * o.n / g, d * o.d / g)
    }
}
// Kotlin's operator functions make this pleasant to write; the stdlib has no rational, so you are
// still writing it
Lua.lua
local function mul(a, b)
  local n, d = a[1] * b[1], a[2] * b[2]
  local g = gcd(n, d)
  return { n // g, d // g }
end
-- every fraction is a two-element table you built, and every operation is a function you wrote. A
-- metatable can restore the operators; the gcd is still yours
Luau.luau
local function mul(a, b)
    local n, d = a[1]*b[1], a[2]*b[2]
    local g = gcd(n, d)
    return {n // g, d // g}
end
-- // is Luau's floor division, not a
-- rational constructor. Every fraction is a table you built and every operation a function you
-- wrote, as in Lua
Nim.nim
import std/rationals
var half = 1 // 2
var third = 1 // 3
echo half * third    # 1/6
echo half + third    # 5/6
# std/rationals is in the standard library, generic over the integer type, with real operators and
# reduction on construction
Objective-C.m
/* NSDecimalNumber is decimal, so one third
   has no exact representation in it */
NSDecimalNumber *third = [NSDecimalNumber
    decimalNumberWithString:@"0.333333"];
// Foundation offers no rational type at all; the honest answer is a struct with two NSIntegers and
// functions you write
Perl.pl
use Math::BigRat;
my $half  = Math::BigRat->new('1/2');
my $third = Math::BigRat->new('1/3');
$half->copy->bmul($third);   # 1/6
$half->copy->badd($third);   # 5/6
# core since 5.8, exact, arbitrary size. Every method mutates in place, so the
# ->copy is load-bearing and forgetting it
# is the classic Math::Big* bug
PHP.php
// bcmath is decimal, not rational
bcdiv('1', '3', 20);   // "0.33333333333..."
// twenty places, truncated, and the twenty was your guess. PHP has no rational type and no package
// that is the obvious answer, so exactness here means picking a scale and hoping it was enough
Python.py
from fractions import Fraction
half  = Fraction(1, 2)
third = Fraction(1, 3)
half * third      # Fraction(1, 6)
half + third      # Fraction(5, 6)
Fraction(2, 4)    # Fraction(1, 2) — reduced
# for you, silently. Exact and native, and the one weak spot is that it will happily accept a float:
# Fraction(0.1) is the huge exact value of the binary64 0.1
Ruby.rb
half  = 1/2r      # Rational(1, 2) — a
third = 1/3r      # literal suffix, built in
half * third      # (1/6)
half + third      # (5/6)
Rational(2, 4)    # (1/2), reduced silently
# exact, native, with real operators and its own literal form. Arithmetic with a Float coerces to
# Float and drops the exactness without saying so
Rust.rs
// num-rational crate
let half  = Ratio::new(1i64, 2);
let third = Ratio::new(1i64, 3);
half * third;    // 1/6
half + third;    // 5/6
// impl Mul/Add/Ord give back the operators,
// and Ratio::new reduces on construction.
// Not in std — the numeric traits live in `num-traits` and everything builds on it
Scala.scala
// spire
import spire.math.Rational
val half = Rational(1, 2)
val third = Rational(1, 3)
half * third       // 1/6
half + third       // 5/6
// Scala's own BigDecimal is decimal; spire supplies Rational, Real and the numeric typeclasses, and
// it is third party
Swift.swift
struct Rat: Equatable {
    let n: Int, d: Int
    static func * (a: Rat, b: Rat) -> Rat {
        let g = gcd(a.n * b.n, a.d * b.d)
        return Rat(n: a.n * b.n / g, d: a.d * b.d / g)
    }
}
// no Rational in the standard library and none in swift-numerics either
TypeScript.ts
// fraction.js, typed
import Fraction from "fraction.js";
const half: Fraction = new Fraction(1, 2);
half.mul(new Fraction(1, 3)).toFraction();
// TypeScript types the wrapper and nothing underneath it: the fraction is two `number` fields, so a
// denominator past
// 2^53 loses precision with no diagnostic
Verse.verse
OneHalf := 1 / 2        # rational
Third := 1 / 3
Sixth := OneHalf * Third
Sum := OneHalf + Third  # 5/6, exact

# rationals reduce to lowest terms for comparison, and int is a SUBTYPE of rational, so an integer
# flows in with no conversion written. There is no rational literal at all: division is the only
# constructor
Zig.zig
const Rat = struct {
    n: i64, d: i64, fn mul(a: Rat, b: Rat) Rat {
        const n = a.n * b.n;
        const d = a.d * b.d;
        const g = std.math.gcd(@abs(n), d);
        return .{ .n = n / g, .d = d / g };
    }
};
// nothing in std; no operator overloading, so .mul() is the only spelling available
35

The currency is in the type

A money type carries its currency and decimal scale. A currency mismatch refuses at compile. A coefficient beyond its width faults.

A currency change uses a typed exchange rate and a named conversion. t.finance.money.allocate splits an amount without losing a minor unit. Multiplication accepts a scalar, not another money value.

Trident.tri
money<USD> price = 12.50
money<USD> total = price + 1.25
# 13.75 USD, exact, no rounding mode

money<EUR> other = 12.50
money<USD> sum = price + other  # refuses
money<USD> moved = other  # refuses

money<JPY> yen = 100      # scale 0
C.c
/* there is no decimal type in C */
long cents = 1250;    /* USD, by comment */
long other = 1250;    /* EUR, by comment */
long sum = cents + other;   /* 2500 of what? */
/* scaled integers and a discipline about
   never mixing them is the whole mechanism,
   and the discipline is not checkable */
C#.cs
decimal price = 12.50m;
decimal other = 12.50m;   // EUR, allegedly
price + other;            // 25.00m
// `decimal` is a native 128-bit exact decimal and one of the best money primitives on this list.
// The currency is a field on a record beside it, checked by an `if` somebody has to remember
C++.cpp
// no decimal in the standard library either
long cents = 1250;       // USD, by comment
// std::chrono proved the committee can ship
// a strongly-typed unit system; nothing equivalent exists for money, so every shop writes
// Money<USD> again, or reaches for Boost.Multiprecision and a convention
Cutlass.cut
money<USD> price = 12.50
money<USD> total = price + 1.25
# 13.75 USD, exact, no rounding mode; a
# sum past the 128-bit coefficient fails with fault.overflow

money<EUR> other = 12.50
money<USD> sum = price + other  # refuses
money<USD> moved = other  # refuses: no conversion is implicit

money<JPY> yen = 100      # scale 0
Dart.dart
// package:decimal
final price = Decimal.parse('12.50');
final other = Decimal.parse('12.50');
price + other;     // 25.00
// exact, with operators, and third party. The Dart SDK's own numeric types are int and double, so
// unadorned Dart money is binary floating point
Elixir.ex
# the Decimal hex package
price = Decimal.new("12.50")
other = Decimal.new("12.50")
Decimal.add(price, other)     # 25.00
# arbitrary-precision decimal, with a process-level rounding context. The Money package layers a
# currency on top and
# returns {:error, _} for a mismatch
Go.go
// shopspring/decimal
price := decimal.NewFromString("12.50")
other := decimal.NewFromString("12.50")
price.Add(other)
// Go's standard library has no decimal type at all: math/big.Float is binary, big.Rat is exact but
// unscaled, and float64 money is the bug this whole section is about
Haskell.hs
-- the Decimal package (Data.Decimal)
price = Decimal 2 1250
other = Decimal 2 1250
price + other      -- 25.00
-- base has Data.Fixed, whose scale is a type-level parameter (Fixed E2), which is genuinely close —
-- and it carries no currency and no overflow check
Jai.jai
cents : s64 = 1250;   // USD, by comment no decimal type in the modules. Jai does have polymorphic
// structs and operator overloading, so Money(USD)
// with a real compile error on a
// currency mismatch is expressible — it is simply all your code
Java.java
BigDecimal price = new BigDecimal("12.50");
Currency usd = Currency.getInstance("USD");
// BigDecimal and Currency are both in the JDK and know nothing about each other. JSR-354
// (javax.money, Moneta) joins them into MonetaryAmount — and a mismatch is a MonetaryException at
// runtime, not javac
JavaScript.js
// dinero.js
const price = dinero({ amount: 1250, currency: USD });
const other = dinero({ amount: 1250, currency: EUR });
add(price, other);
// throws at runtime. JavaScript has no decimal type at all (it is a stage-3 proposal), so money in
// plain JS is cents in a Number and a string beside it
Julia.jl
# Decimals.jl / FixedPointDecimals.jl
price = Decimal(12.50)
other = Decimal(12.50)
price + other      # 25.00
# Base has Rational and BigFloat and no decimal. FixedPointDecimals puts the scale
# in the type (FixedDecimal{Int64,2}), which
# is the closest anyone here gets — the currency still is not in it
Kotlin.kt
val price = BigDecimal("12.50")
val other = BigDecimal("12.50")   // EUR?
price + other                     // 25.00
// Kotlin gives BigDecimal real operators through operator conventions, which is nicer than Java's
// .add() — and the currency is still a separate field
Lua.lua
local price = 12.50   -- a binary64
local other = 12.50
print(price + other)  -- 25.0
-- 0.1 + 0.2 ~= 0.3 here as everywhere, and
-- Lua has no decimal, no exact type, and no integer wide enough to be confident in cents past 2^53
-- before 5.3
Luau.luau
local price = 12.50   -- a binary64
local other = 12.50
print(price + other)  -- 25
-- one binary64 and no decimal type, so cents are a number you scaled by hand. Roblox's own currency
-- APIs are whole Robux for exactly that reason
Nim.nim
# std/rationals is exact but unscaled, and there is no decimal in the standard library
let cents: int64 = 1250   # USD, by comment the nimble ecosystem has decimal packages; none is the
# obvious answer, so scaled integers plus a convention is what most Nim money code actually is
Objective-C.m
NSDecimalNumber *price = [NSDecimalNumber
    decimalNumberWithString:@"12.50"];
[price decimalNumberByAdding:other];
// exact decimal, raises on overflow rather than saturating, which is better than most here.
// Currency is an NSString you carry alongside and compare by hand
Perl.pl
use Math::BigFloat;
my $price = Math::BigFloat->new('12.50');
$price->badd($other);      # 25.00
# core, exact, arbitrary precision — and
# mutating in place like every Math::Big*,
# so $price is now the sum. The currency is
# a hash key beside it
PHP.php
$price = '12.50';
bcadd($price, $other, 2);   // "25.00"
// bcmath is a bundled extension and exact, operating on strings with an explicit scale on every
// call. Forget the scale argument and it silently defaults to 0, truncating your cents away
Python.py
from decimal import Decimal
price = Decimal("12.50")
other = Decimal("12.50")   # EUR, allegedly
price + other              # Decimal('25.00')
# exact decimal, in the standard library, and entirely blind to currency. The code and the amount
# live in two variables and nothing in the language relates them
Ruby.rb
require "bigdecimal"
price = BigDecimal("12.50")
other = BigDecimal("12.50")   # EUR?
price + other                 # 0.25e2
# BigDecimal is in the standard library with real operators. The Money gem adds a
# currency and raises Money::Currency::
# UnknownCurrency at runtime on a mismatch
Rust.rs
// rusty-money / rust_decimal
let price = Money::from_str("12.50", USD)?;
let other = Money::from_str("12.50", EUR)?;
price + other;
// panics, or returns an Err, depending which crate. The currency is a value parameter, not a type
// parameter, so the compiler cannot see the mismatch
Scala.scala
val price = BigDecimal("12.50")
val other = BigDecimal("12.50")   // EUR?
price + other                     // 25.00
// scala.math.BigDecimal wraps Java's with real operators and a MathContext. Squants has a Money
// type whose currency is a runtime value, so a mismatch throws
Swift.swift
let price = Decimal(string: "12.50")!
let other = Decimal(string: "12.50")!
price + other      // 25.00
// Foundation's Decimal is exact, 38 digits, with real operators. No currency in the type; Locale
// and NumberFormatter format a currency, they do not typecheck one
TypeScript.ts
// dinero.js with a branded currency
type USD = Dinero<number> & { c: "USD" };
const price = usd(1250);
const other = eur(1250);
// add(price, other)  <- a brand CAN make
// this a compile error, and it is the one
// place TypeScript gets close. It is a convention over a library over a Number, and all of it
// erases at runtime
Verse.verse
Price := 1250 / 100     # rational, exact
Total := Price + 125 / 100

# the EXACTNESS is free here: rational arithmetic never loses a cent and never needs a rounding
# mode. What is missing is the currency — nothing in the type says USD, so a euro amount and a
# dollar amount add in silence
Zig.zig
const cents: i64 = 1250;   // USD, by comment Zig's std has no decimal type. What it does have is
// comptime, so `Money(.USD)` as a comptime-generated struct type IS expressible and would give
// a real compile error — you just have to
// write the whole thing yourself

Exact decimal arithmetic also derives its result type

Trident.tri
decimal<scale=2,family=length> x = 1.25
decimal<scale=2,family=length> y = 2.50
decimal<scale=4,unit=m2> area = x * y
decimal<scale=2,unit=1> ratio = x / y
# exact 3.1250 m2 and 0.50 no implicit binary-float conversion
36

Unbounded integers under a memory limit

varint holds an exact integer of any size that fits the runtime's memory limit. Negation has no signed minimum. Division truncates toward zero; the remainder follows the dividend's sign.

The runtime admits operand sizes and reserves result memory before publication. It counts no limb products and enforces no work price. CPU quotas belong to the operating system; cancellation and deadlines use coarse polls.

Trident.tri
varint x = 12345678901234567
varint y = 9876543210987654321

varint p = x * y     # exact
varint q = x / y     # truncates toward zero
varint r = x % y     # sign follows the dividend
# x == (x / y) * y + x % y, at every pairing

varint n = -x        # total: no signed minimum to overflow on
C.c
/* nothing in the standard library */
#include <gmp.h>
mpz_t a, b, p;
mpz_init(a); mpz_init(b); mpz_init(p);
mpz_mul(p, a, b);
mpz_clear(a); mpz_clear(b); mpz_clear(p);
/* an LGPL dependency, manual init/clear on
   every temporary, and no operators */
C#.cs
BigInteger a = BigInteger.Parse("1234...");
a * b;            // operators, via overloads
a / b;            // truncates toward zero
a % b;            // follows the dividend System.Numerics.BigInteger is in the BCL, exact,
// unbounded, and has real operators. Also unbounded in time and memory: a hostile exponent is a
// hang
C++.cpp
// nothing in std; boost::multiprecision
using bigint = boost::multiprecision::cpp_int;
bigint a("1234..."), b("9876...");
a * b;            // operators restored by expression templates the standard library still has no
// arbitrary-precision integer in 2026, and the proposals for one keep not landing
Cutlass.cut
varint x = 12345678901234567
varint y = 9876543210987654321

varint p = x * y   # exact
varint q = x / y   # truncates toward 0
varint r = x % y   # sign follows the dividend
varint n = -x      # total: no signed minimum to overflow no work charge: the limits law refuses a
# work price. p is charged to its region, and a region past its bytes fails with fault.memory
Dart.dart
final a = BigInt.parse('12345678901234567');
a * b;            // exact, with operators
a ~/ b;           // truncates toward zero
a % b;            // always non-negative
a.remainder(b);   // follows the dividend BigInt is in dart:core, with operators and both remainders
// named apart. On the web int is a double, so BigInt is not optional
Elixir.ex
a = 12_345_678_901_234_567
b = 9_876_543_210_987_654_321
a * b       # exact; BEAM integers are arbitrary precision natively
div(a, b)   # truncates toward zero
rem(a, b)   # follows the dividend no bignum library, no conversion, no boxing decision to make — it
# is just how integers work on the BEAM
Go.go
a, _ := new(big.Int).SetString("1234...", 10)
new(big.Int).Mul(a, b)
new(big.Int).Quo(a, b)   // truncated
new(big.Int).Rem(a, b)   // follows dividend
new(big.Int).Div(a, b)   // Euclidean math/big is standard, exact and explicit about which division
// you meant, which is more than most manage
Haskell.hs
a = 12345678901234567 :: Integer
a * b             -- exact, unbounded
div a b           -- floor
quot a b          -- truncate
mod a b; rem a b  -- the matching remainders Integer is the DEFAULT integer type in Haskell: you opt
-- in to fixed width with Int, not out of it. Four division functions, each named for its rule
Jai.jai
a : s64 = 12345678901234567;
b : s64 = 9876543210987654321;
// the second literal does not fit s64 and the compiler says so where it is written. s8..s64 and
// u8..u64 are the whole ladder — no arbitrary fixed width like Zig's i1024, and no bignum in the
// modules
Java.java
BigInteger a = new BigInteger("1234...");
a.multiply(b);
a.divide(b);      // truncates toward zero
a.mod(b);         // always non-negative —
a.remainder(b);   // follows the dividend two different remainders, with two different sign rules,
// and picking the wrong one is a classic JDK bug. Exact and unbounded; no cost ceiling anywhere
JavaScript.js
const a = 12345678901234567n;
const b = 9876543210987654321n;
a * b;      // exact, BigInt
a / b;      // truncates toward zero
a % b;      // sign follows the dividend BigInt is native, exact and unbounded.
// Mixing it with a Number throws TypeError,
// which is the right call and still a runtime one
Julia.jl
a = big"12345678901234567"
b = big"9876543210987654321"
a * b      # exact, with a string macro for the literal
div(a, b)  # truncates toward zero
rem(a, b)  # follows the dividend BigInt is in Base, GMP-backed, with real operators and promotion.
# A plain Int64 literal overflows silently, which is why the big"" macro exists
Kotlin.kt
val a = BigInteger("1234...")
a * b             // operator conventions
a / b             // truncates toward zero
a % b             // follows the dividend
a.mod(b)          // non-negative java.math.BigInteger with Kotlin's operators bolted on, which is
// the nicest spelling of it on the JVM
Lua.lua
local a = 12345678901234567
local b = 9876543210987654321  -- overflows Lua integers are 64-bit and wrap silently on overflow;
-- before 5.3 every number was a binary64 and lost exactness past 2^53. There is no bignum in the
-- standard library
Luau.luau
local a = 12345678901234567
-- held as a binary64, so it is exact only to 2^53 and this literal is already rounded with nothing
-- said. Luau dropped Lua 5.3's 64-bit integer subtype, so there is no wrap either — and no bignum
-- anywhere
Nim.nim
# nimble: bigints / stint
import bigints
let a = initBigInt("12345678901234567")
a * b
# Nim's own ints are fixed width and overflow raises OverflowDefect in debug — and the check is off
# under -d:release, so the same expression wraps in production
Objective-C.m
/* NSDecimalNumber tops out at 38 digits and
   is decimal, not integer */
NSDecimalNumber *a = [NSDecimalNumber
    decimalNumberWithString:@"1234..."];
/* past 38 digits the honest answer in
   Objective-C is to link GMP or drop to
   C and write limb arithmetic yourself */
Perl.pl
use Math::BigInt;
my $a = Math::BigInt->new('12345678901234567');
$a->copy->bmul($b);
# core since 5.8, exact, arbitrary size, and mutating in place — the ->copy is the difference
# between multiplying and destroying your operand
PHP.php
$a = gmp_init("12345678901234567");
$p = gmp_mul($a, $b);
gmp_strval($p);
// GMP is a bundled extension, and bcmath is another. PHP's own int is 64-bit and silently becomes a
// FLOAT on overflow, which is the worst failure mode here:
// no error, and the low digits are gone
Python.py
a = 12345678901234567
b = 9876543210987654321
a * b       # exact, automatically
-a          # total
a // b, a % b
# floor division and a remainder that follows the DIVISOR's sign — the opposite convention to C, and
# a standing source of ported-code bugs. 2 ** 10_000_000 will also just try, for as long as it takes
Ruby.rb
a = 12345678901234567
b = 9876543210987654321
a * b       # exact; Integer is unbounded
-a          # total
a / b       # FLOOR division for Integers
a.divmod(b) # and a remainder that follows the divisor, like Python native, exact and seamless —
# Fixnum and Bignum were unified away years ago
Rust.rs
// num-bigint crate
let a = BigInt::from_str("1234...")?;
&a * &b;          // operators via impl Mul
&a / &b;          // truncates toward zero
&a % &b;          // follows the dividend not in std. Rust's own integers are fixed width and panic
// on overflow in debug and wrap in release, which is its own trap
Scala.scala
val a = BigInt("12345678901234567")
a * b             // operators
a / b             // truncates toward zero
a % b             // follows the dividend scala.math.BigInt wraps java.math's with real operators
// and implicit conversion from Int, so mixed expressions just work. Unbounded in time as well as in
// size
Swift.swift
// attaswift/BigInt, or similar
let a = BigInt("1234...")!
a * b
// there is no arbitrary-precision integer in the Swift standard library, and none in swift-numerics
// either. For a language this careful about numeric conversion, it is a conspicuous gap
TypeScript.ts
const a: bigint = 12345678901234567n;
const b: bigint = 9876543210987654321n;
a * b;            // exact TypeScript types BigInt properly and refuses `a * 2` where 2 is a number,
// which is a real compile-time catch — the only one in this row. Everything else is the JavaScript
// runtime's behavior
Verse.verse
A := 12345678901234567
B := 9876543210987654321
P := A * B
# exact. int is arbitrary precision at runtime, so there is no maximum, no wrap and no overflow
# fault to define.

# the LITERAL still has to fit 64 bits to be written; a wider value is one arithmetic builds
Zig.zig
const a: i1024 = 12345678901234567;
var big = try std.math.big.Int.init(alloc);
try big.mul(&a_big, &b_big);
// Zig has two answers and both are good: arbitrary FIXED widths in the language
// (i1024 is an ordinary type), and
// std.math.big.Int in the standard library, allocator-explicit like everything else
37

Expansion carriers, and the width that never promotes

dd128, td192, and qd256 hold two, three, and four binary64 lanes that sum to one value. Arithmetic uses the wider carrier.

Equality and order read the complete values. Exact conversions fault when a carrier cannot hold the value. Named rounding and leading-lane projections state the loss. These kinds publish neither infinity nor NaN.

Trident.tri
dd128 x = (1.0, 0.0)       # two binary64
td192 y = (1.0, 0.0, 0.0)  # three lanes, one value

td192 wide = x + y   # works at the WIDER operand's kind; the narrow one promotes exactly, never
# rounds

bool less = x < y    # false: both are 1
bool same = x == y   # true; neither widens
complex128 z = 2.0i
bool cz = x < z      # refuses at compile
dd128 zero = (0.0, 0.0)
dd128 q = x / zero     # fault, not an infinity
C.c
long double a = 1.0L / 3.0L;
/* 80-bit extended on x86, and exactly
   64-bit — identical to double — on MSVC,
   ARM64 and most others. The same source
   silently has two different precisions on
   two targets, which is the portability
   trap this whole family is full of */
C#.cs
double a = 1.0 / 3.0;      // 53 bits
decimal d = 1m / 3m;       // 96-bit decimal C# has no extended binary float. `decimal` is wider in
// digits and narrower in range, and Math.Sqrt has no overload for it, so the wide type and the math
// library do not meet
C++.cpp
long double a = 1.0L / 3.0L;
__float128 q = 1.0Q / 3.0Q;   // GCC/Clang extension
// std::float128_t landed in C++23 as
// optional, so a portable program still
// cannot assume it. boost::multiprecision
// wraps all of them behind one interface
Cutlass.cut
dd128 x = (1.0, 0.0)       # two binary64
td192 y = (1.0, 0.0, 0.0)  # three lanes, one value

td192 wide = x + y   # at the WIDER operand's kind; the narrow one promotes exactly, never rounds

bool less = x < y    # false: both are 1
bool same = x == y   # true; no widening
complex128 z = 2.0i
bool cz = x < z      # refuses at compile
dd128 zero = (0.0, 0.0)
dd128 q = x / zero   # fails with fault.divide a cut family under the MerTekTypes law: unlike a core
# f64, no path reaches an infinity or a NaN
Dart.dart
final a = 1 / 3;   // double, binary64 Dart has one double, and on the web it is the JavaScript
// number. There is no extended float and no arbitrary-precision float in the SDK at all — only
// BigInt
Elixir.ex
a = 1 / 3      # float, binary64
Decimal.div(Decimal.new(1), Decimal.new(3))
# the BEAM has one float type, IEEE binary64, and no wider one. The Decimal package is decimal with
# a process-dictionary context, so precision is ambient state again
Go.go
a := new(big.Float).SetPrec(113)
a.Quo(big.NewFloat(1), big.NewFloat(3))
// math/big.Float is arbitrary-precision BINARY floating point in the standard library, which is
// rarer than it sounds. The precision is a field you set at runtime, not part of the type, so
// nothing stops two precisions meeting in one Add
Haskell.hs
-- the long-double or rounded packages
a = 1 / 3 :: Double
-- base has Float and Double and CDouble.
-- Numeric.MPFR (hmpfr) and long-double bind
-- C libraries; Rational gives exactness instead of width, which solves a different problem
Jai.jai
a : float32 = 1.0 / 3;
b : float64 = 1.0 / 3;
// float32 and float64 are the ladder. No float16, no float80, no float128 and no double-double.
// `float` names float32, which is the one place on this page where the unqualified spelling is the
// narrow one
Java.java
double a = 1.0 / 3.0;      // 53 bits
new BigDecimal(1).divide(new BigDecimal(3), MathContext.DECIMAL128);
// the JVM has float and double and nothing wider. BigDecimal is decimal, its precision is a
// MathContext argument, and
// divide() throws without one
JavaScript.js
const a = 1 / 3;   // binary64, and that is the entire numeric tower JavaScript has one float width.
// decimal.js and big.js give arbitrary DECIMAL precision, which is a different thing from a wider
// binary float, and neither is in the language
Julia.jl
a = BigFloat(1) / 3        # MPFR, in Base
setprecision(BigFloat, 113)
# arbitrary-precision binary floating point in the standard library, backed by MPFR, with real
# operators — and the precision is a global-ish setting read at construction, so two BigFloats of
# different precision meeting in one `+` is ordinary
Kotlin.kt
val a = 1.0 / 3.0            // Double
val d = BigDecimal.ONE
    .divide(BigDecimal(3), MathContext(34))
// same JVM tower as Java, same decimal MathContext, same absence of anything wider than a Double in
// binary
Lua.lua
local a = 1 / 3   -- binary64 Lua has exactly one float type and no arbitrary precision anywhere in
-- the standard library. lmathx and lbc are C extensions you compile in yourself
Luau.luau
local a = 1 / 3      -- binary64 one float type and nothing wider. Luau's native `vector` is three
-- float32 lanes, and that is the only other float representation in the language — it is narrower,
-- not wider
Nim.nim
let a = 1.0 / 3.0     # float64 Nim has float32 and float64 and stops. The compiler will accept
# `float128` as a name on a C backend where the C compiler has it, which is a backend leak rather
# than a type
Objective-C.m
long double a = 1.0L / 3.0L;
/* inherited straight from C, with the same
   target-dependent width. On Apple Silicon
   long double is double, so code written
   against the Intel behavior loses
   eleven bits of precision on a rebuild */
Perl.pl
use Math::BigFloat;
Math::BigFloat->div_scale(60);
Math::BigFloat->new(1)->bdiv(3);
# arbitrary DECIMAL precision, core, with a CLASS-LEVEL scale setting — global mutable state that
# any module you load can change under you. Perl can be built with a long double NV, which changes
# every float
PHP.php
$a = 1 / 3;   // one float, binary64
bcdiv('1', '3', 60);   // 60 DECIMAL places,
                       // as a string
// no extended binary float, no float128, and bcmath's answer is a decimal string you cannot do
// transcendental math on
Python.py
# mpmath
from mpmath import mp, mpf
mp.prec = 113
a = mpf(1) / 3
# arbitrary binary precision — set on a GLOBAL context object, so `a` carries no precision in its
# type, two values of different precision compare by silently promoting one, and changing mp.prec
# changes the meaning of code elsewhere
Ruby.rb
a = 1.0 / 3.0            # Float, binary64
require "bigdecimal"
BigDecimal(1) / BigDecimal(3, 40)
# BigDecimal is decimal and its precision is an argument at the call site. Ruby has one binary float
# width and no expansion type
Rust.rs
// twofloat, or rug (GMP/MPFR)
let a = TwoFloat::from(1.0) / 3.0;
// double-double as a crate. Rust's own float types are f32 and f64; f128 has been nightly-only for
// years, so anything wider is a dependency and, with rug, an LGPL C library underneath it
Scala.scala
val a = 1.0 / 3.0        // Double
BigDecimal(1) / BigDecimal(3)
// scala.math.BigDecimal defaults to MathContext.DECIMAL128 and will happily throw
// ArithmeticException on a non-terminating division you did not give a context for
Swift.swift
let a = Float80(1) / Float80(3)
// Float80 is a real Swift type on x86_64 — and simply does not exist on Apple Silicon, so `#if
// arch(x86_64)` guards every use of it. Float16, Float32 and Float64 are everywhere; the wide one
// is the one that moved out
TypeScript.ts
const a: number = 1 / 3;
// TypeScript adds no numeric width to JavaScript and cannot: `number` is the only float there is,
// and a branded wider type would have nothing underneath it
Verse.verse
X : float = 1.0 / 3.0

# one float, IEEE binary64, and nothing wider. The exactness Verse does offer comes from the other
# direction — an unbounded int and an exact rational — so a program that needs more reaches for a
# fraction, never for more bits
Zig.zig
const a: f80 = 1.0 / 3.0;
const b: f128 = 1.0 / 3.0;
// f16, f32, f64, f80 and f128 are all ordinary Zig types with ordinary literals, which is the most
// complete native float ladder on this page — f128 is software emulated where the target lacks it,
// and Zig tells you so rather than silently narrowing
38

Durations and timepoints

A timepoint carries its time domain. Different domains require a named map. Two instants in one domain subtract to a duration.

A duration measures elapsed time, not calendar months. Its arithmetic is exact and checked. Clock reads require a lent library call.

Trident.tri
duration step = 1h30m
duration twice = step + step
duration doubled = step * 2
i32 n = 2
duration scaled = step * n  # refuses
timepoint<unix> start = 0
timepoint<unix> later = start + step
duration gap = later - start
bool after = later > start
civil label = t.time.unix.toCivil(later)
# 1970-01-01T01:30:00Z
# timepoint<tai> k = start refuses:
#   a domain change is a named map
# step * n refuses: the factor is an
#   i64, so step * i64(n) is the spelling,
#   and step * 2 reads its bare 2 as one
# 1mo refuses: a month is a calendar
#   span, not a clock span; 1.5h reads
#   exactly, the same span as 90m
C.c
struct timespec m, w;
clock_gettime(CLOCK_MONOTONIC, &m);
clock_gettime(CLOCK_REALTIME, &w);
long s = w.tv_sec - m.tv_sec;
/* compiles: the clock is an argument,
   not a type, and every reading lands in
   the same struct timespec. POSIX, not
   ISO C; CLOCK_TAI is Linux only. A span
   is that same struct, or difftime()'s
   double */
C#.cs
long t0 = Stopwatch.GetTimestamp();
TimeSpan el = Stopwatch.GetElapsedTime(t0);
DateTime u = DateTime.UtcNow;
DateTime l = DateTime.Now;
TimeSpan gap = l - u;   // compiles
// Kind (Utc, Local) is a runtime field
// that subtraction ignores, and the monotonic reading is a bare long. TimeSpan is exact 100 ns
// ticks with no month: AddMonths is a DateTime method. No TAI or GPS scale
C++.cpp
using namespace std::chrono;
auto t0 = steady_clock::now();
auto w  = system_clock::now();
// t0 - w;   does not compile
auto k = clock_cast<tai_clock>(w);
auto step = 1h + 30min;        // exact the clock is time_point's type parameter, and C++20 adds
// utc, tai
// and gps clocks. One gap: months{1} is
// an average-length duration, so
// w + months{1} compiles
Cutlass.cut
duration step = 1h30m
duration twice = step + step
duration doubled = step * 2
i32 n = 2
duration scaled = step * n  # refuses
timepoint<unix> start = 0
timepoint<unix> later = start + step
duration gap = later - start
bool after = later > start
# timepoint<tai> k = start refuses:
#   a domain change is a named map
# step * n refuses: the factor is an
#   i64, so step * i64(n) is the form
# 1mo refuses: a month is a calendar
#   span, not a clock span
# cut reads no clock: cutrun.clock.now()
#   is the one, on the target's counter,
#   and no record names a civil reader
Dart.dart
final sw = Stopwatch()..start();
final a = DateTime.now().toUtc();
final b = a.add(const Duration(hours: 1, minutes: 30));
final el = sw.elapsed;        // Duration Duration is exact microseconds and has no month. DateTime
// is one instant type whose UTC-or-local is a display flag, and the monotonic clock is a Stopwatch
// object, not an instant. No TAI scale
Elixir.ex
t0 = System.monotonic_time(:millisecond)
now = DateTime.utc_now()
later = DateTime.shift(now, minute: 90)
d = Duration.new!(month: 1, hour: 2)
# the monotonic reading is a bare integer, so nothing stops subtracting it from
# System.system_time/1. Duration (1.17) holds months and seconds in one struct, resolved against the
# calendar at shift
Go.go
start := time.Now()   // wall + monotonic
el := time.Since(start)
step := 90 * time.Minute
later := start.Add(step)
next := start.AddDate(0, 1, 0) // calendar one time.Time carries both readings. Sub uses the
// monotonic one when both sides have it, else the wall one, and the type does not say which.
// Duration is int64 ns with no month; no TAI
Haskell.hs
import Data.Time.Clock
import Data.Time.Clock.TAI
now <- getCurrentTime        -- UTCTime
let tai = utcToTAITime lsMap now
-- Maybe AbsoluteTime: Nothing where your leap-second map has no entry. DiffTime
-- (SI seconds) and NominalDiffTime (leap
-- seconds ignored) are two types; months live in CalendarDiffDays. `time` ships with GHC, not base,
-- and GHC.Clock's monotonic reading is a bare Double
Jai.jai
#import "Basic";

t0 := current_time_monotonic();
w  := current_time_consensus();
dt := current_time_monotonic() - t0;
// every reading and every span is one Apollo_Time, a femtosecond count, so t0 - w compiles and
// means nothing. No clock in the type, no calendar period and no time scale
Java.java
long t0 = System.nanoTime();
Instant a = Instant.now();
Instant b = a.plus(Duration.ofMinutes(90));
a.plus(Period.ofMonths(1));
// compiles, then throws at run time: an Instant has no month unit. Duration and Period are separate
// types, a clean split. Instant is one scale
// (UTC, leap seconds smoothed) and the
// monotonic reading is a bare long
JavaScript.js
const t0 = performance.now();   // ms
const a = Temporal.Now.instant();
const b = a.add({ hours: 1, minutes: 30 });
a.add({ months: 1 });   // RangeError Temporal.Instant is exact nanoseconds on one scale. Duration
// holds months and seconds in one type, and Instant refuses the calendar units at run time.
// performance.now() and Date.now() are both plain numbers
Julia.jl
using Dates
t0 = time_ns()        # UInt64, monotonic
a = now(UTC)          # DateTime
b = a + Minute(90)
c = a + Month(1)      # a calendar step Minute is a FixedPeriod and Month an OtherPeriod, distinct
# types, though
# Minute(90) + Month(1) builds a mixed
# CompoundPeriod. DateTime carries no scale and time_ns() is a bare UInt64
Kotlin.kt
val mark = TimeSource.Monotonic.markNow()
val el: Duration = mark.elapsedNow()
val step = 1.hours + 30.minutes
val odd = 1.5.hours       // accepted the monotonic mark is its own type and cannot meet an Instant.
// Duration has no month unit, but takes a Double and rounds it. Calendar periods live in
// kotlinx-datetime (DateTimePeriod). No TAI or GPS scale
Lua.lua
local t0 = os.time()     -- wall seconds
local c0 = os.clock()    -- CPU seconds
local later = t0 + 90 * 60
local gap = os.difftime(later, t0)
-- every reading is a number: nothing tells a wall second from a CPU second, and os.clock measures
-- processor time, not elapsed time. No monotonic clock, no duration type, no time scale
Luau.luau
local t0 = os.clock()
local now = DateTime.now()
local ms = now.UnixTimestampMillis
    + 90 * 60 * 1000
local later =
    DateTime.fromUnixTimestampMillis(ms)
-- DateTime is one UTC instant type; a span is a count of milliseconds added by hand, and
-- os.clock(), Roblox's
-- time() and tick() are plain numbers.
-- No duration type and no time scale
Nim.nim
import std/[times, monotimes]
let t0 = getMonoTime()
let wall = getTime()           # Time
let step = initDuration(minutes = 90)
let next = now() + 1.months    # calendar
let el = getMonoTime() - t0    # Duration MonoTime and Time are separate types, and Duration (exact
# ns) is separate from the calendar TimeInterval that 1.months builds. No TAI or GPS scale
Objective-C.m
NSDate *a = [NSDate date];
NSDate *b = [a dateByAddingTimeInterval:
                 90 * 60];
uint64_t t0 = mach_absolute_time();
NSDateComponents *m =
    [[NSDateComponents alloc] init];
m.month = 1;      // a calendar step
/* NSTimeInterval is a typedef of double,
   so a span is an inexact number and a
   mach tick count converts into one
   silently. NSDate is one scale with no
   clock in its type */
Perl.pl
use Time::Piece;
use Time::Seconds;
use Time::HiRes qw(clock_gettime
                   CLOCK_MONOTONIC);
my $t0 = clock_gettime(CLOCK_MONOTONIC);
my $t = localtime;        # Time::Piece
my $l = $t + ONE_HOUR + 30 * ONE_MINUTE;
my $m = $t + ONE_MONTH;   # an average ONE_MONTH is a fixed count of seconds; add_months is the
# calendar step. The monotonic reading is an untyped float. All three modules are core
PHP.php
$t0 = hrtime(true);            // int ns
$a  = new DateTimeImmutable('now');
$b  = $a->add(new DateInterval('PT1H30M'));
$c  = $a->add(new DateInterval('P1M'));
// DateInterval holds months and seconds in one class, so a calendar step and a clock span are one
// type. hrtime() is a bare integer, and DateTimeImmutable has no clock or scale in its type
Python.py
import time
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
t0 = time.monotonic_ns()        # an int
a = datetime.now(ZoneInfo("Europe/Paris"))
b = a + timedelta(hours=24)
# timedelta is exact and has no months,
# but + applies it to the wall fields, so
# across a DST change b is 23 or 25 real hours after a. No clock in the type
Ruby.rb
t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
a = Time.now
b = a + 90 * 60            # seconds
gap = b - a                # 5400.0, Float
# Time is one wall instant and a span is a Numeric, so a monotonic Float adds to a Time without
# complaint. Months are Date#>>, a separate calendar step. No duration type in core
Rust.rs
use std::time::{Duration, Instant, SystemTime};
let t0 = Instant::now();
let w0 = SystemTime::now();
let el: Duration = t0.elapsed();
let step = Duration::from_secs(5400);
// t0 - w0 does not compile: monotonic and wall are separate types. Duration is
// exact (u64 s, u32 ns) with no month,
// and duration_since returns a Result. No TAI or GPS scale in std
Scala.scala
import scala.concurrent.duration._
val t0 = System.nanoTime()      // a Long
val step = 90.minutes           // exact
val a = java.time.Instant.now()
val b = a.plusSeconds(step.toSeconds)
// two Duration types, Scala's and java.time's; neither has a month, and java.time's Period holds
// calendar steps. Instant is one scale and the monotonic reading is a bare Long
Swift.swift
let clock = ContinuousClock()
let t0 = clock.now
let el = clock.now - t0      // Duration
let step: Duration = .seconds(5400)
let s = SuspendingClock().now
// s - t0 does not compile: each Clock has its own Instant type. Duration is exact and has no month.
// The wall clock is Foundation's Date, outside the Clock family, and there is no TAI scale
TypeScript.ts
type Mono = number & { __mono: true };
const t0 = performance.now() as Mono;
const w = Date.now();
const mixed = t0 - w;    // type-checks
// performance.now() returns
// DOMHighResTimeStamp, an alias of number, so both clocks unify. A brand keeps assignments apart
// and arithmetic strips it. Temporal types Instant and Duration as in JavaScript: one scale
Verse.verse
Start := GetSimulationElapsedTime()
Sleep(90.0 * 60.0)
Gap := GetSimulationElapsedTime() - Start

# a float of seconds since the session began, and Sleep takes float seconds. There is no instant
# type, no duration type, no calendar period and no time scale to carry
Zig.zig
const t0 = try std.time.Instant.now();
const t1 = try std.time.Instant.now();
const ns: u64 = t1.since(t0);
const wall = std.time.timestamp(); // i64 Instant is a typed monotonic reading, kept apart from the
// wall clock's bare integer. A span is a u64 of ns and std.time.ns_per_s and its kin are plain
// constants. No duration type, no calendar, no time scale
39

Measurements with their uncertainty

A measurement holds an estimate, standard uncertainty, and degrees of freedom. Invalid uncertainty or degrees of freedom refuse. A family parameter keeps unrelated measurements separate.

Named uncertainty calls state sensitivities and correlation. Read-only estimate, uncertainty, and degrees-of-freedom groups preserve their derived types.

Trident.tri
measurement rod = (1.5,0.25,9)
# estimate 1.5, standard uncertainty
# 0.25, nine degrees of freedom
measurement[] parts = [rod, rod]
f64[] weights = [1.0, 1.0]
f64[] linked = [1.0, 1.0, 1.0, 1.0]
measurement sum =
    t.math.uncertainty.combine(parts, weights, "correlation", linked, 3.0, 9.0)       # (3,0.5,9)
# one rod counted twice is fully correlated: 0.5, where "independent" would answer 0.354
# rod + rod refuses: a sum states its
# sensitivities and its correlation
# (1.5,-0.25,9) refuses at compile
C.c
typedef struct {
    double value;  /* the estimate */
    double u;      /* std uncertainty */
    int dof;
} Measurement;
Measurement rod = { 1.5, -0.25, 0 };
/* compiles: a struct keeps three loose
   numbers together and refuses none of
   them. No standard or widely used
   library carries an uncertainty */
C#.cs
record Measurement(double Value, double U, int Dof)
{
    public double U { get; } = U >= 0 ? U
        : throw new ArgumentException("U");
}
// a record gives value equality; the check is one you write, and it runs at run time. No BCL type
// or widely used NuGet package carries an uncertainty; UnitsNet types the family and holds a value
// only
C++.cpp
struct Measurement {
    double value, u;
    int dof;
    Measurement(double v, double s, int n)
        : value(v), u(s), dof(n) {
        if (s < 0 || n < 1) throw
            std::invalid_argument("u");
    }
};
// nothing in std. Boost.Units has a
// measurement<Y> only as an example
// file, and it takes abs() of the error
Cutlass.cut
measurement rod = (1.5,0.25,9)
# estimate 1.5, standard uncertainty
# 0.25, nine degrees of freedom
measurement[] parts = [rod, rod]

# rod + rod refuses: the kind has no
# arithmetic operator
# (1.5,-0.25,9) refuses at compile
# no cut member combines the parts: cut.math stops at the IEEE instructions, and the rest belongs to
# a math node above cut that no record designs yet
Dart.dart
// package:quantity
final rod = Length(m: 1.5, uncert: 0.25 / 1.5);   // RELATIVE
rod.standardUncertainty;   // 0.25 m
rod == Length(m: 1.5);     // true one typed value, combined in quadrature
// through + - * /, and a Length is not a
// Mass at compile. == ignores the
// uncertainty, a negative one is taken as given, and there are no degrees of freedom
Elixir.ex
defmodule Measurement do
  @enforce_keys [:value, :u, :dof]
  defstruct [:value, :u, :dof]
  def new(v, u, n) when u >= 0 and n >= 1,
    do: {:ok, %Measurement{value: v, u: u, dof: n}}
  def new(_, _, _), do: {:error, :invalid}
end
# the guard refuses at run time, but a
# literal %Measurement{u: -1, ...} walks
# past it. No package in wide use
Go.go
type Measurement struct {
    Value, U float64 // estimate, std unc
    DoF      int
}
m := Measurement{1.5, -0.25, 0} // compiles three loose numbers in one struct. A
// constructor returning an error is the
// convention, and a literal walks past it. No package in wide use carries an uncertainty
Haskell.hs
import Numeric.Uncertain  -- uncertain pkg
rod = 1.5 +/- 0.25 :: Uncert Double
rod + rod    -- std dev 0.354: each use is an independent sample
rod == (1.5 +/- 9)   -- True: Eq and Ord read the mean only Num and Floating instances, propagated
-- to second order through + and *.
-- +/- squares a negative u into a valid
-- variance; no degrees of freedom
Jai.jai
Measurement :: struct {
    value: float64;
    u:     float64;  // standard uncertainty
    dof:   u32;
}
rod := Measurement.{1.5, -0.25, 0};
// accepted: a struct literal checks the field types and nothing else. No module carries an
// uncertainty or propagates it
Java.java
record Measurement(double value, double u, int dof) {
    Measurement {
        if (u < 0 || dof < 1) throw new
            IllegalArgumentException("u");
    }
}
// a compact constructor refuses a bad triple at run time, and every record passes through it. The
// JDK has no such type; JSR 385 quantities carry a unit
JavaScript.js
class Measurement {
  constructor(value, u, dof) {
    if (u < 0 || dof < 1)
      throw new RangeError("bad triple");
    Object.assign(this, { value, u, dof });
  }
}
// rod === same is false (identity), and
// rod + rod is the string "[object
// Object][object Object]". No npm package
// in wide use carries an uncertainty
Julia.jl
using Measurements   # Measurements.jl
rod  = 1.5 ± 0.25
same = 1.5 ± 0.25
rod == same  # true: value and error
rod - rod    # 0.0 ± 0.0: correlation kept
rod + same   # 3.0 ± 0.35
measurement(1.5, -0.25)   # accepted linear propagation through Base math, with no call; Unitful
# supplies the family. No degrees of freedom
Kotlin.kt
data class Measurement(val value: Double, val u: Double, val dof: Int,) {
    init { require(u >= 0.0 && dof >= 1) }
}
// a data class gives value equality, and init refuses at run time. An operator fun plus could
// propagate, written by hand; no widely used library does it
Lua.lua
local function measurement(v, u, dof)
  assert(u >= 0 and dof >= 1, "bad triple")
  return { value = v, u = u, dof = dof }
end
local rod = measurement(1.5, 0.25, 9)
-- a table: rod.u = -1 later is allowed,
-- and == on two tables compares identity
-- unless a metatable says otherwise. No library in wide use carries this
Luau.luau
type Measurement =
  { value: number, u: number, dof: number }
local rod: Measurement =
  { value = 1.5, u = -0.25, dof = 0 }
-- type-checks: the checker names the shape and the field types, and cannot refuse a negative u or
-- zero dof. Nothing in Luau or the Roblox API carries an uncertainty
Nim.nim
import measuremancer   # nimble package
let rod = 1.5 ± 0.25
let same = 1.5 ± 0.25
echo rod - rod    # 0 ± 0
echo rod == same  # true propagation tracks correlation through derivatives with no call, and with
# unchained a length and a mass are two types. A negative u is stored as given; no degrees of
# freedom
Objective-C.m
NSMeasurement *rod = [[NSMeasurement alloc]
    initWithDoubleValue:1.5
    unit:NSUnitLength.meters];
/* Foundation's measurement is a double
   and a unit, with no uncertainty field.
   The standard uncertainty is a second
   double you keep beside it, and nothing
   refuses a negative one */
Perl.pl
use Number::WithError qw(witherror);
my $rod = witherror(1.5, 0.25);
my $sum = $rod + $rod;   # error 0.354:
                         # no correlation
witherror(1.5, -0.25);   # error 0.25
# CPAN: Gaussian propagation through the overloaded operators, several and asymmetric errors per
# value, with no call. The constructor abs()es each
# error; no degrees of freedom
PHP.php
final readonly class Measurement {
    public function __construct(public float $value, public float $u, public int $dof,) {
        if ($u < 0 || $dof < 1) throw new
            InvalidArgumentException('u');
    }
}
// PHP 8.2: checked once, immutable after. No package in wide use carries this
Python.py
from uncertainties import ufloat
rod  = ufloat(1.5, 0.25)
same = ufloat(1.5, 0.25)
rod - rod     # 0.0+/-0: correlation kept
rod + same    # 3.0, std dev 0.354
rod == same   # False: only a difference of exactly 0+/-0 is equal
ufloat(1.5, -0.25)   # NegativeStdDev linear propagation with no call, and pint wraps it with units.
# No degrees of freedom
Ruby.rb
Measurement = Data.define(:value, :u, :dof) do
  def initialize(value:, u:, dof:)
    raise ArgumentError if u < 0 || dof < 1
    super
  end
end
rod = Measurement.new(1.5, 0.25, 9)
# Data (Ruby 3.2): immutable, value
# equality, and the check runs on every new. No gem in wide use propagates u
Rust.rs
pub struct Measurement {
    value: f64, u: f64, dof: u32, }
impl Measurement {
    pub fn new(value: f64, u: f64, dof: u32)
        -> Option<Self> {
        let ok = u >= 0.0 && dof >= 1;
        ok.then_some(Self { value, u, dof })
    }
}
// private fields: new() is the only way in. No crate in wide use carries uncertainty
Scala.scala
final case class Measurement(value: Double, u: Double, dof: Int) {
  require(u >= 0 && dof >= 1, "bad triple")
}
val rod = Measurement(1.5, 0.25, 9)
// a case class: value equality, and the require runs at construction, at run time. No library in
// wide use carries an uncertainty
Swift.swift
let rod = Measurement(value: 1.5, unit: UnitLength.meters)
let m: Measurement<UnitMass> = rod
// error: the family is checked at compile,
// as in Trident. There is no uncertainty field at all: the standard uncertainty is a second Double
// beside it, and no package in wide use adds one
TypeScript.ts
type Measurement = Readonly<{
  value: number; u: number; dof: number;
}>;
const rod: Measurement =
  { value: 1.5, u: -0.25, dof: 0 };
// compiles: the type checks field types only. A negative u needs a runtime check or a branded type
// you build, and no npm package in wide use carries uncertainty
Verse.verse
measurement := struct:
    Value : float = 0.0
    U : float = 0.0   # standard uncertainty
    Dof : int = 1

Bad := measurement{Value := 1.5, U := -0.25, Dof := 0}
# accepted: a struct checks field types only. A <decides> maker can fail on a bad triple, at run
# time. No uncertainty type, and nothing that propagates one
Zig.zig
const Measurement = struct {
    value: f64, u: f64, dof: u32, };
const rod = Measurement{ .value = 1.5, .u = -0.25, .dof = 0 };
// compiles: a literal checks field types
// only (u32 does refuse a negative dof).
// Nothing in std carries an uncertainty, and there is no operator overloading

Grouped fields carry their derived types

Trident.tri
measurement m = (1.5, 0.25, 9)
f64 estimate = m.estimate
f64 uncertainty = m.standardUncertainty
f64 freedom = m.degreesOfFreedom
assert (estimate == 1.5, "named estimate")
assert (uncertainty == 0.25, "named uncertainty")
40

Complex numbers, signed zero, and no ordering at all

complex128 has real and imaginary binary64 lanes. 4.0i is an imaginary literal. 3.0 + 4.0i is an ordinary addition with required spaces.

Complex ordering refuses. A magnitude order is a named call. Signed zero preserves branch-cut information, so -4.0i and 0.0 - 4.0i are distinct values. Nonfinite arithmetic faults.

Trident.tri
complex128 z = 3.0 + 4.0i
# an ordinary add of an f64 literal and an imaginary one, not a two-lane token

complex128 n = -4.0i       # (-0.0, -4.0)
complex128 S = 0.0 - 4.0i  # (+0.0, -4.0)
# n != s, and that is correct

complex128 w = 1.0 + 1.0i
bool less = z < w  # refuses at compile
bool bigger = t.math.complex.abs(z)
    > t.math.complex.abs(w)
C.c
#include <complex.h>
double complex z = 3.0 + 4.0 * I;
z * z;
cabs(z);
/* built into C99, with operators, and the
   annex-G branch-cut rules are written down
   in the standard. `I` may be an imaginary
   or a complex macro depending on whether
   the implementation has _Imaginary, and
   that changes signed-zero behavior */
C#.cs
var z = new Complex(3, 4);
z * z;              // operators, overloaded
Complex.Abs(z);
// System.Numerics.Complex is in the BCL with real operators and the full transcendental set.
// Comparison operators are not defined, which is the right call — and equality compares the two
// doubles, NaN and all
C++.cpp
#include <complex>
std::complex<double> z(3.0, 4.0);
z * z;
std::abs(z);
// in the standard library since C++98, with operators and transcendentals. The standard says
// behavior is unspecified where the C annex-G rules would apply, so branch cuts are quietly
// implementation business
Cutlass.cut
complex128 z = 3.0 + 4.0i
# an ordinary add of an f64 literal and an imaginary one, not a two-lane token

complex128 n = -4.0i       # (-0.0, -4.0)
complex128 s = 0.0 - 4.0i  # (+0.0, -4.0)
# n != s, and that is correct

complex128 w = 1.0 + 1.0i
bool less = z < w  # refuses at compile no cut member takes the magnitude: cut.math stops at the
# IEEE instructions, so abs(z) < abs(w) waits on a math node above cut
Dart.dart
// dart:math has no Complex.
class Complex {
  final double re, im;
  Complex operator *(Complex o) => Complex(re * o.re - im * o.im, re * o.im + im * o.re);
}
// Dart allows the operator, so the hand-rolled class reads well — you are still the one maintaining
// the branch cuts
Elixir.ex
# the Complex hex package
z = Complex.new(3, 4)
Complex.multiply(z, z)
Complex.abs(z)
# Elixir's number tower is integer and float. Nx carries complex TENSOR types natively, so complex
# arithmetic is available if you are willing to make every scalar a tensor
Go.go
z := complex(3.0, 4.0)      // complex128
w := 4i                     // a native imaginary literal
z * w
real(z); imag(z); cmplx.Abs(z)
// complex64 and complex128 are BUILT-IN types with a built-in literal suffix — the closest any
// language here gets to Trident's spelling. Ordering is a compile
// error in Go too
Haskell.hs
import Data.Complex
z = 3 :+ 4 :: Complex Double
z * z
magnitude z
-- Data.Complex is in base, with the `:+` constructor and Num/Fractional/Floating instances, so
-- every operator and every transcendental comes with it. No Ord instance, deliberately
Jai.jai
Complex :: struct {
    re: float64;
    im: float64;
}
operator * :: (a: Complex, b: Complex)
    -> Complex { ... }
// the Math module has Vector2/3/4, Quaternion and Matrix4 and stops there: no complex type and no
// complex transcendentals. Operator overloading at least makes a hand-rolled one read correctly
Java.java
// Apache Commons Math
Complex z = Complex.valueOf(3, 4);
z.multiply(z);
z.abs();
// nothing in the JDK. Commons' Complex has an explicit NaN and INF singleton and a documented "NaN
// is contagious" rule, so the whole nonfinite question is answered by propagation rather than by
// refusal
JavaScript.js
// JavaScript has no complex type.
const z = { re: 3, im: 4 };
const mul = (a, b) => ({
  re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re, });
// an object literal and four multiplies you wrote, with no operators and nothing that knows a
// branch cut exists
Julia.jl
z = 3 + 4im        # a native literal suffix
z * z              # -7 + 24im
abs(z)             # 5.0
z < w              # MethodError: no method matching isless
# Complex{T} is parametric over the
# component type in Base, so Complex{Int}
# and Complex{Rational} are exact. Ordering
# is undefined rather than wrong
Kotlin.kt
// Apache Commons, or a data class
data class C(val re: Double, val im: Double) {
    operator fun times(o: C) = C(re * o.re - im * o.im, re * o.im + im * o.re)
}
// Kotlin's operator conventions make the hand-rolled version readable. There is no complex type in
// the Kotlin or Java stdlib
Lua.lua
local function cmul(a, b)
  return { a[1]*b[1] - a[2]*b[2], a[1]*b[2] + a[2]*b[1] }
end
-- no complex type in Lua at all, and the standard math library has no complex functions.
-- Two-element tables and functions you wrote is the whole answer
Luau.luau
local function cmul(a, b)
    return {a[1]*b[1] - a[2]*b[2], a[1]*b[2] + a[2]*b[1]}
end
-- no complex type in Luau or anywhere in the Roblox API, and math has no complex functions.
-- Two-element tables and functions you wrote, as in Lua
Nim.nim
import std/complex
let z = complex(3.0, 4.0)
z * z
abs(z)
# std/complex is in the standard library, generic over the float type, with real operators through
# Nim's ordinary operator procs and the full transcendental set
Objective-C.m
#include <complex.h>
double complex z = 3.0 + 4.0 * I;
z * z;
cabs(z);
/* the C99 type, reachable because
   Objective-C is a C superset. Nothing in
   Foundation wraps it, so a complex number
   cannot go in an NSArray without boxing it
   into an NSValue by hand */
Perl.pl
use Math::Complex;
my $z = 3 + 4*i;
$z * $z;
abs($z);
# core since 5.004, and it works by OVERLOADING the ordinary operators globally for any scalar that
# became a
# Math::Complex — so a stray sqrt(-1)
# anywhere in the process now returns one
PHP.php
// PHP has no complex number type.
$z = ['re' => 3, 'im' => 4];
function cmul($a, $b) {
  return ['re' => $a['re']*$b['re']
               - $a['im']*$b['im'], ...];
}
// associative arrays and functions. No bundled extension provides one either
Python.py
z = 3 + 4j          # a native literal
(4j) * (4j)         # (-16+0j)
abs(z)              # 5.0
z < w               # TypeError: '<' not
                    # supported — at runtime
complex(float('inf'), 0) * z
# (nan+infj) — infinities and NaNs flow in
# and out freely, and -0.0 lanes survive or not depending on the operation
Ruby.rb
z = Complex(3, 4)    # or 3 + 4i — a native literal suffix
z * z                # (-7+24i)
z.abs                # 5.0
z < w                # NoMethodError, at runtime Complex is built in, exact over Rationals and
# Integers as well as Floats, which is unusual and genuinely good
Rust.rs
// num-complex crate
let z = Complex::new(3.0, 4.0);
z * z;
z.norm();
// not in std. The crate implements Mul, Div and the transcendentals, deliberately does NOT
// implement Ord, and propagates nonfinite lanes the way the IEEE rules do
Scala.scala
// spire, or breeze
import spire.math.Complex
val z = Complex(3.0, 4.0)
z * z
z.abs
// nothing in the Scala standard library. spire's Complex is generic over its
// numeric typeclasses, so Complex[Rational]
// is exact — which not many of these are
Swift.swift
// swift-numerics
import ComplexModule
let z = Complex(3.0, 4.0)
z * z
z.length
// a first-party Apple package, not the standard library, and notable for taking the opposite
// decision on nonfinites: one single "infinity" and one "nan" value, on the grounds that IEEE's
// many are not distinguishable in a useful way
TypeScript.ts
type Complex = { re: number; im: number };
const mul = (a: Complex, b: Complex) => ({
  re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re, });
// TypeScript can NAME the shape, which is more than JavaScript does, and then the arithmetic is
// still four multiplies you maintain and no operator form at all
Verse.verse
complex := struct:
    Re<public> : float = 0.0
    Im<public> : float = 0.0

# no complex type, and no operator overloading to give one a *. Every product is a named function,
# which has the accidental virtue that nobody can mistake which product they wrote
Zig.zig
const C = std.math.Complex(f64);
const z = C.init(3.0, 4.0);
z.mul(z);
std.math.complex.abs(z);
// std.math.Complex is a generic struct in the standard library with the full transcendental set. No
// operator overloading in Zig, so every operation is a method call, which Zig considers a feature
41

Interval arithmetic with no ambient rounding mode

An interval encloses the result with outward-rounded endpoints. It derives them from exact residuals without changing ambient rounding state. A reversed endpoint pair refuses.

A divisor that contains zero faults. One interval cannot represent the separate half-lines that division could produce.

Trident.tri
interval lo = (0,1)
interval hi = (1,3)

interval S = lo + hi  # (1,4)
interval p = lo * hi  # (0,3), from all
                      # four endpoint products
interval n = -lo      # (-1,-0): the
                      # endpoints swap

interval q = lo / (-1,1)  # fault: a divisor enclosing zero is two half-lines
bool less = lo < hi  # refuses: containment is named
C.c
#include <fenv.h>
typedef struct { double lo, hi; } Iv;

Iv add(Iv a, Iv b) {
    fesetround(FE_DOWNWARD);
    double lo = a.lo + b.lo;
    fesetround(FE_UPWARD);
    double hi = a.hi + b.hi;
    return (Iv){lo, hi};   /* and the mode is
        still UPWARD for whoever runs next */
}
C#.cs
readonly record struct Iv(double Lo, double Hi);
// no interval type in the BCL. .NET exposes no rounding-mode control either, so the sound
// construction is the same BitIncrement/BitDecrement dance on every endpoint of every operation
C++.cpp
// boost::numeric::interval
using namespace boost::numeric;
interval<double> a(0, 1), b(1, 3);
a + b;          // [1, 4]
// the rounding policy is a TEMPLATE PARAMETER, which is the best design here
// — and the default policy still flips the
// hardware mode, so it is unsafe under
// -ffast-math and under any vectorization
// the compiler decides to do
Cutlass.cut
interval lo = (0, 1)
interval hi = (1, 3)

interval s = lo + hi  # (1, 4)
interval p = lo * hi  # (0, 3), from
                      # all four products
interval n = -lo      # (-1, -0)
# a cut kind on its MerTekTypes law: outward ends from an exact residual, and no rounding mode is
# touched

interval q = lo / (-1, 1)  # fails: a divisor enclosing zero is two half-lines, and the caller's
# else arm handles it like any fault
bool less = lo < hi  # refuses: containment is named
Dart.dart
class Iv {
  final double lo, hi;
  Iv operator +(Iv o) =>
      Iv(lo + o.lo, hi + o.hi);
}
// no interval package of note, no rounding mode, and on the web no control over the float lane at
// all. The operator reads nicely and the enclosure is still unsound
Elixir.ex
# no interval arithmetic on the BEAM, and no rounding-mode control: Erlang floats are
# round-to-nearest with no fenv surface.
defmodule Iv do
  defstruct [:lo, :hi]
end
# a sound version needs a NIF, which takes the whole computation off the BEAM
Go.go
type Iv struct{ Lo, Hi float64 }

func Add(a, b Iv) Iv {
    return Iv{a.Lo + b.Lo, a.Hi + b.Hi}
}
// Go has no interval package of note and no access to the FPU rounding mode. That sum is not
// outward-rounded, so it is not an enclosure — it just looks like one
Haskell.hs
-- the intervals package
import Numeric.Interval
a = 0 ... 1 :: Interval Double
b = 1 ... 3 :: Interval Double
a + b          -- 1 ... 4
-- Kmett's package, Num instance and all. It documents that it does NOT do directed rounding, so the
-- enclosure is tight rather than sound — the cost is stated, which is more than most manage
Jai.jai
Iv :: struct {
    lo: float64;
    hi: float64;
}
// nothing in the modules, and no rounding-mode control either. Jai can call libc directly with
// #foreign, so fesetround IS reachable
// — it is a C call, and the soundness
// argument on top of it is yours
Java.java
// no interval type, and — critically — no way to set the rounding mode from Java at all. StrictMath
// is round-to-nearest, full stop.
record Iv(double lo, double hi) {}
// a sound implementation on the JVM has to fake outward rounding with Math.nextDown and Math.nextUp
// on every operation
JavaScript.js
// JavaScript has no interval arithmetic and no access to the rounding mode at all: the language
// specifies round-to-nearest and offers no fesetround.
const a = { lo: 0, hi: 1 };
const add = (x, y) => ({ lo: x.lo + y.lo, hi: x.hi + y.hi });
// and that sum is not outward-rounded, so the enclosure is unsound
Julia.jl
# IntervalArithmetic.jl
using IntervalArithmetic
a = interval(0, 1)
b = interval(1, 3)
a + b            # [1, 4]
a / interval(-1, 1)
# fully IEEE 1788 conformant, directed rounding and all, and the best interval implementation on
# this page by a wide margin — a registered package rather than part of the language
Kotlin.kt
data class Iv(val lo: Double, val hi: Double)
operator fun Iv.plus(o: Iv) =
    Iv(lo + o.lo, hi + o.hi)
// Kotlin gives you the operator and the JVM gives you no rounding mode, so the nextDown/nextUp
// construction is the only sound one here too
Lua.lua
local function add(a, b)
  return { a[1] + b[1], a[2] + b[2] }
end
-- Lua has no interval type, no fenv access, and no nextafter. There is no way to write a sound
-- outward-rounded add in pure Lua at all; it has to be a C extension
Luau.luau
local function add(a, b)
    return {a[1] + b[1], a[2] + b[2]}
end
-- no interval type and no rounding-mode control. Roblox forbids loading native code, so unlike
-- stock Lua there is not even a C extension to fall back on
Nim.nim
type Iv = object
  lo, hi: float64
proc `+`(a, b: Iv): Iv =
  Iv(lo: a.lo + b.lo, hi: a.hi + b.hi)
# nothing in the standard library. Nim can call fesetround through its C backend, which brings back
# every problem the C card above has
Objective-C.m
#include <fenv.h>
typedef struct { double lo, hi; } Iv;
/* the C route, with the same global
   rounding mode, now also shared with every
   Objective-C framework on the thread —
   including Core Graphics and Accelerate,
   neither of which expects it to move */
Perl.pl
# no interval arithmetic in core or on CPAN that is in current use, and no rounding mode from Perl.
my %iv = (lo => 0, hi => 1);
# Math::BigFloat's per-operation rounding
# modes are the only directed rounding available, at decimal and at speed cost
PHP.php
// no interval type and no fenv access.
class Iv { public float $lo, $hi; }
// PHP cannot even reach nextafter without an extension, so an outward-rounded endpoint has to be
// faked by nudging the bits through pack()/unpack()
Python.py
# pyinterval / portion / mpmath.iv
from interval import interval
a = interval[0, 1]
b = interval[1, 3]
a + b        # interval([1.0, 4.0])
a / interval[-1, 1]
# pyinterval returns the two half-lines as a multi-component interval, so downstream code that asked
# for a width gets a set. Rounding control comes from crlibm or the platform's fenv, set
# process-wide
Ruby.rb
# no interval arithmetic in Ruby, and no rounding-mode control in the language.
Iv = Struct.new(:lo, :hi)
def add(a, b) = Iv.new(a.lo + b.lo, a.hi + b.hi)
# BigDecimal has directed rounding modes per operation, which is the one sound-ish path
# — at decimal, and slowly
Rust.rs
// inari crate — IEEE 1788 conformant
let a = const_interval!(0.0, 1.0);
let b = const_interval!(1.0, 3.0);
a + b;          // [1.0, 4.0]
a / const_interval!(-1.0, 1.0);
// returns the entire line, [-inf, inf],
// which is the standard's answer and the one Trident rejects as useless to a caller reading a width
Scala.scala
// spire
import spire.math.Interval
val a = Interval(0.0, 1.0)
val b = Interval(1.0, 3.0)
a + b          // [1.0, 4.0]
a / Interval(-1.0, 1.0)
// spire's Interval handles open and closed bounds and unbounded ends properly, and like everything
// on the JVM it cannot do directed rounding, so it is not an enclosure in the IEEE 1788 sense
Swift.swift
struct Iv { let lo: Double, hi: Double }
// no interval type in the standard library or in swift-numerics. Swift does expose
// .nextDown and .nextUp on FloatingPoint,
// which at least makes a sound implementation writable without touching process state
TypeScript.ts
type Iv = { lo: number; hi: number };
// same as JavaScript: no rounding mode, no nextafter, no sound enclosure available in the language.
// TypeScript types the unsound version very precisely
Verse.verse
iv := struct:
    Lo<public> : float = 0.0
    Hi<public> : float = 0.0

# no interval type, no rounding mode, and no FFI to borrow one through. UEFN exposes no libc at all,
# so a sound outward-rounded add cannot be written in Verse by any route
Zig.zig
const Iv = struct { lo: f64, hi: f64 };
// nothing in std. Zig has @setFloatMode but that selects strict vs optimized, not a rounding
// direction, and there is no fesetround binding in std — so a sound interval in Zig means calling
// into libc
42

Derivatives as a kind, not a framework

A dual holds a value and tangent. Its product follows the derivative product rule. It is a different type from dvec2, even though both occupy two binary64 lanes.

Forward-mode arithmetic and the t.math.dual functions work. At x = 3, 4*x*x has value 36 and tangent 24.

Trident.tri
dual x = (3.0, 1.0)  # variable: tangent 1
dual c = (4.0, 0.0)  # constant: tangent 0
dual y = x * x * c
f64 value = t.math.dual.value(y)
f64 slope = t.math.dual.tangent(y)
assert (value == 36.0, "function value")
assert (slope == 24.0, "derivative")
# dual is not dvec2: no implicit conversion
C.c
typedef struct { double v, d; } Dual;

Dual dmul(Dual a, Dual b) {
    Dual r = { a.v * b.v, a.v * b.d + a.d * b.v };
    return r;
}
/* and dsin, dcos, dexp, dlog, dpow, each
   written out with its own chain-rule step,
   before you can differentiate anything
   interesting */
C#.cs
readonly record struct Dual(double V, double D) {
    public static Dual operator *(Dual a, Dual b)
        => new(a.V * b.V, a.V * b.D + a.D * b.V);
}
// C# operator overloading makes this read correctly. Nothing in the BCL provides it, and Math.Sin
// has no Dual overload, so the transcendentals are yours too
C++.cpp
// Eigen's AutoDiffScalar, or autodiff
#include <autodiff/forward/dual.hpp>
dual x = 3.0;
auto dydx = derivative(f, wrt(x), at(x));
// C++ is where forward-mode AD via operator
// overloading was perfected: templates mean the same f() compiles for double and for
// dual. It is a library, and the error
// messages when it does not fit are famous
Cutlass.cut
dual x = (3.0, 1.0)  # variable: tangent 1
dual c = (4.0, 0.0)  # constant: tangent 0
dual y = x * x * c   # the product rule
dual want = (36.0, 24.0)
assert (y == want, "value and slope")
# a cut kind on its MerTekTypes law, so value 36 and tangent 24 hold
# dvec2 v = y refuses: the same two f64
#   lanes, no conversion either way
# the value and tangent readers and the elementary functions belong to the dual family; their cut
# names are not fixed yet
Dart.dart
class Dual {
  final double v, d;
  Dual operator *(Dual o) =>
      Dual(v * o.v, v * o.d + d * o.v);
}
// Dart's operator overloading makes the class pleasant. No AD package of note; the ML story on Dart
// is calling out to something else
Elixir.ex
# Nx
defn f(x), do: x * x * 4
Nx.Defn.grad(&f/1).(Nx.tensor(3.0))
# Nx brings real AD to the BEAM, inside `defn`, which is a restricted sublanguage compiled to its
# own IR — the framework shape again, and everything in it is a tensor rather than a number
Go.go
type Dual struct{ V, D float64 }

func Mul(a, b Dual) Dual {
    return Dual{a.V * b.V, a.V*b.D + a.D*b.V}
}
// no operator overloading, so every expression in the function you want to differentiate must be
// rewritten as
// Mul(Add(...), ...) by hand
Haskell.hs
-- the ad package
import Numeric.AD
diff (\x -> x * x * 4) 3   -- 24
-- Kmett's `ad` is exceptional: forward, reverse, and mixed modes, and the rank-2 type trick makes
-- perturbation confusion — mixing tangents from two nested
-- derivatives — a TYPE ERROR rather than a
-- silently wrong number
Jai.jai
Dual :: struct {
    v: float64;
    d: float64;
}
operator * :: (a: Dual, b: Dual) -> Dual {
    return .{ a.v*b.v, a.v*b.d + a.d*b.v };
}
// nothing in the modules. The operators read correctly and sin/cos have no Dual overload, so every
// transcendental chain rule is yours
Java.java
// no AD in the JDK and no operator overloading to hang one on, so a Java dual number is:
record Dual(double v, double dv) {
    Dual times(Dual o) {
        return new Dual(v * o.v, v * o.dv + dv * o.v);
    }
}
// every expression becomes a method chain
JavaScript.js
// nothing. The usual answer is a finite difference:
const h = 1e-8;
const d = (f(x + h) - f(x)) / h;
// and h is a guess: too large measures curvature, too small measures rounding, and nothing tells
// you which you got
Julia.jl
# ForwardDiff.jl
using ForwardDiff
ForwardDiff.derivative(x -> x * x * 4, 3.0)
# 24.0 — and it works by defining a Dual
# NUMBER TYPE and letting multiple dispatch run your ordinary, unmodified function on it. That is
# exactly Trident's shape: a kind, not a framework. It is a package, and Julia's promotion rules do
# the rest
Kotlin.kt
data class Dual(val v: Double, val d: Double) {
    operator fun times(o: Dual) = Dual(v * o.v, v * o.d + d * o.v)
}
// reads well, thanks to operator conventions, and is entirely yours. There is no AD library in the
// Kotlin ecosystem that is the obvious choice
Lua.lua
local function dmul(a, b)
  return { a[1] * b[1], a[1] * b[2] + a[2] * b[1] }
end
-- metatables can restore * and + over two-element tables, which is the one thing Lua makes easy
-- here. The transcendental chain rules are all yours
Luau.luau
local Dual = {}
Dual.__mul = function(a, b)
    return setmetatable({
        v = a.v * b.v, d = a.v * b.d + a.d * b.v, }, Dual)
end
-- metatables give * and + back. The chain rules are all yours, and math.sin takes a number, so the
-- transcendentals never compose
Nim.nim
type Dual = object
  v, d: float64
proc `*`(a, b: Dual): Dual =
  Dual(v: a.v * b.v, d: a.v * b.d + a.d * b.v)
# operator procs make this clean, and Nim's generics would let one implementation serve float32 and
# float64 both. Still entirely yours to write
Objective-C.m
typedef struct { double v, d; } Dual;
/* the C construction, reachable from
   Objective-C, with no operators at all
   because Objective-C has none for structs.
   Accelerate has no AD, and neither does
   Foundation */
Perl.pl
# no AD in core or in current CPAN use.
sub dmul {
    my ($a, $b) = @_;
    return [ $a->[0] * $b->[0], $a->[0] * $b->[1]
           + $a->[1] * $b->[0] ];
}
# `overload` can restore the operators for a blessed reference, at the usual Perl cost
PHP.php
// no AD anywhere in PHP.
final class Dual {
    public function __construct(public float $v, public float $d) {}
}
// no operator overloading either, so this is method calls all the way down, in a language where the
// numeric work would not have been fast regardless
Python.py
# JAX (or autograd)
import jax
def f(x): return x * x * 4.0
jax.grad(f)(3.0)     # 24.0
# world-class, and a framework: it traces your function into its own IR, so the derivative is of the
# TRACE, and a Python `if` on a traced value raises rather than branching. Not a number type
Ruby.rb
class Dual
  def *(o) = Dual.new(@v * o.v, @v * o.d + @d * o.v)
end
# Ruby's operator methods make the hand-rolled version idiomatic, and
# coerce() lets it mix with Integer. Nothing
# in the ecosystem provides it
Rust.rs
// num-dual crate
use num_dual::*;
let x = Dual64::from(3.0).derivative();
let y = x * x * 4.0;
y.eps;      // 24.0
// operators via impl Mul, transcendentals via the crate's own traits, and generic over the order so
// second derivatives work too. A crate, not the language
Scala.scala
// spire's Jet
import spire.math.Jet
implicit val d: JetDim = JetDim(1)
val x = 3.0 + Jet.h[Double](0)
(x * x * 4).infinitesimal(0)   // 24.0
// spire ships forward-mode AD as a numeric type, generic over its own typeclasses, so it composes
// with Rational and the rest of the tower. Third party
Swift.swift
import _Differentiation

@differentiable(reverse)
func f(_ x: Double) -> Double { x * x * 4 }
gradient(at: 3.0, of: f)     // 24.0
// Swift is the ONE language here with automatic differentiation in the COMPILER rather than in a
// library — a survivor of Swift for TensorFlow. It lives behind an underscored module and has never
// been formally released, so it is real and it is not finished
TypeScript.ts
type Dual = { v: number; d: number };
// no AD, no operator overloading, and the numeric tower is one float. TensorFlow.js has gradients,
// which is the framework answer again — a graph, not a number
Verse.verse
dual := struct:
    V<public> : float = 0.0
    D<public> : float = 0.0

DualMul(A : dual, B : dual) : dual =
    dual{V := A.V * B.V, D := A.V * B.D + A.D * B.V}

# no operator to overload, so the function being differentiated has to be rewritten into calls by
# hand
Zig.zig
const Dual = struct {
    v: f64, d: f64, fn mul(a: Dual, b: Dual) Dual {
        return .{ .v = a.v * b.v, .d = a.v * b.d + a.d * b.v };
    }
};
// no operator overloading, so the function under differentiation gets rewritten into method calls.
// Nothing in std
43

Domains that refuse the operators they could have had

A quaternion composes with @. A lane-wise quaternion product refuses. A bound uses named union and containment operations; it is not an ordered number.

Subtracting geographic labels does not produce a valid distance. A named geodesic operation answers that question. The newer frame-qualified shape families still have separate source and binding work.

Trident.tri
dquat spin = t.math.dquat.fromAxisAngle((0.0,1.0,0.0), 1.5707963)

dquat both = spin @ spin   # composition
dquat bad = spin * spin    # refuses
dvec3 point = (1.0, 0.0, 0.0)
dvec3 turned = t.math.dquat.rotate(spin, point, 1e-9)

daabb box = (0,0,0, 1,1,1)
# box + box and box < other both refuse
geodetic here = (0,0,0)
# here - there refuses too
C.c
typedef struct { double x, y, z, w; } Quat;

Quat qmul(Quat a, Quat b) { ... }
/* nothing in C, so every project writes the
   four lanes out again. The compiler will
   also happily let you write
   a.x * b.x, a.y * b.y, ... and that is
   exactly how the bug gets written */
C#.cs
var a = Quaternion.Identity;
var both = a * b;     // composition, via an operator overload System.Numerics.Quaternion is in the
// BCL and gets this right: * IS composition. It also defines no < at all, which is correct. There
// is no AABB type, and Vector3 subtraction of two positions is always legal, geographic or not
C++.cpp
// glm
glm::dquat both = spin * spin;   // correct
glm::dvec4 wrong =
    glm::dvec4(spin) * glm::dvec4(other);
// glm gets * right on dquat — and provides the explicit conversion to dvec4 right beside it, where
// * is component-wise. Two operators, two meanings, one keystroke apart
Cutlass.cut
# spin, other and there come from the dquat, daabb and geodetic families, whose cut member names are
# not fixed
dquat both = spin @ spin  # composes
dquat bad = spin * spin   # refuses: four lanes are one rotation no negation: the inverse and the
# conjugate are each named, and a dvec3 rotation is a named member with a unit tolerance, not @

daabb box = (0, 0, 0, 1, 1, 1)
# box + box and box < other refuse
geodetic here = (0, 0, 0)
# here - there refuses: a difference of positions is a geodesic
Dart.dart
// package:vector_math
final both = spin * other;   // Quaternion, composition
final box = Aabb3();
box.hull(other);
// vector_math is the de-facto package
// (Flutter depends on it). Quaternion
// exposes .storage, a Float32List, and
// Vector4 * Vector4 is component-wise
Elixir.ex
# nothing on the BEAM: no quaternion, no bounding box, no vector type.
def qmul({ax, ay, az, aw}, {bx, by, bz, bw}) do
  ...
end
# tuples and functions. Nx can hold the four lanes as a tensor, where * is elementwise, which is the
# wrong product
Go.go
type Quat struct{ X, Y, Z, W float64 }

func Mul(a, b Quat) Quat { ... }
// no quaternion, no AABB, no operators, and
// — the sharp edge — Go structs of
// comparable fields are == comparable by
// default, so two positions compare "equal" by bytes including -0.0 vs +0.0
Haskell.hs
-- the linear package
import Linear.Quaternion
spin * other      -- composition, via the Num instance Kmett's `linear` gets this right and does it
-- the way Haskell does: Quaternion's Num instance defines (*) as the Hamilton product, while V4's
-- defines it component-wise. Two types, two instances
Jai.jai
#import "Math";

q := Quaternion.{0, 0, 0, 1};
both := q * other;      // COMPOSITION
moved := rotate(point, q);

v : Vector3;
scaled := v * 2.0;      // scale Math gets the important one right:
// Quaternion * Quaternion composes.
// There is no AABB type, subtracting one position from another is always legal, and no type refuses
// an ordering — < is simply not defined
Java.java
// JOML
Quaternionf a = new Quaternionf();
a.mul(b);             // composition
AABBf box = new AABBf();
box.union(other);
// nothing in the JDK. JOML names the operations properly — and Quaternionf also exposes .x .y .z .w
// as public fields, so the wrong product stays one keystroke away
JavaScript.js
// three.js
const a = new THREE.Quaternion();
a.multiply(b);        // correct composition
const box = new THREE.Box3();
box.union(other);     // named, good and both are plain objects with public
// .x/.y/.z/.w, so `a.x * b.x` is right
// there, typechecks nowhere, and is what a hurried refactor produces
Julia.jl
# Quaternions.jl
using Quaternions
both = spin * other        # composition multiple dispatch makes this exactly right: * on two
# Quaternions is the Hamilton product, * on two arrays is a matrix product, and .* is elementwise.
# Three operations, three spellings, and Quaternions.jl is a registered package
Kotlin.kt
data class Quat(val x: Double, val y: Double, val z: Double, val w: Double)
operator fun Quat.times(o: Quat): Quat = ...
// nothing in the Kotlin stdlib. The operator convention lets you define * as composition — and
// equally lets someone define it component-wise, which is the whole problem
Lua.lua
local function qmul(a, b) ... end
-- no quaternion, no bounding box, no vector type. Everything is a table, every operation is a
-- function, and a metatable
-- __mul can be given either meaning with
-- nothing to say which one it was
Luau.luau
local both = a * other   -- CFrame: COMPOSITION
local moved = a * point  -- a POINT

local v = Vector3.new(1, 2, 3)
local lanes = v * other  -- LANE BY LANE

-- Roblox is the case Trident names: one
-- * means composition on CFrame and
-- lanes on Vector3, in one expression language. < is undefined on both, and subtracting two world
-- positions is always legal
Nim.nim
type Quat = object
  x, y, z, w: float64
proc `*`(a, b: Quat): Quat = ...
# nothing in the standard library. Nim's operator procs are overloadable per type, so `*` on Quat
# and `*` on Vec4 can mean different things — if whoever wrote them made that choice deliberately
Objective-C.m
simd_quatd both = simd_mul(spin, other);
// the same simd types, in their C spelling. simd_mul is overloaded on the argument type, so
// simd_mul over two simd_double4 is component-wise and over two simd_quatd composes — one name, two
// meanings, chosen by which type you passed
Perl.pl
# Math::Quaternion (CPAN)
my $both = $spin->multiply($other);
# CPAN has it; core does not. Perl's `overload` pragma can attach * to a blessed reference with any
# meaning at all, and nothing in the language records which meaning was chosen
PHP.php
// no quaternion, no bounding box, no vector type, and no operator overloading.
function qmul(array $a, array $b): array {
    ...
}
// arrays of four floats and functions. The geospatial case is the live one in PHP, and lat/lon
// subtraction is exactly how it is usually written
Python.py
# numpy-quaternion / scipy Rotation
import numpy as np
a = np.array([0., 0., 0., 1.])
b = np.array([0., .707, 0., .707])
a * b        # component-wise. Runs. Wrong. the correct call is quaternion.quaternion or
# Rotation.from_quat(...) * ..., and nothing distinguishes the two at the call site except which
# array you happen to have
Ruby.rb
# no quaternion, no bounding box, no vector type anywhere in the Ruby standard library.
class Quat
  def *(o) = ...   # whatever you decide
end
# Matrix and Vector exist in the stdlib; Vector#* is scalar multiplication, and Vector#inner_product
# is the dot — named apart, which is the right instinct
Rust.rs
// glam / nalgebra
let both = spin * spin;      // composition
let hull = box.union(other);
// glam's Quat implements Mul as composition and deliberately does not implement Ord.
// It does implement Index, so q[0] * r[0] is
// available; and Vec3 - Vec3 is a Vec3 whether or not those were lat/lon
Scala.scala
case class Quat(x: Double, y: Double, z: Double, w: Double) {
  def *(o: Quat): Quat = ???
}
// nothing in the Scala standard library. Breeze is linear algebra, where * on DenseVector is
// elementwise and :* and dot are named apart — the right instinct, applied to vectors rather than
// rotations
Swift.swift
import simd
let both = spin * other     // simd_quatd, composition Apple's simd module is part of the platform
// and gets quaternions right: * composes, and simd_quatd defines no ordering. simd_double4 sits
// beside it with a component-wise * and an explicit conversion between the two
TypeScript.ts
// three.js, typed
const a: THREE.Quaternion = new Quaternion();
a.multiply(b);
// TypeScript types the quaternion class and its public x/y/z/w numbers. `a.x * b.x` is a perfectly
// well-typed `number`, so the type system has nothing to say about the one mistake this section is
// about
Verse.verse
using { /UnrealEngine.com/Temporary/SpatialMath }

R : rotation = IdentityRotation()
Both := R.RotateBy(Other)  # composition

V : vector3 = vector3{
    X := 1.0, Y := 0.0, Z := 0.0}
Scaled := V * 2.0          # scalar only

# composition is a NAMED method, never an operator, so the ambiguity cannot arise. There is no
# vector3 * vector3 form to confuse it with, and no type here defines <
Zig.zig
const Quat = struct { x: f64, y: f64, z: f64, w: f64 };
fn qmul(a: Quat, b: Quat) Quat { ... }
// nothing in std. Zig has no operator overloading at all, which means the
// component-wise/composition ambiguity cannot arise from a punctuator — every product is a name you
// read
44

Geometric algebra carriers

Bivectors, rotors, and multivectors carry their metric signature in the type. A Euclidean 3D rotor uses dquat. Different signatures cannot assign across.

The t.math.geometricAlgebra library provides named products, contractions, transforms, and motor operations. A bivector is not a direction vector.

Trident.tri
bivector<euclid3> xy = (0,0,1)
bivector<projective3> axis =
    (0,0,1,0,0,0)
rotor<projective3> motor =
    (1,0,0,0,0,0,0,0)
# the signature is in the type: projective3 is (3,0,1), 8 even lanes
# rotor<euclid3> refuses: its rotor
#   is dquat, which already ships
# dvec3 v = xy refuses
# motor @ motor refuses: the product
#   is the named member
#   t.math.geometricAlgebra.product
C.c
typedef struct { double x, y, z; } vec3;
typedef struct { double xy, yz, zx; } biv3;

biv3 b = { 0.0, 0.0, 1.0 };
vec3 v = b;   /* error: incompatible */
/* two structs are two types, so that mix
   is refused. The signature is only the
   name you chose, every product is code
   you write, and no GA library for C is
   in common use */
C#.cs
var q = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2);
var v = Vector3.Transform(Vector3.UnitX, q);
// System.Numerics.Quaternion is the Euclidean 3D rotor and its own struct, not a Vector4. There is
// no bivector
// type (the axis is a plain Vector3), no
// PGA or CGA, and no GA library on NuGet in common use
C++.cpp
// klein: 3D PGA, header-only
kln::rotor r{1.5708f, 0.f, 0.f, 1.f};
kln::translator t{2.f, 1.f, 0.f, 0.f};
kln::motor m = r * t;
kln::point p{1.f, 3.f, 2.f};
kln::point q = m(p);   // the sandwich rotor, translator, motor, point, line and plane are distinct
// types, and the product and sandwich ship: ahead of Trident there. One algebra only, float lanes,
// SSE3 required, and the repository is archived
Cutlass.cut
bivector<euclid3> xy = (0, 0, 1)
bivector<projective3> axis =
    (0, 0, 1, 0, 0, 0)
rotor<projective3> motor =
    (1, 0, 0, 0, 0, 0, 0, 0)
# the signature is in the type: cut carries the kinds on their wire
# rotor<euclid3> refuses: its rotor
#   is dquat, which already ships
# dvec3 v = xy refuses
# motor @ motor refuses: the product
#   is a named member of the geometric
#   algebra family, whose cut name is
#   not fixed yet
Dart.dart
// package:vector_math
final q = Quaternion.axisAngle(Vector3(0.0, 0.0, 1.0), pi / 2);
final v = q.rotated(Vector3(1.0, 0.0, 0.0));
// the quaternion is the Euclidean rotor and a class of its own. No bivector, no PGA or CGA, and no
// GA package in common use on pub.dev
Elixir.ex
defmodule Biv3 do
  defstruct xy: 0.0, yz: 0.0, zx: 0.0
  def wedge({ax, ay, az}, {bx, by, bz}) do
    %Biv3{xy: ax * by - ay * bx, yz: ay * bz - az * by, zx: az * bx - ax * bz}
  end
end
# a struct tag, matched at run time by a clause head. No GA package on Hex in common use; the
# signature is a name
Go.go
type Vec3 [3]float64
type Bivec3 [3]float64 // xy, yz, zx

b := Bivec3{0, 0, 1}
var v Vec3 = b    // compile error
w := Vec3(b)      // allowed: same underlying type named types keep the two apart until a conversion
// is written. No GA package in common use, nothing in the standard library; the signature is a name
Haskell.hs
{-# LANGUAGE DataKinds, KindSignatures #-}
data Sig = Euclid3 | Projective3
newtype Bivector (s :: Sig) =
  Bivector [Double]
xy :: Bivector 'Euclid3
xy = Bivector [0, 0, 1]
-- a phantom index puts the signature in the type; GHC refuses a mix, though coerce crosses unless
-- the role is nominal. Hackage's clifford (2014) is reported as failing to build
Jai.jai
Bivector3 :: struct {
    xy: float;
    yz: float;
    zx: float;
}
wedge :: (a: Vector3, b: Vector3)
    -> Bivector3 { ... }
// the Math module has Vector3 and
// Quaternion (the Euclidean rotor) and
// nothing graded: no bivector, no PGA or CGA. A struct is its own type; the signature is a name you
// chose
Java.java
record Vec3(double x, double y, double z) {}
record Bivec3(double xy, double yz, double zx) {}

Bivec3 b = new Bivec3(0, 0, 1);
Vec3 v = b;   // incompatible types a record is its own nominal type, so the mix is refused. The JDK
// has no GA and no quaternion; JOML's Quaterniond is the usual Euclidean rotor
JavaScript.js
// ganja.js: any signature, generated
Algebra(3, 0, 1, () => {
  var R = Math.cos(0.5)
        + Math.sin(0.5) * 1e12;  // rotor
  var P = R >>> 1e1;     // the sandwich
});
// the product and sandwich ship, ahead of Trident. Each signature is its own generated class, but a
// rotor, a bivector and a vector are all the one multivector class: grade is unchecked
Julia.jl
using Grassmann
@basis S"+++"        # the metric
R = exp(π/4 * v12)   # a rotor
R >>> v1             # the sandwich the manifold V, signature included, and the grade are type
# parameters:
# Chain{V,2} is a bivector, Spinor{V}
# the even subalgebra. The guarantee holds and the algebra ships, ahead of Trident. A package, not
# Base
Kotlin.kt
data class Bivec3(val xy: Double, val yz: Double, val zx: Double)

infix fun Vec3.wedge(b: Vec3) = Bivec3(x * b.y - y * b.x, y * b.z - z * b.y, z * b.x - x * b.z)
// a data class is its own type, so a Bivec3 is never a Vec3. No GA library in common use; the
// signature is a name
Lua.lua
local Biv3 = {}
Biv3.__index = Biv3
local function biv3(xy, yz, zx)
  return setmetatable({ xy = xy, yz = yz, zx = zx }, Biv3)
end
-- the metatable tags the table, and the tag is checked only where your code checks it. No GA
-- library in common use, and no types to hold a signature
Luau.luau
-- Roblox: CFrame is the rigid motion
local cf = CFrame.fromAxisAngle(Vector3.new(0, 0, 1), math.pi / 2)
local p = cf * Vector3.new(1, 0, 0)
-- a CFrame (a position and orientation) is its own type, never a Vector3. It is not a bivector or a
-- rotor, there is no GA library in common use, and outside Roblox there is no CFrame
Nim.nim
type
  Vec3 = array[3, float64]
  Bivec3 = distinct array[3, float64]

let b = Bivec3([0.0, 0.0, 1.0])
let v: Vec3 = b        # type mismatch
let w = Vec3(b)        # explicit: allowed distinct keeps the lanes apart until a conversion is
# written. No GA library in common use; the signature is a name
Objective-C.m
#include <simd/simd.h>
simd_quatd q = simd_quaternion(M_PI / 2, simd_make_double3(0, 0, 1));
simd_double3 v = simd_act(q, simd_make_double3(1, 0, 0));
/* simd_quatd is the Euclidean rotor and
   its own type, not a simd_double4. No
   bivector, no PGA or CGA, and nothing in
   Foundation for either */
Perl.pl
package Bivec3;
sub new { my ($c, @l) = @_; bless [@l], $c }

package main;
my $b = Bivec3->new(0, 0, 1);
ref($b) eq 'Bivec3' or die;
# a blessed array knows its class, and the check runs only where you write it. No GA module on CPAN
# in common use; the signature is a name you chose
PHP.php
final class Bivec3 {
    public function __construct(public float $xy = 0.0, public float $yz = 0.0,
        public float $zx = 0.0,) {}
}
function spin(Bivec3 $b): void {}
spin(new Vec3(0, 0, 1));   // TypeError
// the class type is checked when the call runs. No GA library in common use; the signature is a
// name you chose
Python.py
# PyPI clifford (kingdon is similar)
from clifford import Cl
from math import e, pi
layout, blades = Cl(3)     # Cl(4, 1): CGA
e1, e12 = blades["e1"], blades["e12"]
R = e**(pi/4*e12)          # a rotor
v = R*e1*~R                # the sandwich the product, exp and sandwich ship, ahead of Trident. But
# rotor, bivector and vector are one MultiVector class: grade is data, and the signature is the
# layout each value carries
Ruby.rb
Vec3   = Struct.new(:x, :y, :z)
Bivec3 = Struct.new(:xy, :yz, :zx)

b = Bivec3.new(0.0, 0.0, 1.0)
b.is_a?(Vec3)    # false each Struct.new is its own class, so a bivector is not a vector, but
# nothing refuses the wrong one until a method is missing. No GA gem in common use
Rust.rs
// ultraviolet crate
use ultraviolet::{Bivec3, Rotor3, Vec3};
let xy = Bivec3::unit_xy();
let r = Rotor3::from_angle_plane(std::f32::consts::FRAC_PI_2, xy);
let v = r * Vec3::unit_x();
// let w: Vec3 = xy;  // mismatched types
// Bivec3 and Rotor3 are their own types, with the product and rotation Trident lacks. Euclidean
// only: no signature parameter, so no PGA motor
Scala.scala
final case class Vec3(x: Double, y: Double, z: Double)
final case class Bivec3(xy: Double, yz: Double, zx: Double)

val b = Bivec3(0, 0, 1)
val v: Vec3 = b    // type mismatch case classes keep the two apart. No GA library in common use;
// spire's Quaternion is the nearest, and the signature is only a class name
Swift.swift
import simd
let q = simd_quatd(angle: .pi / 2, axis: SIMD3<Double>(0, 0, 1))
let v = q.act(SIMD3<Double>(1, 0, 0))
// simd_quatd is the Euclidean rotor and its own type, not a SIMD4<Double>. No bivector, no PGA or
// CGA, and no GA package in common use
TypeScript.ts
// ganja.js, called from TypeScript
Algebra(3, 0, 1, () => {
  const R = Math.cos(0.5)
          + Math.sin(0.5) * 1e12;
  const P = R >>> 1e1;   // the sandwich
});
// ganja rewrites the function's source when it runs, so the checker sees only numbers: every
// element is `number` to TypeScript, and no signature or grade reaches the type system
Verse.verse
bivector3 := struct:
    XY : float = 0.0
    YZ : float = 0.0
    ZX : float = 0.0

# no GA type in Verse. SpatialMath has vector3, rotation and transform, the nearest idiom; a struct
# like this one is its own type, and every product is a function you write
Zig.zig
const Bivec3 = struct {
    xy: f64 = 0, yz: f64 = 0, zx: f64 = 0, };
const b = Bivec3{ .xy = 1 };
const v: @Vector(3, f64) = b; // error
// a struct is its own type, so the mix
// is a compile error. std has no GA type,
// and without operator overloading every product is a named function
45

A number multiplies and a map composes

* multiplies scalar values or corresponding lanes. @ composes maps or applies a map to a value. Matrix, quaternion, and tensor operators now implement that distinction.

Tensor equality returns one boolean. A payload-dependent shape mismatch faults at execution. Named axis operations remain calls because their axes are explicit arguments.

Trident.tri
tensor<f64> left = t.math.tensor.from([[1.0, 2.0], [3.0, 4.0]], "f64")
tensor<f64> right = left
tensor<f64> rows = left @ right  # composition
tensor<f64> lanes = left * right # elementwise
bool same = left == right       # one bool
C.c
/* nothing. The answer in C is BLAS: */
cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, 1.0, A, lda, B, ldb, 0.0, C, ldc);
/* fourteen arguments, no shape checking,
   and the dtype is in the function NAME
   (dgemm vs sgemm) rather than in any type
   the compiler can see */
C#.cs
// System.Numerics.Tensors
var rows = Tensor.Multiply(left, right);
// .NET's tensor work is recent and is mostly
// elementwise primitives over spans; TorchSharp is the real answer, where
// left.mm(right) and left * right are both
// available and C# operator overloading
// makes * elementwise — the NumPy split
// again
C++.cpp
// Eigen / xtensor
MatrixXd rows = left * right;      // matrix product
ArrayXXd scaled = la * ra;         // elementwise Eigen's answer is two TYPES — Matrix and Array —
// with the same storage and a different meaning for *, plus .array() and
// .matrix() to switch. Honest, and it means
// * still means two things
Cutlass.cut
# left comes from the tensor family's constructor, whose cut name is not fixed yet
tensor<f64> right = left
tensor<f64> rows = left @ right  # map
tensor<f64> lanes = left * right # lanes
bool same = left == right  # one bool
# @ is the grammar's composition token
# at multiply's rank, so * stays lane by lane on every kind
Dart.dart
// package:ml_linalg
final rows = left * right;   // Matrix *
                             // Matrix is a MATRIX product
final scaled = left.multiply(right);
// ml_linalg chose the opposite convention to NumPy: * is the matrix product and the elementwise one
// is named. Same two operations, same one punctuator, opposite assignment
Elixir.ex
# Nx
rows = Nx.dot(left, right)
scaled = Nx.multiply(left, right)
# named, all of it: Elixir has no operator overloading and Nx does not pretend otherwise. Nx tensors
# carry a dtype and a shape as runtime struct fields, checked on every call
Go.go
// gonum
var rows mat.Dense
rows.Mul(left, right)       // matrix product
rows.MulElem(left, right)   // elementwise two methods, named apart, and Go's lack of operator
// overloading means neither can be spelled with a punctuator. Nothing in the standard library
Haskell.hs
-- hmatrix
rows = left <> right      -- matrix product
scaled = left * right     -- elementwise hmatrix invents a separate operator (<>) for the matrix
-- product and leaves Num's * elementwise, which is the cleanest resolution on this page: two
-- punctuators, two operations, neither overloaded by shape
Jai.jai
// nothing in the modules past Matrix4. Jai's SOA and polymorphic structs would make a shape-checked
// tensor real at compile time, and nobody ships one — this language's audience writes the loop and
// reads the disassembly
Java.java
// ND4J / Deep Java Library
INDArray rows = left.mmul(right);
INDArray scaled = left.mul(right);
// mmul and mul, named apart, because Java has no operator overloading either. Nothing in the JDK —
// the Vector API
// (jdk.incubator.vector) is SIMD lanes, not
// shaped tensors, and has incubated for eight releases
JavaScript.js
// TensorFlow.js / ndarray
const rows = tf.matMul(left, right);
const scaled = tf.mul(left, right);
// named, because JavaScript has no operator overloading to abuse — so the ambiguity never arises.
// Nothing native: there are TypedArrays and no shape, no dtype checking, and no broadcast
Julia.jl
rows = left * right        # matrix product
scaled = left .* right     # elementwise
left == right              # ONE Bool
left .== right             # a BitArray the same principle, the opposite assignment: Julia gives *
# to the map and marks the lane-wise form with a dot, uniformly, across the whole language. Native
# and unambiguous — and it means
# `v * w` on two vectors is an error rather
# than the lane product, which is the cost of spending * on the map
Kotlin.kt
// multik
val rows = left dot right      // matrix
val scaled = left * right      // elementwise Kotlin's infix functions give `dot` a clean spelling
// and operator conventions give `*` the elementwise one — the same split, chosen deliberately, with
// two distinguishable spellings
Lua.lua
-- nothing in Lua. Torch was written in Lua and left for Python a decade ago; what remains is tables
-- of tables, or an FFI binding to BLAS that you write
local function matmul(a, b) ... end
Luau.luau
-- no tensor type. The only matrix in the API is the one inside CFrame, and there is no general
-- linear algebra. Parallel Luau and the native `vector` type make a hand-rolled kernel faster than
-- it was; it is still your loop
Nim.nim
# Arraymancer
let rows = left * right        # matrix
let scaled = left .* right     # elementwise Arraymancer takes Julia's answer: a separate dotted
# operator for the elementwise form, so `*` means one thing and `.*` means the other
Objective-C.m
// Accelerate / BNNS
vDSP_mmulD(A, 1, B, 1, C, 1, M, N, P);
vDSP_vmulD(A, 1, B, 1, C, 1, N);
/* mmul and vmul, named apart in the C API.
   No tensor type, no dtype in the type
   system — the element width is in the
   function suffix, D for double */
Perl.pl
# PDL
$rows = $left x $right;        # matrix
$scaled = $left * $right;      # elementwise PDL is thirty years old and still good: `x` is the
# matrix product (borrowing Perl's repetition operator) and `*` is elementwise with PDL's own
# broadcasting
PHP.php
// no tensor type, no BLAS binding in core, and no operator overloading.
function matmul(array $a, array $b): array {
    ...
}
// nested arrays and three loops. PHP is where this comparison stops being about design and starts
// being about whether the work belongs in the language at all
Python.py
import numpy as np
a @ b        # matrix product
a * b        # elementwise the same split Trident chose — arrived at the hard way. PEP 465 added @
# to the LANGUAGE in 3.5 after years of .dot(), and np.matrix, which gave * the product, was retired
# for the confusion of having two array types whose * meant opposite things

a == b       # an ARRAY of bools, so
if a == b:   # ValueError: the truth value of an array ... is ambiguous
Ruby.rb
# Numo::NArray
rows = left.dot(right)     # matrix product
scaled = left * right      # elementwise
(left == right)            # an NArray of
                           # 0s and 1s
# the NumPy split again, and the same truthiness trap: an NArray in an `if` is truthy regardless of
# its contents, because in Ruby everything but nil and false is
Rust.rs
// ndarray crate
let rows = left.dot(&right);  // matrix
let scaled = &left * &right;  // elementwise impl Mul on ArrayBase is ELEMENTWISE and
// .dot() is the matrix product — the NumPy
// convention, with `*` as the ambiguous one.
// `==` on two arrays is a single bool,
// which ndarray gets right
Scala.scala
// Breeze
val rows = left * right        // matrix
val scaled = left *:* right    // elementwise Breeze names them apart with two distinct operators,
// which Scala's symbolic method names make possible. Nothing in the Scala standard library
Swift.swift
// MLX, or Accelerate
let rows = MLX.matmul(left, right)
let scaled = left * right      // elementwise Apple's MLX follows NumPy: * is elementwise, matmul is
// named, and @ does not exist in Swift. Accelerate's BLAS is the platform-native route and looks
// like the C card
TypeScript.ts
// TensorFlow.js, typed
const rows: tf.Tensor2D = tf.matMul(l, r);
// the dtype and RANK are in the TypeScript
// type (Tensor2D, Tensor3D), which is real
// and useful — the SHAPE is not, so a
// 2x3 times a 4x5 is a runtime error, and
// all of it erases before the code runs
Verse.verse
# no tensor, no matrix type, no BLAS binding and no FFI to make one. Arrays of arrays of float are
# the whole vocabulary, and a matmul is a triple loop written inside a
# <computes> function
Zig.zig
// nothing in std, and no operator overloading to argue about.
fn matmul(a: Tensor, b: Tensor) !Tensor {
    ...
}
// zml and similar projects exist and are young. What Zig's comptime would buy here is a real one:
// shapes checked at compile time, as comptime parameters

Dimensions, lanes and swizzles stay typed

Trident.tri
dmat2<family=length> map = (2.0, 0.0, 0.0, 3.0)
dvec2<family=length> edge = (1.0, 1.0)
dvec2<unit=m2> area = map @ edge
dvec3 p = (1.5, -2.0, 4.0)
dvec3 reversed = p.zyx
f64 y = p.y
p.z += 0.5
46

Color carries its space

A color type carries its space. A value in another space needs an explicit operation. Color arithmetic and ordering refuse.

Keel conversion and blend bodies are live, with focused gamut-mapping proof retained.

Trident.tri
color<srgb> orange = (1,0.5,0,1)
color<oklch> soft = (0.7,0.2,50,1)
color<srgb> copy = orange
bool same = copy == orange
# color<oklch> x = orange refuses:
#   a space change is a conversion
# orange + orange refuses
# vec4 v = orange refuses: four lanes
#   are not a color without a space
# conversion and blending are named calls
C.c
typedef struct { float r, g, b, a; } Srgb;
typedef struct { float l, c, h, a; } Oklch;

Srgb orange = { 1.0f, 0.5f, 0.0f, 1.0f };
Oklch soft = { 0.7f, 0.2f, 50.0f, 1.0f };
/* soft = orange; is refused: two struct
   types never assign. That is the whole
   check, and you write it. No color type
   ships, and a float[4] holds a color
   just as well, with no space at all */
C#.cs
// Colourful (NuGet)
var orange = new RGBColor(1, 0.5, 0);
var toLab = new ConverterBuilder()
    .FromRGB(RGBWorkingSpaces.sRGB)
    .ToLab(Illuminants.D65).Build();
LabColor lab = toLab.Convert(orange);
// LabColor x = orange;  does not compile
// a type per model, crossed by a built converter. RGBColor does not say which RGB; the converter
// does. The BCL's System.Drawing.Color has no space
C++.cpp
// Boost.GIL
using namespace boost::gil;
rgb8_pixel_t orange(255, 128, 0);
cmyk8_pixel_t ink;
// ink = orange;   does not compile
color_convert(orange, ink);   // explicit GIL puts the color space in the pixel type. rgb_t is the
// RGB model, so sRGB and linear RGB share one type. The standard library has no color at all
Cutlass.cut
color<srgb> orange = (1, 0.5, 0, 1)
color<oklch> soft = (0.7, 0.2, 50, 1)
color<srgb> copy = orange
bool same = copy == orange
# color<oklch> x = orange refuses:
#   no conversion is implicit
# orange + orange refuses
# vec4 v = orange refuses: four lanes
#   are not a color without a space
# cut names no conversion or blend
#   member for color yet
Dart.dart
// Flutter's dart:ui; the SDK has none
final p3 = Color.from(alpha: 1, red: 1, green: 0.5, blue: 0, colorSpace: ColorSpace.displayP3);
final srgb = Color.from(alpha: 1, red: 1, green: 0.5, blue: 0);
Color c = p3;
c = srgb;          // one type, compiles the space is a runtime field of one Color class, and the
// enum names three: sRGB, extendedSRGB and displayP3
Elixir.ex
defmodule Srgb do
  defstruct [:r, :g, :b, a: 1.0]
end

orange = %Srgb{r: 1.0, g: 0.5, b: 0.0}
# a struct per space is the idiom, and a
# function head matching %Srgb{} raises
# FunctionClauseError on an %Oklch{} when
# it runs. No color type in the standard library, and no package that is the obvious answer
Go.go
orange := color.NRGBA{R: 255, G: 128, A: 255}
ink := color.CMYK{M: 127, Y: 255}
// orange = ink   does not compile
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
img.Set(0, 0, ink)   // converted silently image/color gives each model a struct, but all meet
// color.Color, so Set turns a CMYK into RGBA without a word, and no type says sRGB or linear
Haskell.hs
-- the colour package (Hackage)
import Data.Colour
import Data.Colour.SRGB
orange :: Colour Double
orange = sRGB 1 0.5 0
mid = blend 0.5 orange black
-- Colour is abstract and device independent: sRGB and linear RGB are ways in and out, and blends
-- come out
-- right (ahead of Trident). The RGB triple
-- toSRGB returns does not say sRGB
Jai.jai
Srgb  :: struct { r, g, b, a: float; }
Oklch :: struct { l, c, h, a: float; }

orange := Srgb.{1, 0.5, 0, 1};
// structs are nominal, so an Oklch will not assign to an Srgb. Math offers Vector4, which has no
// space in it; a color type and every conversion are yours to write
Java.java
ColorSpace lin = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
Color a = new Color(lin, new float[] {1f, 0.5f, 0f}, 1f);
Color b = new Color(1f, 0.5f, 0f); // sRGB
a.getColorSpace();   // known at run time one Color class for every space, so the compiler cannot
// tell a from b, and
// equals() compares 8-bit sRGB ints.
// getColorComponents(space, null) converts
JavaScript.js
import Color from "colorjs.io";
const orange = new Color("srgb", [1, 0.5, 0]);
const soft = orange.to("oklch");
soft.spaceId;          // "oklch"
ctx.fillStyle = "oklch(0.7 0.2 50)";
// colorjs.io keeps the space on the value at run time, and converts and gamut-maps
// (ahead of Trident). Nothing checks it
// before then, and to the canvas a color is a CSS string
Julia.jl
using Colors
orange = RGB(1.0, 0.5, 0.0)     # sRGB
soft = convert(LCHab, orange)
mid = weighted_color_mean(0.5, orange, RGB(0.0, 0.0, 1.0))
v = RGB{Float64}[]
push!(v, soft)    # converts, silently ColorTypes.jl has a type per space, Oklch included, and
# Colors.jl converts and blends (ahead of Trident). Julia calls convert at every typed slot
Kotlin.kt
// Jetpack Compose
val orange = Color(1f, 0.5f, 0f)
val p3 = Color(1f, 0.5f, 0f, colorSpace = ColorSpaces.DisplayP3)
val lab = orange.convert(ColorSpaces.Oklab)
var c: Color = p3
c = lab            // one type, compiles the space id is packed into the value and read at run time;
// lerp() mixes in Oklab, which Trident cannot yet. The View system's @ColorInt is a bare Int
Lua.lua
local orange = { 1, 0.5, 0, 1 }
local soft   = { 0.7, 0.2, 50, 1 }
-- two four-element tables, and nothing records that the second is OKLCH. Lua has no color type and
-- no static types to carry a space; a field such as
-- space = "oklch" holds only if every
-- function checks it
Luau.luau
--!strict
local orange = Color3.new(1, 0.5, 0)
local warm = Color3.fromHSV(0.08, 1, 1)
local mid = orange:Lerp(warm, 0.5)
local v: Vector3 = orange  -- type error
-- Color3 is Roblox's own color type and will not pass for a Vector3. It has no space to check:
-- fromHSV builds the same RGB Color3, and alpha lives apart, as a Transparency property
Nim.nim
# the chroma package (nimble)
import chroma
let orange = color(1.0, 0.5, 0.0)
let cie: ColorLAB = orange.lab
let back: Color = cie.color
# let x: ColorLAB = orange   refused
# an object type per space and a named proc to cross, so the compiler keeps them apart. std/colors
# is 24-bit RGB in a distinct int, with no space
Objective-C.m
NSColor *orange = [NSColor
    colorWithSRGBRed:1 green:0.5 blue:0
    alpha:1];
NSColor *p3 = [orange colorUsingColorSpace:
    [NSColorSpace displayP3ColorSpace]];
NSColor *c = p3;
c = orange;        // one class, compiles
/* the space is a runtime property of one
   class. colorUsingColorSpace: is a real,
   explicit conversion, which Trident has
   not shipped */
Perl.pl
use Convert::Color;
my $rgb = Convert::Color->new('rgb:1,0.5,0');
my $hsv = $rgb->convert_to('hsv');
print ref $hsv;   # Convert::Color::HSV
# Convert::Color (CPAN): a class per space
# and an explicit convert_to, so the space is the object's class, known when the code runs. Core
# Perl has no color type
PHP.php
use Spatie\Color\Rgb;
$orange = Rgb::fromString('rgb(255,128,0)');
$hsl = $orange->toHsl();
function paint(Rgb $c): void {}
paint($hsl);   // TypeError, at run time
// spatie/color (Composer): a class per format and a named conversion, and a declared Rgb parameter
// refuses an Hsl. PHP itself has no color type
Python.py
from colormath.color_objects import (sRGBColor, LabColor)
from colormath.color_conversions import (convert_color)
orange = sRGBColor(1.0, 0.5, 0.0)
lab = convert_color(orange, LabColor)
# colormath (PyPI): a class per space and a
# named conversion, told apart only when the code runs. The stdlib's colorsys converts bare tuples;
# coloraide keeps the space as a string on one Color class
Ruby.rb
require "color"
orange = Color::RGB.from_values(255, 128, 0)
hsl = orange.to_hsl       # Color::HSL
orange == hsl             # compares anyway the color gem: a class per space and
# named conversions, but == coerces the
# other color to its own class first, so two spaces compare without a word. The standard library has
# no color class
Rust.rs
// palette crate
use palette::{IntoColor, LinSrgb, Mix, Oklch, Srgb};
let orange: Srgb = Srgb::new(1.0, 0.5, 0.0);
let lin: LinSrgb = orange.into_linear();
let soft: Oklch = orange.into_color();
let mid = lin.mix(LinSrgb::new(0.0, 0.0, 1.0), 0.5);
// let x: Oklch = orange;  does not compile
// a type per space, conversions named, and mixing in linear light (ahead of Trident). Not in std
Scala.scala
import java.awt.Color
import java.awt.color.ColorSpace
val lin = ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB)
val a = new Color(lin, Array(1f, 0.5f, 0f), 1f)
val b = new Color(1f, 0.5f, 0f)
a == b    // equals() on 8-bit sRGB ints
// the JVM's java.awt.Color: the space is a runtime field of one class, and no widely used Scala
// library puts it in the type
Swift.swift
let orange = Color(.sRGB, red: 1, green: 0.5, blue: 0)
let p3 = Color(.displayP3, red: 1, green: 0.5, blue: 0)
var c = orange
c = p3             // one type, compiles
let cg = CGColor(srgbRed: 1, green: 0.5, blue: 0, alpha: 1)
// SwiftUI takes the space as an argument and keeps it inside one Color type, and CGColor carries it
// as a runtime field
TypeScript.ts
// culori, typed by @types/culori
import { converter } from "culori";
const toOklch = converter("oklch");
const soft = toOklch({ mode: "rgb", r: 1, g: 0.5, b: 0 });
soft.l;       // Oklch has l, c and h
soft.r;       // type error: not an Rgb
// a literal mode field puts the space in the type, so an Rgb will not pass as an Oklch. The DOM
// types a color as a string
Verse.verse
using { /Verse.org/Colors }

Orange : color =
    MakeColorFromSRGB(1.0, 0.5, 0.0)
Blue : color =
    MakeColorFromSRGB(0.0, 0.0, 1.0)
Sum := Orange + Blue      # defined one color struct in one documented space, linear ACES 2065-1.
# sRGB and HSV are float arguments and tuples, not types, and color_alpha adds A as a second struct
Zig.zig
const Srgb = struct {
    r: f32, g: f32, b: f32, a: f32 = 1, };
const Oklch = struct {
    l: f32, c: f32, h: f32, a: f32 = 1, };
const orange = Srgb{ .r = 1, .g = 0.5, .b = 0 };
// structs are nominal, so an Oklch will not coerce to an Srgb. Nothing in std:
// @Vector(4, f32) is the other spelling,
// and it has no space at all
47

The offset nobody recorded is its own state

A civil label can have a known offset or no offset. A label without an offset is not an instant on a timeline. Calendar validation rejects an impossible date.

Labels with matching offsets compare by their fields. Different offsets fault without a timeline authority. Z, +00:00, and -00:00 canonicalize to UTC.

Trident.tri
civil utc    = 1987-03-05T14:30:00Z
civil local  = 1987-03-05T14:30:00+02:00
civil nozone = 1987-03-05T14:30:00
# three states: known UTC, a known offset, and an offset nobody recorded

civildate day = 1987-03-05  # the two
civiltime clock = 14:30:00  # halves

bool before = utc < nozone  # faults: the offsets differ, so no order exists
bool same = utc == local   # false
C.c
struct tm t = {0};
strptime("1987-03-05T14:30:00", "%Y-%m-%dT%H:%M:%S", &t);
/* struct tm has no offset field in standard
   C — tm_gmtoff is a BSD/glibc extension.
   mktime() then interprets it as LOCAL time,
   reading the TZ environment variable, so
   the same bytes parse to different instants
   on different machines */
C#.cs
var nozone = new DateTime(1987, 3, 5, 14, 30);
// DateTimeKind.Unspecified — the third state
var utc = DateTime.SpecifyKind(nozone, DateTimeKind.Utc);
nozone == utc;   // TRUE. The Kind is simply ignored by comparison and equality, which is the
// notorious .NET date bug: the state exists and the operators do not read it
C++.cpp
// C++20 <chrono>
local_days ld = 1987y/March/5d;
local_time<seconds> nozone = ld + 14h + 30min;
sys_time<seconds> utc = ...;
// std::chrono distinguishes local_time from
// sys_time as separate types, which is exactly the right distinction, and converting needs a
// time_zone. Library support arrived slowly and unevenly
Cutlass.cut
civil utc    = 1987-03-05T14:30:00Z
civil local  = 1987-03-05T14:30:00+02:00
civil nozone = 1987-03-05T14:30:00
# three states: known UTC, a known offset, and an offset nobody recorded

civildate day = 1987-03-05  # the two
civiltime clock = 14:30:00  # halves

bool before = utc < nozone  # fails: the offsets differ, so no order exists; the caller handles it
bool same = utc == local   # false
Dart.dart
final nozone = DateTime.parse("1987-03-05T14:30:00");
// no trailing Z means isUtc is false, which means LOCAL — Dart has exactly two states and no offset
// type at all. A DateTime knows only whether it is UTC or the device's zone, so a +02:00 input is
// converted and the offset discarded
Elixir.ex
{:ok, nozone} = NaiveDateTime.from_iso8601("1987-03-05T14:30:00")
{:ok, utc, 0} = DateTime.from_iso8601("1987-03-05T14:30:00Z")
# Elixir names the state outright — NaiveDateTime IS "we do not know the offset" — and the two are
# separate structs with separate modules. The clearest naming of this distinction on the page
Go.go
nozone, _ := time.Parse("2006-01-02T15:04:05", "1987-03-05T14:30:00")
// Go gives this a UTC Location — there is no unzoned type at all, so the unknown case becomes UTC
// silently, exactly the collapse this section is about. Everything else in package time is
// excellent
Haskell.hs
-- the time package
nozone :: LocalTime
utc    :: UTCTime
zoned  :: ZonedTime
-- three distinct types, kept apart by the type checker, with ZonedTime carrying a TimeZone. Not in
-- base; `time` ships with GHC, which is close enough that most Haskell programmers never notice
Jai.jai
#import "Basic";

t := current_time_consensus();
cal := to_calendar(t, .UTC);
s := calendar_to_string(cal);
// Apollo_Time counts femtoseconds from an epoch, and to_calendar takes .UTC or .LOCAL — two states,
// not three. No stored offset, no parser, and no wall time that remembers it was never placed
Java.java
LocalDateTime nozone =
    LocalDateTime.parse("1987-03-05T14:30");
OffsetDateTime utc =
    OffsetDateTime.parse("1987-03-05T14:30Z");
// java.time is the best of these: Local, Offset and Zoned are three types and the compiler keeps
// them apart. A LocalDateTime is unplaceable by construction, and turning one into an instant
// demands a zone
JavaScript.js
// Temporal (ES2026)
const nozone = Temporal.PlainDateTime
    .from("1987-03-05T14:30:00");
const utc = Temporal.Instant
    .from("1987-03-05T14:30:00Z");
// PlainDateTime is genuinely the unzoned state and cannot be compared to an Instant without an
// explicit zone. Legacy `Date` has none of this: it is one UTC millisecond count with a local-time
// face
Julia.jl
using Dates
nozone = DateTime(1987, 3, 5, 14, 30)
# Base's DateTime has NO time zone at all — every value is naive, so Julia models the unknown state
# by having only it. TimeZones.jl adds ZonedDateTime; comparing one to a Base DateTime needs an
# explicit conversion
Kotlin.kt
val nozone = LocalDateTime.parse("1987-03-05T14:30")
// kotlinx-datetime names them LocalDateTime and Instant, and — notably — omits an offset-only type
// on the grounds that an offset without a zone is rarely what you want. java.time is one interop
// call away on the JVM
Lua.lua
local t = os.time({year=1987, month=3, day=5, hour=14, min=30})
-- os.time interprets the table as LOCAL time and returns an epoch number. There are no date types,
-- no offset, no validation, and os.date's output depends on the process locale and TZ
Luau.luau
local now = DateTime.now()
now.UnixTimestampMillis
now:ToIsoDate()
now:FormatLocalTime("LLL", "en-us")

-- DateTime is an INSTANT and only an instant: it carries no offset and no unplaced wall time, so
-- the third state has nowhere to live. A "local" reading is a formatting call against the SERVER's
-- zone, decided at the call and not stored
Nim.nim
let t = parse("1987-03-05T14:30:00", "yyyy-MM-dd'T'HH:mm:ss")
# std/times' DateTime always carries a Timezone, and parse defaults to the LOCAL one when the string
# has no offset — the same silent placement as Go and Ruby, with the same consequence for a value
# that was genuinely unplaceable
Objective-C.m
NSDateComponents *c =
    [[NSDateComponents alloc] init];
c.year = 1987; c.hour = 14;
/* same Foundation model, and the same gap:
   an NSDate is an absolute instant, an
   NSDateComponents is a mutable field bag,
   and there is no type in between that is a
   validated wall time */
Perl.pl
# DateTime (CPAN)
my $dt = DateTime->new(year => 1987, month => 3, day => 5, hour => 14);
# DateTime defaults to the "floating" time zone — which IS the unknown state, named and documented,
# and one of very few libraries anywhere to model it explicitly. Core Perl gives you epoch seconds
# and
# localtime()
PHP.php
$nozone = new DateTimeImmutable("1987-03-05T14:30:00");
// with no offset in the string, PHP applies date.timezone from php.ini — a GLOBAL SETTING in a
// config file, so the unknown case becomes whatever the server was configured with. The class is
// otherwise capable and the default is the trap
Python.py
from datetime import datetime, timezone
naive = datetime(1987, 3, 5, 14, 30)
aware = naive.replace(tzinfo=timezone.utc)
naive < aware
# TypeError: can't compare offset-naive and
# offset-aware datetimes — Python has all three states and refuses the comparison, the closest match
# on this page. The trap is that utcnow() returned a NAIVE value for twenty-five years
Ruby.rb
t = Time.new(1987, 3, 5, 14, 30)
# Ruby's Time always carries an offset: with none given it takes the SYSTEM's, so the unknown case
# silently becomes local time and the same script gives two answers on two machines. DateTime (from
# `date`) defaults to +00:00, a different silent assumption
Rust.rs
// chrono crate
let nozone = NaiveDateTime::parse_from_str("1987-03-05T14:30:00", "%Y-%m-%dT%H:%M:%S")?;
let utc: DateTime<Utc> = ...;
// three types — NaiveDateTime, DateTime<Utc>,
// DateTime<FixedOffset> — and the compiler
// refuses to compare across them. std has only Instant and SystemTime, no calendar at all
Scala.scala
val nozone = java.time.LocalDateTime.parse("1987-03-05T14:30")
// Scala uses java.time directly and inherits all of its correctness. The standard library adds
// nothing of its own here, which in this one case is the right decision
Swift.swift
var comps = DateComponents()
comps.year = 1987; comps.hour = 14
// DateComponents is Foundation's unzoned form: a bag of OPTIONAL Ints with no validity law, so
// month 13 and day 32 are representable and only Calendar's validate step objects. Date itself is
// one instant with no calendar label at all
TypeScript.ts
// Temporal, typed
const nozone: Temporal.PlainDateTime =
    Temporal.PlainDateTime.from("1987-03-05T14:30");
// TypeScript types Temporal's three classes properly, so the distinction survives into the type
// checker. Before Temporal the ecosystem answer was Moment or date-fns over the same single legacy
// Date
Verse.verse
Elapsed := GetSimulationElapsedTime()

# a float of seconds since the session began, and that is the entire clock: no calendar type, no
# date, no time of day, no offset and no parser. UEFN hands a program a session, not a wall clock,
# so the third state has nothing to be a state of
Zig.zig
const ts = std.time.timestamp();
const ed = std.time.epoch.EpochDay{ .day = d };
// std.time is a clock and an epoch-day calculator: no calendar type, no wall time, no offset, no
// parser, no formatter. Date handling in Zig today means a package or a C call
48

Overflow is a fault, and floats hold the same line

Integer overflow, division by zero, and negation of the signed minimum raise catchable faults. The rules do not change in release builds. Mixed integer operators need an exact common width.

Binary-float arithmetic publishes a finite result or faults. NaN comparisons fault; infinity retains its order. Nonfinite literals can be stored and classified, but arithmetic does not silently use them.

Trident.tri
i32 big  = 2147483647
i32 over = big + 1     # catchable fault
i32 zero = 1 / 0       # catchable fault

i32 low = -2147483647 - 1   # the i32 min
i32 neg = -low         # catchable fault
i32 div = low / -1     # catchable fault

f64 far = 1.0e308 * 10.0   # catchable fault
f64 positive = f64(infinity) # a literal, inert
f64 bad = 1e400        # refuses at PARSE
C.c
int big = INT_MAX;
big + 1;   /* UNDEFINED BEHAVIOR. Not a
   wrap — undefined, so the compiler may
   assume it never happens and DELETE the
   overflow check you wrote to detect it.
   This is not theoretical: it is the reason
   -fwrapv and -ftrapv exist, and why
   __builtin_add_overflow was added */
1 / 0;     /* also undefined */
C#.cs
int big = int.MaxValue;
big + 1;                // wraps (unchecked
                        // is the default)
checked { big + 1; }    // OverflowException C# has `checked`/`unchecked` as LANGUAGE keywords and a
// project-wide
// <CheckForOverflowUnderflow> switch, which
// is the most complete overflow story here — and the default is still the wrapping one
C++.cpp
int big = INT_MAX;
big + 1;          // undefined behavior, inherited from C
// C++20 finally fixed the REPRESENTATION
// (two's complement is now mandatory) and
// deliberately left signed overflow undefined. <utility>'s saturating and checked helpers are
// C++26, and
// __builtin_*_overflow remains an extension
Cutlass.cut
i32 big  = 2147483647
i32 over = big + 1     # fault.overflow
i32 zero = 1 / 0       # fault.divide

i32 low = -2147483647 - 1   # the i32 min
i32 neg = -low         # fault.overflow
i32 div = low / -1     # fault.divide each fails the function in every build, and the caller's else
# arm handles it; +% wraps, +| saturates

f64 far = 1.0e308 * 10.0  # infinity: IEEE by default, so an infinity or a NaN is a value that
# computes on
f64<finite=true> held =
    1.0e308 * 10.0   # fault.intent:
# Trident's finite law, per binding
f64 positive = infinity  # a value
f64 bad = 1.0e400  # refuses: a finite spelling that rounds to infinity
Dart.dart
final big = 0x7FFFFFFFFFFFFFFF;
big + 1;   // wraps to the minimum on native
           // — and on the WEB, int is a
           // double, so the same expression loses precision instead
1 ~/ 0;    // IntegerDivisionByZeroException the same source has two different arithmetic behaviors
// depending on the compilation target
Elixir.ex
big = 1 <<< 62
big + 1        # exact — BEAM integers are arbitrary precision
1 / 0          # ArithmeticError
div(1, 0)      # ArithmeticError and float overflow raises too: 1.0e308 *
# 10 is an ArithmeticError rather than an
# infinity, because the BEAM has no representation for one
Go.go
var big int32 = math.MaxInt32
big + 1        // -2147483648, wraps silently
1 / 0          // a COMPILE error for constant
               // operands, a runtime panic for variables
// Go has no checked arithmetic in the standard library at all: math/bits gives you Add64 with a
// carry out, and building a checked add on top is yours
Haskell.hs
maxBound :: Int
maxBound + 1     -- wraps silently
(2 :: Integer) ^ 200   -- exact, unbounded
div 1 0          -- divide by zero exception
1 / 0 :: Double  -- Infinity, IEEE Integer is the default numeric type under Haskell's defaulting
-- rules, so the common case does not overflow at all; Int is the one you opted into
Jai.jai
big : s32 = 2147483647;
big + 1;        // wraps — arithmetic bounds checks are not implemented

cast(s8) big;   // RUNTIME error: a
                // cast that loses information fails
cast,no_check(s8) big;  // opts out

values[99];     // bounds-checked unless the scope says #no_abc
1.0 / 0.0;      // +inf, a value
Java.java
int big = Integer.MAX_VALUE;
big + 1;                  // -2147483648,
                          // wraps silently
Math.addExact(big, 1);    // ArithmeticException
1 / 0;                    // ArithmeticException
1.0 / 0.0;                // Infinity, IEEE the JDK gives you addExact, subtractExact, multiplyExact
// and toIntExact — and the operator stays the wrapping one
JavaScript.js
2 ** 53 + 1        // 9007199254740992 —
                   // the +1 is simply gone
2147483647 | 0;    // bitwise ops are i32
1 / 0              // Infinity, silently every JavaScript number is a double, so
// "integer overflow" is a silent precision
// loss that begins at 2^53 and gets worse from there, with no diagnostic ever
Julia.jl
big = typemax(Int64)
big + 1        # -9223372036854775808 —
               # wraps SILENTLY, by design
Base.checked_add(big, 1)   # OverflowError
1 ÷ 0          # DivideError
1 / 0          # Inf, IEEE Julia's silent integer wrap is a documented, deliberate performance
# decision, and the most-discussed one in the language
Kotlin.kt
val big = Int.MAX_VALUE
big + 1                  // wraps silently
Math.addExact(big, 1)    // ArithmeticException
1 / 0                    // ArithmeticException the JVM's checked helpers, reachable as always.
// Kotlin adds no overflow-checking of its own, and its unsigned types wrap by definition
Lua.lua
math.maxinteger + 1    -- wraps to math.mininteger, silently
1 // 0                 -- error: attempt to
                       -- perform 'n//0'
1 / 0                  -- inf (float divide)
-- integers wrap with no diagnostic and no checked alternative in the standard library
Luau.luau
local big = 2^53
big + 1 == big     -- true, silently
1 / 0              -- inf
1 // 0             -- inf (floor div)
-- one binary64 lane, so there is no integer to overflow and no wrap like Lua 5.3's. What replaces
-- it is silent precision loss past 2^53, with no diagnostic on any path
Nim.nim
var big: int32 = high(int32)
big + 1     # OverflowDefect — and the check is COMPILED OUT under
            # -d:release, so the same
            # expression wraps in production unless you also pass
            # --overflowChecks:on
1 div 0     # DivByZeroDefect
Objective-C.m
int big = INT_MAX;
big + 1;   /* undefined behavior, straight
   from C. Foundation's NSDecimalNumber DOES
   raise on overflow, and NSInteger does not,
   so the safe path is the one that boxes
   every value into an object */
Perl.pl
my $big = 9223372036854775807;
$big + 1;   # 9.22337203685478e+18 — the IV
            # silently becomes an NV
# exactly PHP's failure: no diagnostic, and the precision is gone. `use bigint` swaps every literal
# in the lexical scope for a
# Math::BigInt, which is a large hammer
PHP.php
$big = PHP_INT_MAX;
$big + 1;   // 9.2233720368548E+18 — the int
            // silently becomes a FLOAT
// which is the worst failure mode on this
// page: no error, no wrap, no exception,
// and the low-order digits are simply gone while the value still looks plausible
intdiv(1, 0);   // DivisionByZeroError
Python.py
big = 2 ** 31 - 1
big + 1            # 2147483648 — int is
                   # arbitrary precision, so integer overflow does not exist
1 / 0              # ZeroDivisionError
1.0 / 0.0          # ZeroDivisionError too, which is NOT IEEE: you cannot get an infinity from
# division at all
Ruby.rb
big = 2 ** 62
big + 1        # exact — Ruby Integers are arbitrary precision
1 / 0          # ZeroDivisionError
1.0 / 0.0      # Infinity, IEEE integer overflow does not exist, and the integer and float division
# operators answer differently on zero, which is the correct pair of answers
Rust.rs
let big = i32::MAX;
big + 1;   // panics in debug, WRAPS in release — the shipped binary does not behave like the one
// you tested
big.checked_add(1);      // Option<i32>
big.wrapping_add(1);     // explicit wrap
big.saturating_add(1);   // explicit clamp the explicit methods are excellent; the default is a
// build-profile decision
Scala.scala
val big = Int.MaxValue
big + 1                  // wraps silently
Math.addExact(big, 1)    // ArithmeticException
1 / 0                    // ArithmeticException the JVM story again. Scala's own numeric types are
// the JVM's, so there is no Scala answer distinct from the Java one
Swift.swift
let big = Int32.max
big + 1        // runtime TRAP — the process dies immediately
big &+ 1       // explicit wrap
big.addingReportingOverflow(1)  // (value,
                                //  Bool)
// the best DEFAULT on this page by a distance. The trap is a fatalError, so nothing can catch it —
// a library cannot contain one inside its caller's request
TypeScript.ts
const big = 2 ** 53;
big + 1;   // 9007199254740992 — identical
           // to JavaScript, because it IS JavaScript
// TypeScript types `number` and has nothing to say about its range. The one place it helps is
// bigint, where it refuses to mix a bigint with a number
Verse.verse
Big := 2147483647
Big + 1     # 2147483648, exact: int is
            # arbitrary precision, so there is no overflow for a fault to name

X := 1 / 0  # FAILS its context. No inf, no NaN, no thrown value — the surrounding if simply takes
# else

# float is still IEEE, and still publishes an infinity of its own
Zig.zig
const big: i32 = math.maxInt(i32);
big + 1;   // panics in Debug and ReleaseSafe, undefined in ReleaseFast — "safety-checked undefined
// behavior"
big +% 1;                    // explicit wrap
@addWithOverflow(big, 1);    // value + bit
// three explicit spellings and a default that depends on the build mode, like Rust
49

What is refused before the program runs

Compile means the program refuses before it runs. Fault means execution raises a typed failure. Runtime means another language reports an exception or error return. Silent means the shown idiom reports no failure.

The table describes the cards' stated types and libraries. A package can change the result. Nullable Trident reads require compile-time presence proof; an explicit null branch can raise a runtime fault.

MistakeTridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
Adding USD to EURCompileSilentSilentSilentCompileSilentRuntimeSilentCompileSilentRuntimeRuntimeCompileSilentSilentSilentSilentSilentSilentRuntimeRuntimeRuntimeRuntimeRuntimeSilentCompileSilentSilent
Adding a mass to a forceCompileSilentCompileCompileCompileRuntimeRuntimeSilentCompileSilentRuntimeRuntimeRuntimeSilentSilentSilentSilentRuntimeSilentRuntimeRuntimeRuntimeCompileCompileCompileCompileSilentSilent
An f32 amount meeting an f64 amountCompileSilentSilentSilentCompileN/AN/ACompileCompileSilentSilentN/ASilentSilentN/AN/ACompileSilentN/AN/AN/AN/ACompileSilentCompileN/AN/ACompile
Ordering two complex numbersCompileCompileCompileCompileCompileCompileSilentCompileCompileN/ACompileN/ARuntimeCompileN/AN/ACompileCompileSilentN/ARuntimeRuntimeCompileCompileCompileN/AN/ACompile
Ordering two bounding boxesCompileCompileCompileCompileCompileCompileSilentCompileSilentN/ACompileSilentRuntimeCompileRuntimeRuntimeCompileSilentSilentSilentSilentRuntimeCompileCompileCompileSilentN/ACompile
Subtracting one geographic position from anotherCompileSilentSilentSilentCompileSilentSilentSilentSilentSilentSilentSilentRuntimeSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilent
Multiplying two quaternions lane by laneCompileSilentSilentSilentCompileSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentCompileSilent
Comparing an unplaceable wall time to a UTC oneFaultSilentSilentCompileFaultSilentRuntimeSilentCompileN/ACompileRuntimeRuntimeCompileSilentN/ASilentSilentSilentSilentRuntimeSilentCompileCompileSilentCompileN/AN/A
Reading a null where a value is requiredCompile: presence proof requiredSilentRuntimeSilentCompileRuntimeRuntimeCompileRuntimeRuntimeRuntimeRuntimeCompileRuntimeRuntimeRuntimeSilentSilentRuntimeRuntimeRuntimeCompileRuntimeCompileCompileCompileRuntime
Indexing an array out of boundsFaultSilentRuntimeSilentFaultRuntimeRuntimeRuntimeRuntimeRuntimeRuntimeSilentRuntimeRuntimeSilentSilentRuntimeRuntimeSilentRuntimeRuntimeSilentRuntimeRuntimeRuntimeSilentCompileRuntime
A multi-way select that misses an enum memberCompileSilentSilentSilentCompileCompileRuntimeSilentRuntimeSilentCompileN/AN/ACompileN/AN/ACompileSilentN/ARuntimeRuntimeN/ACompileRuntimeCompileSilentSilentCompile
Reaching an enum member by its bare nameCompileSilentCompileSilentCompileCompileN/ACompileSilentCompileCompileN/ASilentCompileN/AN/ASilentSilentN/ACompileCompileN/ACompileCompileCompileCompileCompileCompile
Comparing two values of different enum typesCompileSilentCompileCompileCompileSilentN/ACompileCompileCompileCompileN/ASilentCompileN/AN/ACompileSilentN/ASilentSilentN/ACompileCompileCompileCompileCompileCompile
An integer literal too large for its declared typeCompileSilentCompileSilentCompileCompileN/ACompileSilentCompileCompileSilentCompileCompileSilentSilentCompileSilentSilentSilentN/AN/ACompileCompileCompileSilentCompileCompile
Narrowing an i64 into an i32 by assignmentCompileSilentCompileSilentCompileN/AN/ACompileCompileRuntimeCompileN/ARuntimeCompileN/AN/ARuntimeSilentN/AN/AN/AN/ACompileCompileCompileN/AN/ACompile
Concatenating a number into text with no conversionCompileSilentSilentSilentCompileCompileRuntimeCompileCompileCompileSilentSilentRuntimeSilentSilentSilentCompileSilentSilentSilentRuntimeRuntimeCompileSilentCompileSilentCompileCompile
Reading the clock or the environment from a function that never declared itFaultSilentSilentSilentSilent, unless intent pure bounds itSilentSilentSilentCompileSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentSilentCompileSilent

Refused before the program runs , a typed fault at execute , an exception or error return on the line that ran , no diagnostic anywhere , and not expressible in the language .

50

What the refusal actually says

A Trident refusal names its stage, source location, rule, and repair. Syntax diagnostics also state the token or end of input they found. Execute faults keep a typed reason and cause.

The cards retain previously recorded compiler output. This page refresh does not rerun those language toolchains. The language reference and fault catalog own current exact wording.

Trident.tri
text main(text[] argv) {
    text digits = "12"
    i32 count = digits
    return text(count + 1)
}
# main.tri:3:17: refused [D.bind] expression
# type does not match its destination: `i32(digits)` converts `text` to `i32` or faults at a value
# `i32` does not hold
#   3 |     i32 count = digits
#     |                 ^^^^^^
#   alternative: replace bytes [64, 70)
#   with `i32(digits)`
#   help: D.bind: `Type name = expr`
# tricli: falseStart: compile tricli: 1 refusal in main.tri

# Coordinates count from one, as an editor's do, so 3:17 is `digits` on the third line. The edit is
# in the message, i32(digits), and so is what that edit can still do at run time: fault on text that
# is no i32. The alternative line is the same edit as data, the bytes of `digits` an editor
# replaces, counted from zero; an alternative, because accepting that fault is the author's choice.
C.c
#include <stdio.h>

int main(void) {
    const char *digits = "12";
    int count = digits;
    printf("%d", count + 1);
    return 0;
}
/* gcc 16.1.0 -std=c17:

m.c: In function 'main':
m.c:5:17: error: initialization of 'int'
    from 'const char *' makes integer
    from pointer without a cast
    [-Wint-conversion]
    5 |     int count = digits;
      |                 ^~~~~~

   The two types it names are the two C
   has: there is no string type, so the
   mistake reads as a pointer. It was a
   warning that still linked until GCC
   14 promoted it, and -w restores that
   today. */
C#.cs
string digits = "12";
int count = digits;
System.Console.WriteLine(count + 1);
// .NET SDK 10.0.401 (Roslyn):

// m.cs(2,13): error CS0029: Cannot
// implicitly convert type 'string' to
// 'int'

// Both types, an exact coordinate in its own parenthesized form, and a stable code worth searching
// for. The edit, int.Parse(digits), is the reader's to know.
C++.cpp
#include <string>

int main() {
    std::string digits = "12";
    int count = digits;
    return count + 1;
}
/* g++ 16.1.0 -std=c++20:

m.cpp:5:17: error: cannot convert
    'std::string' {aka 'std::__cxx11::
    basic_string<char>'} to 'int' in
    initialization
    5 |     int count = digits;
      |                 ^~~~~~
      |                 |
      |                 std::string {aka
      |                 std::__cxx11::
      |                 basic_string<char>}

   The type is named twice and the
   second spelling is the mangled one,
   which is the version that turns up in
   the linker error later. */
Cutlass.cut
# target: x64-linux, x64-windows
export void main() {
    text digits = "12"
    i32 count = digits
    cut.host.write(text(count + 1))
        else use 0
}
# check: main.cut:4:17: ...;
#   write `i32(digits)`

# Described, not quoted: the check area is not written, so no wording exists yet. The design fixes
# the shape: one line, the compiler area, the unit by its basename, line and byte column both from
# 1, and the message ending in the repair.
# i32(digits) converts exactly or
# fails with fault.convert. No error
# code, and no caret display.
Dart.dart
String digits = "12";
int count = digits;
print(count + 1);
// dart analyze:

// error - m.dart:2:13 - A value of type
// 'String' can't be assigned to a
// variable of type 'int'. -invalid_assignment

// A sentence rather than a code, with the diagnostic's name on the end for anyone who needs to
// suppress it
Elixir.ex
digits = "12"
count = digits
IO.puts(count + 1)
# elixir:

# ** (ArithmeticError) bad argument in
#    arithmetic expression: "12" + 1
#     :erlang.+("12", 1)

# There is no destination to declare, so there is nothing for the compiler to check. Dialyzer, run
# separately over the built BEAM files, is where a mismatch is found before the run.
Go.go
digits := "12"
var count int = digits
fmt.Println(count + 1)
// go build:

// ./m.go:2:17: cannot use digits
// (variable of type string) as int
// value in variable declaration

// It names the value, both types, and which position the value was in. No code, no caret, one line.
Haskell.hs
digits :: String
digits = "12"
count = digits :: Int
-- ghc:

-- m.hs:3:9: error: [GHC-83865]
--     * Couldn't match type '[Char]'
--                    with 'Int'
--       Expected: Int
--         Actual: String

-- Expected and Actual on their own lines, and since GHC 9.4 a stable
-- code beside the word error
Jai.jai
digits := "12";
count: s32 = digits;
print("%\n", count + 1);
// The Jai compiler refuses at compile time, naming the type it wanted and the type it was given, at
// the assignment.

// (Described, not quoted: this is a
// closed toolchain no run behind this page exercised, so the card states what the message carries
// rather than reproducing wording it cannot check.)
Java.java
String digits = "12";
int count = digits;
System.out.println(count + 1);
// javac 21:

// M.java:2: error: incompatible types:
// String cannot be converted to int
// 1 error

// No column and no code: javac names the line, then reprints it with a caret beneath the site.
// "cannot be converted" is the whole of the advice.
JavaScript.js
const digits = "12";
let count = digits;
console.log(count + 1);
// node v24.16.0:

// 121

// There is no diagnostic to print. The mistake became a string concatenation, the process exited 0,
// and 121 is what shipped.
Julia.jl
digits::String = "12"
count::Int = digits
println(count + 1)
# julia:

# ERROR: MethodError: Cannot `convert` an
# object of type String to an object of type Int64 Stacktrace:
#  [1] top-level scope

# The annotation is real and it is enforced -- by a convert call on the line, at the moment that
# line runs.
Kotlin.kt
val digits: String = "12"
val count: Int = digits
println(count + 1)
// kotlinc 2 (K2):

// m.kt:2:18: error: initializer type
// mismatch: expected 'kotlin.Int', actual 'kotlin.String'.

// Fully qualified on both sides, which is what keeps it unambiguous when the two types share a
// short name
Lua.lua
local digits = "12"
local count = digits
print(count + 1)
-- lua 5.4:

-- 13

-- Nothing is declared, so nothing is checked, and arithmetic coerces a numeric string: the answer
-- is 13 rather than JavaScript's 121. Same mistake, different wrong number, no message from either
-- one.
Luau.luau
--!strict
local digits: string = "12"
local count: number = digits
-- luau-analyze:

-- m.luau(3,23): TypeError: Type 'string'
-- could not be converted into 'number'

-- The check belongs to the analyzer, not to the interpreter: drop --!strict from line one and the
-- same file runs and prints 13.
Nim.nim
let digits: string = "12"
let count: int = digits
echo count + 1
# nim c:

# m.nim(2, 18) Error: type mismatch
#   got <string>
#   but expected 'int'

# Angle brackets for what arrived, quotes for what was wanted, and the two kept on separate lines
Objective-C.m
NSString *digits = @"12";
int count = digits;
NSLog(@"%d", count + 1);
// clang:

// m.m:2:9: error: incompatible pointer
// to integer conversion initializing
// 'int' with an expression of type
// 'NSString *'

// The C diagnosis with a better type name on the right of it: a pointer is still what it thinks the
// mistake is
Perl.pl
use strict;
use warnings;
my $digits = "12";
my $count = $digits;
print $count + 1;
# perl 5.34.0:

# 13

# strict and warnings are both on and neither has anything to say. A scalar holds whatever you put
# in it, and arithmetic reads it back as a number.
PHP.php
declare(strict_types=1);
class Row { public int $count; }
$digits = "12";
$row = new Row();
$row->count = $digits;
# php 8:

# PHP Fatal error: Uncaught TypeError:
# Cannot assign string to property
# Row::$count of type int in m.php:5

# A bare $count would have been checked
# by nothing at all -- the declaration had to live on a property to exist --
# and it is still a fatal at run rather
# than a refusal before one.
Python.py
digits: str = "12"
count: int = digits
print(count + 1)
# CPython 3.13.5:

# Traceback (most recent call last):
#   File "m.py", line 3, in <module>
#     print(count + 1)
#           ~~~~~~^~~
# TypeError: can only concatenate str
# (not "int") to str

# The annotation on line 2 is real to a reader and invisible to CPython, so the failure names line
# 3. mypy is the package that refuses line 2.
Ruby.rb
digits = "12"
count = digits
puts count + 1
# ruby 3:

# m.rb:3:in `+': no implicit conversion of Integer into String (TypeError)

# ruby -c answers "Syntax OK" for this file. Both types are in the message, and the line it names is
# the use, not the assignment. Sorbet or an RBS signature is what moves it earlier.
Rust.rs
fn main() {
    let digits: String = String::from("12");
    let count: i32 = digits;
    println!("{}", count + 1);
}
// rustc 1.91.1:

// error[E0308]: mismatched types
//  --> m.rs:3:22
//   |
// 3 |     let count: i32 = digits;
//   |                ---   ^^^^^^ expected `i32`, found `String`
//   |                |
//   |                expected due to this
//
// error: aborting due to 1 previous error
//
// For more information about this error,
// try `rustc --explain E0308`.

// It underlines two spans rather than one: the value that is wrong, and the annotation that made it
// wrong.
Scala.scala
val digits: String = "12"
val count: Int = digits
println(count + 1)
// scalac 3:

// -- [E007] Type Mismatch Error:
//    m.scala:2:17
//    Found:    (digits : String)
//    Required: Int

// Found carries the value's own name beside its type, which is the part that helps when any of
// three things on the line could have been wrong
Swift.swift
let digits: String = "12"
let count: Int = digits
print(count + 1)
// swiftc:

// m.swift:2:18: error: cannot convert
// value of type 'String' to specified type 'Int'

// "specified" is doing real work: it
// says the Int came from an annotation someone wrote, not from inference.
TypeScript.ts
const digits: string = "12";
const count: number = digits;
console.log(count + 1);
// tsc:

// m.ts:2:7 - error TS2322: Type 'string'
// is not assignable to type 'number'.

// The check belongs to tsc and to nothing downstream: the emitted JavaScript is the JavaScript
// card, and node prints 121 from it whether or not tsc ever ran.
Verse.verse
digits : string = "12"
count : int = digits
Print("{count + 1}")
# The Verse compiler refuses before the project builds, with a numbered script
# error naming the expected type and the
# type it found.

# (Described, not quoted: this is a
# closed toolchain no run behind this page exercised, so the card states what the message carries
# rather than reproducing wording it cannot check.)
Zig.zig
const digits: []const u8 = "12";
const count: i32 = digits;
std.debug.print("{}", .{count + 1});
// zig build-exe:

// m.zig:2:20: error: expected type
// 'i32', found '[]const u8'

// Expected first, found second, and the found type is the true one: a string literal in Zig is a
// slice of bytes, and the message says that rather than saying "string"
The messageTridentCC#C++CutlassDartElixirGoHaskellJaiJavaJSJuliaKotlinLuaLuauNimObjective-CPerlPHPPythonRubyRustScalaSwiftTypeScriptVerseZig
When it arrivesCompileCompileCompileCompileCompileCompileRunCompileCompileCompileCompileNeverRunCompileNeverCompileCompileCompileNeverRunRunRunCompileCompileCompileCompileCompileCompile
Points at the assignment, not a later lineYesYesYesYesYesYesNoYesYesYesYesNoYesYesNoYesYesYesNoYesNoNoYesYesYesYesYesYes
Names both typesYesThe pointerYesYesYesNoYesYesYesYesNoYesYesNoYesYesThe pointerNoYesYesYesYesYesYesYesYesYes
Names the edit to write hereYesNoNoNoYesNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNoNo
A code to look upnone needed-Wint-conversionCS0029nonenone (the area word, check)invalid_assignmentnonenoneGHC-83865nonenonenonenonenonenonenonenonenonenonenonenonenoneE0308E007noneTS2322numberednone

The strongest answer , a weaker one , and the answer that costs you . The last row is left uncolored on purpose: a code is a handle on a message, not a grade of one, and Trident has none because the message it writes is already the whole answer.

51

Selected designs and implementation boundaries

The table separates language syntax, native functions, catalog exports, runtime admission, and Host workflows.

The right column names each remaining boundary.

DesignSelected behaviorCurrent boundary
Structural records and shared declarationsAn outline requires fields and preserves extras. An ingot is an exact, immutable record with source declarations. Canonical field types and constraints define structural identity.Outline declarations, include/check rows, module reaches, and library outline imports now have source evidence.
Value guarantees and explicit preparationA source write prepares a private value, checks its declared rules, then publishes it. A received or stored value is validated without repair.The owning constraint rows retain wider integration and proof.
Physical constants with provenanceThe shared catalog separates exact definitions, measured values, and derivations. Each value carries its uncertainty and source identity.Native exact values work. Exact stored digits do not establish physical exactness.
Color conversion, blending and gamut mappingConversion names the destination space. A blend states its space and hue rule. Gamut mapping is explicit. HDR crossings state reference-white facts.Keel conversion and blend bodies are live. Gamut mapping has focused native proof.
Typed chemical and nuclear identitiesElement, particle, nuclide, and composition types keep identity separate from uncertainty and source edition.Native vocabulary and schemas exist. Existing numerical chemistry calls prove a narrower API.
Runtime frame identities from the store's mintA runtime frame has a durable issuer scope and a nonzero instance identity. A world derives its scope from an issued parent.The frame kind, instance constructor, and derived issuer have native evidence.
Privacy-qualified values and key custodySecret values reject ordinary sinks. Sensitive values reject log sinks. Named key handles and explicit declassification control later boundaries.Source qualifiers and flow checks work.
Cohesive tapes and shareable replayAn exact tape stays in its trust domain. Export re-wraps it for an admitted recipient or creates a redacted journal view. A shareable replay executes no source.Recording and replay primitives exist.
Visual debugging without target interferenceA separate debugger observes copied state. Target authority stays separate from debugger authority.Statement stops, step, capture, breakpoints, and fault stops exist in runtime/Host scopes.
Installed builds and digest-pinned replayThe launcher selects a local installed build. Later install commands add builds and verify update trust.It proves no shipped version manager, new command syntax, or public release channel.
Forked runtimes and fleet dutiesA fork requests a runtime of the current deployment. A duty body runs only while its runtime holds that duty. Both have explicit admission rules.
Application roles, send and handleServer and client roots restrict source reach. A send names a handle with a signature checked across both builds.Positional reservations alone prove no working statement.
Typed retained message busesA bus declares retained topics. A subscription records one reader's progress. An immutable snapshot gives a handler a read-only message view.Store stream records exist.
The transaction blockA transaction stages writes and commits them once. A commit conflict runs its failure arm. Its own writes overlay current reads.Reserved spelling proves no executable transaction.
Almanacs and typed migration stepsAn almanac declares permanent named members and typed properties. A retired member remains readable but cannot be constructed as a new value.Source declarations, module reaches, properties, and retired-member rules work.
Phrase declarationsA phrase call builds a message identity and arguments. The viewer's language renders the finished text.
Declared jobs and declared endpointsA declared job states its schedule and retry policy. A declared endpoint derives a route and checks typed parameters before its handler runs.On job and on request are current handler forms.
Slabs: retained per-tick stateA slab retains fixed-width typed state across ticks. The declaration states its capacity and client visibility.
Realms, access and security labelsA realm types handler authority. An access rule controls reads and writes. A security label grants no authority by itself.Native primitives do not establish the full source and Host workflow.
Lunar and Martian timeTCL names lunar coordinate time. Darian labels use a Martian calendar and sol. Named maps must state their time relations.Carrier vocabulary is live.
52

Observation declarations and metric classes

An observation declaration describes metrics from one supplied record. The compiler checks each field path, metric bit, complete value type, unit, reducer set, and scope. The declaration adds no callback or sampling loop.

A reading is a reading at one moment. A tally is a cumulative total with an explicit u64 generation. The application changes that generation whenever the total restarts. A changed generation starts a new segment; a decrease under the same generation is a gap.

Publisher and dimension fields identify the reading. The Host supplies runtime, activation, and collection context. These metrics describe the application; they do not charge runtime work. Histogram, balance, and delta rows compile. The receiver contracts that read a completed call's rows still need implementation.

Five metric classes: reading, tally, balance, histogram, delta

The compiler accepts reading, tally, balance, histogram, and delta. Each class has one head and one reducer domain. Old heads and type names refuse; there are no aliases. Declaration metadata works.

The compiler accepts reading, tally, balance, histogram, and delta. Each class has one head and one reducer domain. Old heads and type names refuse; there are no aliases. Declaration metadata works.

Normal updates: current values, running totals, cumulative buckets, and separate batches have different rules.

reading

A value at one moment.

reading exampleLatest reading: 14. Maximum: 14. A fall from 12 to 9 is valid. Values: 12, 9, 14.129140s1s2s
Latest reading: 14. Maximum: 14. A fall from 12 to 9 is valid.
Value types
Numeric values: i64, u64, f64, or duration. Other supported complete values allow last only.
Source fields
value, dimensions, optional unit, reducers, scopes
Updates and restarts
No generation field. A new reading replaces the current value.
Allowed answers
Compatible last, minimum, maximum, mean, or integral reducers. Closed values allow last only. Domain: readingReducers.
History result type
observationHistoryNumericReadingResult Closed last-only values use observationHistoryLastResult.
Declaration and support

The declaration and reducer domain compile. Class-specific history support is described below.

reading OrderMetrics.active {
    value = supplied.active
    dimensions = supplied.dimensions
    unit = "1"
    reducers = readingReducers.last | readingReducers.maximum
    scopes = observationScopes.self
}

Supplied fields: active: u64. Use the shared publisher and dimensions pattern from the complete producer example. Add these fields and a distinct live flagset member to its record and declaration.

Naming law and type mapping; implementation owner.

tally

A cumulative total that only rises within one generation.

tally exampleOne generation: change +5 over 2 seconds, or 2.5 per second. Values: 100, 102, 105.1001021050s1s2s
One generation: change +5 over 2 seconds, or 2.5 per second.
Value types
One nonnullable scalar u64 or duration. The total cannot be negative.
Source fields
value, generation, dimensions, optional unit, reducers, scopes
Updates and restarts
A separate u64 generation marks a restart. A fall without a new generation is a gap.
Allowed answers
total, change, rate. Domain: tallyReducers.
History result type
observationHistoryTallyResult
Declaration and support

The declaration and reducer domain compile. Class-specific history support is described below.

tally OrderMetrics.placed {
    value = supplied.placed
    generation = supplied.placedEpoch
    dimensions = supplied.dimensions
    unit = "1"
    reducers = tallyReducers.total | tallyReducers.rate
    scopes = observationScopes.self
}

Supplied fields: placed: u64; placedEpoch: u64. Use the shared publisher and dimensions pattern from the complete producer example. Add these fields and a distinct live flagset member to its record and declaration.

Naming law and type mapping; implementation owner.

balance

A signed cumulative total that can rise or fall.

balance exampleOne generation: net change +1. The fall from 10 to 7 contributes −3. Values: 10, 7, 11.107110s1s2s
One generation: net change +1. The fall from 10 to 7 contributes −3.
Value types
One nonnullable scalar i64 or duration. Negative values are valid.
Source fields
value, generation, dimensions, optional unit, reducers, scopes
Updates and restarts
A separate u64 generation marks a restart. A fall in one generation is valid change.
Allowed answers
total, change, rate. Domain: balanceReducers.
History result type
observationHistoryBalanceResult
Declaration and support

The declaration and reducer domain compile. Class-specific history support is described below.

balance OrderMetrics.openOrders {
    value = supplied.openOrders
    generation = supplied.placedEpoch
    dimensions = supplied.dimensions
    unit = "1"
    reducers = balanceReducers.total | balanceReducers.change
    scopes = observationScopes.self
}

Supplied fields: openOrders: i64; placedEpoch: u64. Use the shared publisher and dimensions pattern from the complete producer example. Add these fields and a distinct live flagset member to its record and declaration.

Naming law and type mapping; implementation owner.

histogram

Cumulative bucket counts and a sum.

histogram exampleCurrent cumulative buckets: 12 values in all. Count comes from the five buckets. Values: 2, 5, 4, 1, 0.25410<1ms1–10ms10–100ms100ms–1s≥1s
Current cumulative buckets: 12 values in all. Count comes from the five buckets.
Value types
Fixed u64[N+1] buckets. The sum is a scalar u64, i64, f64, or duration. N bounds use the sum type.
Source fields
buckets, sum, bounds, generation, dimensions, optional unit, reducers, scopes
Updates and restarts
A separate u64 generation marks a restart. Count is derived from the buckets.
Allowed answers
count, sum, buckets, quantiles. Domain: histogramReducers.
History result type
observationHistoryHistogramResult
Current declaration and support

This head and reducer domain compile.

histogram OrderMetrics.latency {
    buckets = supplied.latencyBuckets
    sum = supplied.latencySum
    bounds = latencyBounds
    generation = supplied.placedEpoch
    dimensions = supplied.dimensions
    unit = "ms"
    reducers = histogramReducers.count | histogramReducers.buckets
    scopes = observationScopes.self
}

Supplied fields: latencyBuckets: u64[5]; latencySum: duration; placedEpoch: u64. Module constant: const duration[] latencyBounds = [1ms, 10ms, 100ms, 1s]. Use the shared publisher and dimensions pattern from the complete producer example. Add these fields and a distinct live flagset member to its record and declaration.

Naming law and type mapping; implementation owner.

delta

Activity since the previous completed call.

delta exampleThree completed calls supply separate batches: 2 + 0 + 3 = 5. Zero is a real batch. Values: 2, 0, 3.203call 1call 2call 3
Three completed calls supply separate batches: 2 + 0 + 3 = 5. Zero is a real batch.
Value types
A u64 count; an optional sum has type u64, i64, f64, or duration. A distribution uses u64[N+1] buckets and N typed bounds.
Source fields
Count form: count, optional sum. Distribution form: buckets, sum, bounds. Both add dimensions, optional unit, reducers, scopes.
Updates and restarts
No generation field. Each call supplies its own batch. Zero is valid; a missing batch is unknown.
Allowed answers
count, sum, distribution; the last two need a sum. Domain: deltaReducers.
History result type
observationHistoryDeltaResult
Declaration and support

The delta declaration and reducer domain compile. Count-only, count with a sum, and distribution forms work.

delta OrderMetrics.placed {
    count = supplied.placedSinceLastCall
    dimensions = supplied.dimensions
    unit = "1"
    reducers = deltaReducers.count
    scopes = observationScopes.self
}

Supplied fields: placedSinceLastCall: u64. Use the shared publisher and dimensions pattern from the complete producer example. Add these fields and a distinct live flagset member to its record and declaration.

Naming law and type mapping; implementation owner.

Delta distribution form
delta OrderMetrics.placed {
    buckets = supplied.placedBuckets
    sum = supplied.placedValue
    bounds = [10.0, 100.0, 1000.0]
    dimensions = supplied.dimensions
    unit = "1"
    reducers = deltaReducers.count | deltaReducers.sum | deltaReducers.distribution
    scopes = observationScopes.self
}

Here placedBuckets is u64[4] and placedValue is f64. Count is derived from buckets. This form has no authored count path and no generation field.

The numbers are illustrative. Line charts use one-second intervals; delta bars show completed calls. Histogram bars show a snapshot, with each lower bound included in its bucket. A gap here means continuity has already failed under the collection policy. A missed intermediate cumulative snapshot alone need not break continuity.

Each snippet is a separate example. The tally and delta snippets both use OrderMetrics.placed; one observation cannot declare that member twice. All fields above have checked complete types. Bounds are nonempty and strictly increasing. Duration bounds count whole nanoseconds, even when the display unit is ms. A completed call can publish at most one row for each metric member, source, publisher, and dimension series. A delta supplies data; on event still runs a declared delivery handler.

How the app supplies, publishes, stores, and reads observations; accepted compiler spelling examples; full declaration rules.

Metric classes: canonical names

The owner decisions of October 14 settle balance, histogram, and delta rows, and all three heads compile under their current spellings. Existing native reducers and publisher descriptors are separate working features.

ClassMeaning and value shapeReducers
ReadingA reading at one moment, such as current memory use. The complete type fixes the permitted numeric or closed value family.Compatible members of readingReducers, such as last and maximum.
Monotonic tallyA nonnullable scalar u64 or duration running total with a separate u64 generation. A fall under the same generation is a gap.tallyReducers selects compatible total, change, and rate answers.
Signed balanceA running i64 or duration total may rise or fall. A separate u64 generation marks a restart. A fall is signed change, not a reset.The balanceReducers domain has total, change, and rate. Rate is net signed change divided by covered duration.
HistogramA cumulative distribution has a fixed u64[N+1] bucket group, a sum path, a u64 generation, and N fixed bounds. Count is derived from the buckets.histogramReducers has count, sum, buckets, and quantiles. Quantiles are bucket-bounded estimates.
Delta metricOne completed call's accepted publication is one delta batch for each series. A zero-count batch is valid. A u64 count may stand alone or beside a sum; a distribution uses a sum, fixed bounds, and buckets, and derives its count from the buckets.deltaReducers has count, sum, and distribution. Sum without a sum path, and distribution without buckets, refuse at compile.

A delta metric summarizes activity. on event is a handler that runs application code for a named delivery. These are separate forms.

Open the approved bounds, batch, and balance rules
  • Histogram bounds are a literal list or a module constant at the sum path's type. Bounds must be nonempty, finite, strictly ascending, and exactly representable in binary64.
  • A duration bound projects to its whole count of nanoseconds, its coefficient at scale 9. A duration value carries its own scale, so the nanosecond is the one step every bound shares. This keeps ordinary millisecond bounds exact.
  • A value equal to a bound enters the bucket that starts at that bound. Values below the first bound and at or beyond the last bound use the outer buckets.
  • The fixed bucket-group size and Host histogramCells capacity supply the bounds.
  • A histogram is cumulative; a delta metric is delta. The class word supplies temporality, so no separate temporality property is authored.
  • Each completed call supplies one delta batch. A refused publication loses its batch; collection validation must account for that boundary.
  • A signed balance admits i64 and duration, not f64. Its change is exact signed i128. Real declared sums use the delta reduction lane.

Owner decisions and row shapes; signed fold owner. All five declarations compile.

Code examples with canonical names

Each example shows one row inside an observation declaration. The complete current example below supplies the reading, tally and histogram fields, metric members, source label, and publisher.

Every row below uses the existing declaration case, which compiles and runs in the language suite. All five declaration heads compile under their selected names.

Reading.tri
reading OrderMetrics.active {
    value = supplied.active
    dimensions = supplied.dimensions
    unit = "1"
    reducers = readingReducers.last | readingReducers.maximum
    scopes = observationScopes.self
}

The supplied record has active: u64 and a dimensions record. This row describes a current reading. Source.

Tally.tri
tally OrderMetrics.placed {
    value = supplied.placed
    generation = supplied.placedEpoch
    dimensions = supplied.dimensions
    unit = "1"
    reducers = tallyReducers.total | tallyReducers.rate
    scopes = observationScopes.self
}

The supplied record has placed: u64 and placedEpoch: u64. Change placedEpoch whenever the total restarts. Source.

Balance.tri
balance OrderMetrics.openOrders {
    value = supplied.openOrders
    generation = supplied.placedEpoch
    dimensions = supplied.dimensions
    unit = "1"
    reducers = balanceReducers.total | balanceReducers.change
    scopes = observationScopes.self
}

The supplied record has openOrders: i64 and placedEpoch: u64. A change from 12 to 9 under one generation contributes -3. Source.

Histogram.tri
histogram OrderMetrics.latency {
    buckets = supplied.latencyBuckets
    sum = supplied.latencySum
    bounds = latencyBounds
    generation = supplied.placedEpoch
    dimensions = supplied.dimensions
    unit = "ms"
    reducers = histogramReducers.count | histogramReducers.buckets
    scopes = observationScopes.self
}

The supplied record has latencyBuckets: u64[5] and latencySum: duration, and the module states const duration[] latencyBounds = [1ms, 10ms, 100ms, 1s]. Four bounds need five buckets, and each duration bound counts whole nanoseconds. Count comes from the bucket sum. Source.

Delta.tri
delta OrderMetrics.refunds {
    count = supplied.refunds
    sum = supplied.refundAmount
    dimensions = supplied.dimensions
    unit = "1"
    reducers = deltaReducers.count | deltaReducers.sum
    scopes = observationScopes.self
}
delta OrderMetrics.refundSizes {
    buckets = supplied.refundSizes
    sum = supplied.refundAmount
    bounds = [10.0, 100.0]
    dimensions = supplied.dimensions
    unit = "1"
    reducers = deltaReducers.distribution
    scopes = observationScopes.self
}

The supplied record has refunds: u64, refundAmount: f64, and refundSizes: u64[3]. These describe one call's batch, rather than a lifetime total. Source.

The second delta selects a distribution, so it supplies a sum, bounds, and buckets, and its count comes from the buckets. A count-only delta omits the sum, and it cannot select the sum or distribution reducers.

Native reducers and library helpers

Keel already has caller-owned tally, signed balance, reading, distribution, and delta state. Their step and merge entries retain counts, sums, duration, and coverage. They acquire no sample, allocate no storage, and activate no collection policy.

Signed sums inside distribution and delta state differ from the cumulative balance fold. Keel now supplies that separate fold through generation-aware clear, step, merge, and rate entries, and the application declaration and its reducer domain compile.

t.telemetry places equality in the bucket that starts at the bound.

Keel reducer law; current public reducer entries; native job descriptors; current histogram and delta reducer members.

Open the current reading and tally source example
outline Publisher { text shard = "" }
outline Dimensions { text region = "" }
outline Publication {
    Publisher publisher = { shard = "" }
    u64 active = 0
    u64 resident = 0
    u64 placed = 0
    u64 placedEpoch = 0
    Dimensions dimensions = { region = "" }
}
flagset OrderMetrics { active = 1, resident = 2, placed = 4 }
observation orders(Publication supplied) {
    source = "orders"
    publisher = supplied.publisher
    reading OrderMetrics.active {
        value = supplied.active
        dimensions = supplied.dimensions
        unit = "1"
        reducers = readingReducers.last | readingReducers.maximum
        scopes = observationScopes.self
    }
    reading OrderMetrics.resident {
        value = supplied.resident
        dimensions = supplied.dimensions
        unit = "byte"
        reducers = readingReducers.last
        scopes = observationScopes.self
    }
    tally OrderMetrics.placed {
        value = supplied.placed
        generation = supplied.placedEpoch
        dimensions = supplied.dimensions
        unit = "1"
        reducers = tallyReducers.total | tallyReducers.rate
        scopes = observationScopes.self
    }
}

Current declaration case; source law; collection design. The case proves metadata declarations, not a full Host collection run.

QuestionTrident observationOpenTelemetry Metrics API
How is a metric introduced?A module declaration fixes its member, class, paths, unit, reducers, and scopes before execution.A Meter creates instrument objects through library calls with names, units, and descriptions.
Where does the value come from?Checked paths read one admitted supplied document. The declaration contains no executable function.Synchronous calls record values; asynchronous instruments use callbacks that report observations.
Which forms are available?Reading, monotonic tally, cumulative histogram, signed balance, and delta delta source rows work under their current spellings.The API specifies counters, gauges, histograms, UpDownCounters, and asynchronous forms.
Can a total rise and fall?The signed balance row reads a cumulative total and explicit generation, and Keel's signed fold reduces it.UpDownCounter adds positive or negative changes. Asynchronous UpDownCounter reports absolute additive values through a callback.
How is a distribution supplied?The histogram row reads cumulative fixed buckets and a sum from the supplied record.A Histogram records individual measurements through library calls.
How complete is the pipeline?Compiler metadata and readers have proof.The API separates instruments from SDK collection and export.

Comparison source: OpenTelemetry Metrics API. This compares declaration and API shape; it gives no throughput or telemetry-maturity ranking.

53

Guarded reads: expect and conditional checks

The older field-read head guard is now spelled expect. expect { Type field,... } = source else {... } checks every named field before it binds any. A missing field or wrong complete type takes the failure arm without a mismatch fault.

The failure arm must leave. Every path that reaches the next statement therefore has all the declared bindings. guard {... } is not an alias; that old statement form refuses. A function or local may still use guard as an ordinary name.

Trident: all fields or the leaving arm.tri
text main(text[] argv) {
    chest m = { i32 amount = 7, text label = "k" }
    expect { i32 amount, text label } = m else {
        return "arm"
    }
    return label + t.text.of(amount)
}

Current field-list case; old-head refusal case.

Trident: a check guarded by a selector.tri
enum Status { Open = 1, Paid = 2 }
outline Invoice {
    Status status = Status.Open
    i64? paidAt = null
    check paid = if (status == Status.Paid) present(paidAt)
}

Closed check-row law. A Paid record needs a present payment time. The clause runs no helper function.

A guarded check is a record rule, not a guarded read. Its selector decides when a closed clause applies. It remains part of the record's complete type and is enforced at the owning admission and publication boundaries.

FormWhat is checked?What happens on failure?After success
Trident expect field listNamed record fields at complete declared types.The required arm leaves; no partial set of bindings is published.All named bindings are available in the enclosing scope.
Swift guardA condition, optional bindings, or pattern conditions.The required else transfers control outside the enclosing scope.Successful bindings remain available in that scope.
Rust let-elseA pattern over an already typed value. Document decoding is a separate step.The else block must diverge.The matching pattern's bindings are available.
TypeScript type guardA condition or a user-written predicate can narrow a static type.The branch decides how to proceed; there is no required leaving arm.Control-flow analysis narrows the value in the applicable path.

Sources: Swift control flow; Rust let statements; TypeScript narrowing. These forms share an early-check purpose but validate different inputs.

54

Conditional results with use

An if or when can produce a value. Each arm writes use expression at the destination's complete type. The last expression of a block is not an implicit result.

A value if needs else. A value when must cover its enum or have else. An arm that reaches its end without a result refuses. Only the selected arm executes.

Trident: the other arm never runs.tri
text main(text[] argv) {
    i32[] xs = []
    i32 at = 0
    i32 v = if (true) { use 4 } else { use xs[at] }
    return t.text.of(v)
}

Rust uses expression-valued blocks, including if and match. Swift supports conditional expressions with its own branch rules. Trident uses an explicit use and takes the complete result type from a declared destination.

Sources: Rust block expressions; Swift expressions.

An exhaustive when produces a declared result.tri
enum Mode { Quiet = 1, Loud = 2 }
text main(text[] argv) {
    Mode mode = Mode.Quiet
    text label = when (mode) {
        is Mode.Quiet { use "quiet" }
        is Mode.Loud { use "loud" }
    }
    return label
}

Conditional result law.

Other source features to check

These source forms deserve separate mention. A source rule and a completed Host workflow are separate claims.

FormCurrent ruleOwning source
System observation enums and flagsetsCPU, process, storage, scope, reducer, availability, and result domains have distinct types derived from their owners. Equal bits do not make equal domains.Source case
when unguardedA partial enum selection may omit members and use else. Duplicate members still refuse. The word is contextual, not globally reserved.Selection rule
retired enum armAn exhaustive enum selection covers its live members and one retired arm. Saved members remain readable; new value construction refuses.Member rule
Lock on a function signatureOne lock clause protects the body and postconditions. Preconditions run before acquisition. Returns and faults release the held grant.Clause rule
Sorted outline layersAn array of outline values can sort by a declared field path or path tuple. Nullable keys state their null order; strict requires unique keys.Layer rule
Typed named groupsGroups such as measurement.estimate and sphere.radius derive complete types from their receiver. The group is read-only and needs no destination to define its type.Group rule
An outline layer keeps its declared key order.tri
outline score { text name = "", i32 points = 0 }
text main(text[] argv) {
    score[]<sort=points> board = [
        { text name = "b", i32 points = 3 }, { text name = "a", i32 points = 1 }]
    return board[0].name
}
# A path tuple is sort=(points,name).
# Nullable keys also state nulls=first or nulls=last.

Source case.

A signature lock releases when the call ends.tri
i64 guarded(text key, chest p) lock (key, p) {
    return t.sync.held().count() + 10
}
text main(text[] argv) {
    chest p = t.sync.policy.exclusive(0ms, 1s, true)
    i64 inside = guarded("example:guard", p)
    return text(inside) + "/" + text(t.sync.held().count())
}
# 11/0: one held inside, none after the call.

Source case.

A named group gets its type from the receiver.tri
# Function excerpt: no destination is needed for the group.
f32<family=length> radiusOf(sphere<frame=builtin:itrs> shape) {
    return shape.radius
}
# radius has the receiver's complete scalar type. The group is read-only; it is not a dictionary
# lookup.

Named group law.

55

Complete observation example: producer, history, and consumers

The application owns the supplied values. The compiler owns the metric declaration and checked paths. Host owns admission and collection; Keel owns reductions; Store owns persistence. Readers consume qualified pages rather than reading application variables.

  1. Application updates a record
  2. Call returns publication rows
  3. Host admits and stamps them
  4. Keel folds live state
  5. Store confirms history
  6. Reader returns a typed page

1. A complete supplied record and producer

The standalone program below updates two counts, builds one source/document row, and reads the resulting document back. Every structural-record helper is private.

The standalone main inspects the data and returns text; it sends nothing to a collector.

Complete producer and local demonstration.tri
outline Publisher { text shard = "" }
outline Dimensions { text region = "" }
outline Publication {
    Publisher publisher = { shard = "" }
    u64 active = 0
    u64 resident = 0
    u64 placed = 0
    u64 placedEpoch = 0
    Dimensions dimensions = { region = "" }
}
flagset OrderMetrics { active = 1, resident = 2, placed = 4 }
observation orders(Publication supplied) {
    source = "orders"
    publisher = supplied.publisher
    reading OrderMetrics.active {
        value = supplied.active
        dimensions = supplied.dimensions
        unit = "1"
        reducers = readingReducers.last | readingReducers.maximum
        scopes = observationScopes.self
    }
    reading OrderMetrics.resident {
        value = supplied.resident
        dimensions = supplied.dimensions
        unit = "byte"
        reducers = readingReducers.last
        scopes = observationScopes.self
    }
    tally OrderMetrics.placed {
        value = supplied.placed
        generation = supplied.placedEpoch
        dimensions = supplied.dimensions
        unit = "1"
        reducers = tallyReducers.total | tallyReducers.rate
        scopes = observationScopes.self
    }
}

# The caller supplies counts from a confirmed business operation.
private Publication afterOrders(Publication previous, u64 placedCount, u64 closedCount) {
    Publication next = previous
    u64 total = next.placed
    u64 active = next.active
    total += placedCount
    active += placedCount
    active -= closedCount
    next.placed = total
    next.active = active
    return next
}

# Build ordinary return data. Host publication of this envelope is pending.
private chest publicationResult(u64 compositionGeneration, Publication snapshot) {
    chest row = {}
    row.source = "orders"
    row.document = snapshot
    chest[] rows = [row]
    chest[] none = []
    chest? noDocument = null
    chest out = {}
    out.generation = compositionGeneration
    out.sceneUpdate = noDocument
    out.transitions = none
    out.messages = none
    out.uiEdits = none
    out.platformRequests = none
    out.childRequests = none
    out.captureRequests = none
    out.hapticRequests = none
    chest[]? nullRows = null
    out.openDocuments = nullRows
    out.textSources = nullRows
    out.state = snapshot
    out.responses = none
    out.clockRequest = noDocument
    out.renderDeclaration = noDocument
    out.programRequest = noDocument
    out.observationPublications = rows
    return out
}

text main(text[] argv) {
    Publisher publisher = { shard = "orders-1" }
    Dimensions dimensions = { region = "US" }
    Publication before = {
        publisher = publisher
        active = 12
        resident = 4096
        placed = 100
        placedEpoch = 1
        dimensions = dimensions
    }
    Publication next = afterOrders(before, 2, 0)
    # A real session supplies its admitted composition generation.
    chest result = publicationResult(1, next)
    chest[] rows = result.observationPublications
    chest row = rows[0]
    text source = row.source
    chest document = row.document
    u64 active = document.active
    u64 total = document.placed
    u64 epoch = document.placedEpoch
    return source + ": active=" + t.text.of(active) +
        ", placed=" + t.text.of(total) +
        ", generation=" + t.text.of(epoch)
}

Executed with C:\MerTekProto\build\tricli.exe, version 1.0.1, using --config none --wall 10s. It returned orders: active=14, placed=102, generation=1 with exit status zero. This verifies data construction and reads, not Host publication.

Current declaration case; supplied-record law; result fields; publication owner.

2. What the application changes, and when

A real caller uses afterOrders only after its business operation has a confirmed outcome. Submission alone does not prove that an order exists. The demonstration supplies an example batch; it performs no business Store write.

Metric classApplication updatePublication meaning
ReadingRefresh active from accepted opens/closes; refresh resident from the application's actual supplied measurement.The supplied current value replaces the prior last reading. Maximum describes observed samples only.
TallyIncrease placed for confirmed placements. Change placedEpoch whenever placed restarts.The first admitted reading establishes a baseline. Compatible later totals produce change and rate.
BalanceUpdate an i64 or duration running total that may rise or fall. Keep a separate u64 generation.A fall is a signed change. The application row and its reducer domain compile, and Keel's native signed fold reduces the readings.
HistogramIncrease the bucket for each observation and add its typed amount to the cumulative sum. Change its generation on restart.Fixed bounds define the bucket population. Count is derived from the buckets; compatible cumulative images are differenced.
Event metric sourceBuild fresh count/sum/bucket fields for each completed call's batch. Zero-count batches are valid.Each accepted publication is one delta batch. A refused publication loses that batch; it is not an instruction to repeat the business operation.

The declaration's supplied is a compile-time path root. In the running example, row.document = snapshot supplies the actual record. No callback, field watcher, implicit variable scan, or call to a fictional emission API is needed.

The retained state belongs to the session. It survives only accepted session results and is not a durable business table. A restart or explicit counter reset needs the application's new tally generation. Composition generation, tally generation, and collection epoch are separate facts.

3. Trove definitions and durable records

Publication is an in-memory outline, so it needs no trove of its own. An application that stores orders can declare a business trove as shown below. Its install, keys, transaction, and authority follow the ordinary application Store path.

Business trove: selected schema-file design.tri
# Application schema placement: _db/troves/order.tri
trove order {
    text region = ""
    bool open = true
    u64 amountCents = 0
}

Trove source placement and defaults.

The order trove holds business facts. Metric history is a separate Host-owned use of ordinary protected Store records. The history owner still owes the exact trove declarations and indexes. The table states required record roles, without inventing final trove names or a second schema.

Record roleRequired retained factsWho updates it?
Source schema and policyThe admitted compiled declaration, its types and units, and exact stored schema/policy version references.Host installs or activates the admitted configuration through the catalog and normal schema path.
Raw chunksQualified readings or batches, producer/incarnation, collection epoch, sequence ranges, and clock/coverage evidence.Host submits admitted chunks; Store confirms them through ordinary transactions.
RollupsTyped series, tier/window/revision, sufficient statistics, consumed ranges, and referenced raw or boundary evidence.Host publishes Keel's state together with progress and evidence references.
Partition anchor and progressExact prior row version, owner generation, reserved capacity, consumed ranges, and publication progress.The fenced collection owner updates it conditionally. A stale owner cannot advance it.
Retention stateOriginal admission deadlines, per-tier logical cutoffs, clock watermark, and maintenance progress.Bounded Store maintenance expires and reclaims confirmed records under the admitted policy.

History and identity law; open history-schema and publication owner; retention owner.

4. The technical update sequence

WhenOperationVisible result
Compile and installCompile each declaration's source, member, class, complete types, units, paths, reducers, and scopes. Install immutable program metadata.Changing a payload value changes no declaration. A source or shape change needs a new admitted program/configuration.
Host selects declared sources and admits cadence, gaps, raw retention, rollup tiers, capacity, and audience. Collection starts disabled when no policy selects it.The resolved configuration and collection epoch fence later readings. App policy only narrows Host grants.
During an application callThe app changes its private next state and constructs publication rows. Pure t.telemetry helpers may compute documents.No observation sink is called by this declaration or result factory.
Host selects the source declaration, checks the supplied document and current authority, reads retained paths, and stamps actual call/runtime/configuration/clock evidence.Host admits the reading or reports refusal/loss. Returning data is separate from queue acceptance or Store confirmation.
The collector performs bounded native acquisition and reduction. A stream's every is snapshot cadence or event-summary flush cadence. Missed deadlines skip periods.Live summaries keep coverage and gaps. An application's data changes only when its own logic changes and publishes it.
One ordinary sealed transaction publishes a rollup revision, consumed progress, and evidence references. An unknown outcome resolves the original transaction.Readers follow confirmed publication and exact versions. An uncertain delta is never resubmitted as a fresh transaction.
Raw and each rollup tier expire independently from their original admission time. Revisions and retries do not restart the deadline.Logical cutoff denies expired history even when physical pins delay reclaim.

An example policy could collect at 1s, allow a 3s maximum gap, keep raw evidence for 15m, and retain 1m rollups for 24h. Those are example choices, not defaults. They require Host permission and do not make the application run every second.

Policy activation; collector lifecycle; reduction and accuracy.

5. Query and consume the data

The t.host.observe.history constructor builds a request. It reads no Store and returns no page immediately. The application returns that row in platformRequests; a later call.hostResources input carries the outcome and available page, or a separate unavailable arm.

The sketch below uses the catalog's named record shapes. Collection and series identities come from admitted Host facts. The interval carries qualified clocks; neither a source label nor an invented timestamp substitutes for those records.

History request and one tally consumer.tri
# The history constructor also takes these records as chest documents. Collection, series, and interval come from admitted
# Host facts.
private chest requestOrderHistory(u64 generation, observationCollectionIdentity collection,
        observationSeriesIdentity series, observationHistoryInterval interval) {
    observationHistoryQueryBounds bounds = {
        bytes = 65536
        cursorLifetime = 30s
        rows = 128
    }
    observationHistoryResolution resolution = {
        mode = observationResolution.raw
        width = null
    }
    observationHistoryQuery query = {
        bounds = bounds
        collection = collection
        continuation = null
        exactness = observationExactness.requireExact
        interval = interval
        resolution = resolution
        series = series
    }
    return t.host.observe.history(generation, 7, query)
}

# Called for a typed tally result from an available history page.
private text tallySummary(observationHistoryTallyResult result) {
    u128 delta = result.delta
    u128 covered = result.coveredNanoseconds
    return t.text.of(delta) + " orders over " +
        t.text.of(covered) + " ns"
}

Append the returned request to the existing result's platformRequests array, then return the complete result. Dispatch the later Host outcome by its outer requestId. A successful available page exposes typed results; tallySummary reads one tally result. A page also carries collection/series identities, actual resolution, cut, policy/schema versions, completeness, gaps, and a bounded continuation.

The result keeps exact change and covered nanoseconds. The common reader derives rate from those facts. A chart draws admitted values with their interval and gap status. The renderer, OTLP sink, and probe adapter use the same protected reader.

Continuing a page is an explicit new request with its qualified cursor. historyClose closes that cursor. A stale session, changed authority, expiry, or close invalidates it. Replay serves recorded pages and performs no live collection, Store write/delete, or external export.

Query, page, and cursor law; current field catalog; landed constructors; source-projection owner; protected reader owner; probe adapter owner.

6. A concrete sequence of readings

Host-qualified timePlaced total / generationActive readingMeaning
0s100 / 112First admitted tally reading is the baseline. No prior interval exists from which to infer rate.
1s101 / 113One new order over one covered second. The reading's observed maximum becomes 13.
2s103 / 115Across the whole compatible interval, change is 3 over 2 seconds: 1.5 orders/s. Reading last and observed maximum are 15.
3s, explicit reset0 / 215A new generation establishes a new baseline; the reader does not subtract 103 from zero.
Alternate invalid reset0 / 115A falling total under the unchanged generation creates a gap. It is neither a negative rate nor proof of zero activity.

This sequence assumes one source, publisher, dimension record, runtime, configuration activation, collection epoch, compatible quality, and qualified clock. A change in that context ends continuity. A tally interval crossing a chart window stays boundary evidence unless an explicitly allowed estimate allocates it.

Glossary

A dotted word on this page has an entry here. Hover over the word, or tap it on a touch screen, to read the entry where you are; press Escape or tap elsewhere to close it. Each entry says what the word means in plain English, shows a short Trident example where code helps, and names the bug the idea prevents. The page's checks run every example and require an entry for every kind the type registry holds.

Show every entry
Resource limits

A limit bounds memory, input capacity, or time. Trident has no deterministic work budget or work price. CPU quotas belong to the operating system.

1h30m duration literal

A length of clock time written with its units, like 16ms or 1h30m, with larger units first. A month is a calendar span, not a fixed number of seconds, so 1mo refuses as a duration.

duration frame = 16ms
duration trip = 1h30m
duration total = frame + trip
duration month = 1mo   # refuses: a month is not a clock span

Bug it prevents: treating every month as 30 days, or mixing up milliseconds and seconds in a bare number.

4.0i imaginary literal

A number glued to i, like 4.0i, is the imaginary part of a complex number. 3.0 + 4.0i is an ordinary sum of a real number and an imaginary one, not a special two-part token.

complex128 z = 3.0 + 4.0i
complex128 n = -4.0i
complex128 w = z * n
5kg unit-suffixed number

A number glued to a unit, like 5kg or 9.81m/s^2, read in the unit family its destination's type names. So 5m is five meters at a length and five minutes at a time. With no family to read it in, it refuses rather than guess.

f64<mass> load = 5kg
f64<length> run = 5m    # five meters
f64<time> wait = 5m     # five minutes
f64<mass> heavy = 5m   # refuses: m is no mass unit
f64 plain = 5kg   # refuses: no family to read kg in

Bug it prevents: degrees used where radians were meant, or meters read as feet, giving a wrong number that looks fine.

# comment

A note for people reading the code. Everything from # to the end of its line is ignored. Trident has no // comment: // builds a fraction.

i32 tries = 3   # a comment runs to the end of its line
# a whole line can be a comment too
i32 left = tries
// 2   # refuses: // is no comment, so this reads as tries // 2
#! shebang line

An optional first line, #!/usr/bin/env tricli, that lets a Unix shell run the file as a program. In Trident it is an ordinary # comment, so no tool needs a special rule for it.

#!/usr/bin/env tricli
# the line above is an ordinary comment
i32 tries = 3
#{ } structured comment

Trident's one comment that can span several lines. Its body holds typed fields, like i32 since = 1, which the compiler reads and checks but never runs. Plain notes that span lines use several # lines instead.

#{ i32 since = 1
   text who = "Ada" }
i32 tries = 3

Bug it prevents: a multi-line comment left open that silently swallows the code after it. An unclosed or malformed #{ refuses.

Named type and semantic domain

A declaration such as type Email = text gives its base a distinct identity. The module path and name determine that identity unless an origin pin states it. Declarations work.

Example and current boundary.

crash

Ends the whole process at once with exit status 79. No fault handler, deferred cleanup, or on stop runs. An optional reason is one nonempty plain text literal; a computed reason refuses.

Compare crash, stop, and fail.

on start

The launch's vote before the entry runs. A bare return accepts. A returned refusal document or an uncaught fault prevents the entry from running.

Launch sequence and example.

Scene tick and timer folds

Declared handlers that receive retained state and return its next value. The scene pump passes each successful result to the next step. A fault keeps the previous state. Monotonic and session timers use on event.

Handler examples.

`…{x}…` interpolation

A text literal enclosed in backticks fills each {…} hole with a value, like `n={n}`. Each hole is converted with text(…), runs once, left to right. Formatting is a named call inside a hole, not a hidden mini-language.

i32 n = 5
text line = `n={n}`   # "n=5"
text bad = "n=" + n   # refuses: write text(n)

Bug it prevents: a number glued onto text with no visible conversion, as JavaScript's "n=" + n does.

% remainder

Gives what is left after whole-number division: 7 % 2 is 1. The sign follows the number being divided, so -7 % 2 is -1, and a == (a / b) * b + a % b always holds. Dividing by zero faults.

i32 r = 7 % 2      # 1
i32 s = -7 % 2     # -1: the sign follows -7
i32 zero = 0
i32 bad = 7 % zero   # faults: divide by zero
& | ^ ~ bit and set operators

Bit operators on whole numbers, and set operators on flagsets: & keeps what both have, | what either has, ^ what exactly one has, and ~ flips. On a flagset, ~ flips only the named members.

u8 both = 6 & 3      # 2
u8 either = 6 | 9    # 15
i32 differ = 5 ^ 3   # 6
u8 flipped = ~0      # 255
bool odd = true & false   # refuses: & takes no bool

Bug it prevents: ~ flipping all 64 bits of a flag word and switching on dozens of flags nobody named.

(a, b) lane tuple

Numbers in parentheses that fill a value's parts in order, like (1.0, 0.0, 0.0) for a 3D vector. The count must match the type: a 2D vector takes exactly two.

dvec3 p = (1.0, 0.0, 0.0)
interval lo = (0,1)
dvec2 bad = (1.0, 2.0, 3.0)   # refuses: three parts for two

Bug it prevents: a value built with a missing or extra part, which shifts every number into the wrong slot.

** power

Raises a number to a whole-number power: 2 ** 10 is 1024. The exponent must be an integer; a fractional power, like a square root, is a named library call.

i32 k = 2 ** 10      # 1024
f64 cube = 1.5 ** 3  # 3.375
f64 root = 2.0 ** 0.5   # refuses: the exponent is an integer

Bug it prevents: a negative number raised to a fractional power, which has no real answer, quietly turning into NaN.

+ - * /

Add, subtract, multiply and divide. Both sides must agree in type (two whole-number widths meet at one that holds both), and whole-number division rounds toward zero. Going past a type's limit, or dividing by zero, faults instead of wrapping around or giving infinity.

i32 total = 7 + 5 * 2   # 17
i32 half = 7 / 2        # 3: rounds toward zero
f64 precise = 7.0 / 2.0   # 3.5
i32 big = 2147483647
i32 over = big + 1   # faults: overflow

Bug it prevents: a whole number silently rolling over from its largest value to a negative one, like a car's odometer.

+= ++ and other updates

Shortcuts that update a variable in place: n += 4 means n = n + 4, and n++ adds one. Each follows the rules of the operator it stands for, and a const can never be updated.

i32 n = 1
n += 4     # 5
n *= 2     # 10
n++        # 11
const i32 limit = 3
limit += 1   # refuses: nothing writes a const
.tri

The file extension for Trident source. A script is one.tri file, and an application is a folder whose a/ holds many. Opening a.tri file to read, check or index it never runs anything, because code runs only when the runner calls main.

Bug it prevents: importing a file only to inspect it and accidentally running its top-level code, which Python or JavaScript can do.

.x .y .z .w lane reads

Reads one part of a vector by name, like p.y. Several letters together pick and reorder parts at once, so p.zyx is the vector reversed. That reordering is called a swizzle.

dvec3 p = (1.5, -2.0, 4.0)
f64 y = p.y           # -2.0
dvec3 back = p.zyx    # (4.0, -2.0, 1.5)
p.z += 0.5
// rational constructor

Builds an exact fraction from two whole numbers: 1//2 is exactly one half, with no rounding. It is never a comment and never ordinary division, which is /.

rational half = 1//2
rational third = 1 // 3
rational sum = half + third   # exactly 5/6
f64 bad = 1.0 // 2   # refuses: // takes integers

Bug it prevents: one third stored as 0.333 and small rounding errors piling up. A fraction stays exact until you convert it.

<, >, <=, >=

Compare two values by order and give a bool. Only kinds with a true order allow them: complex numbers, flagsets, intervals and bounding boxes refuse, because any order there would be made up.

i32 low = 2
bool less = low < 5
bool most = low >= 2
complex128 z = 1.0 + 1.0i
bool bad = z < z   # refuses: complex numbers have no order

Bug it prevents: sorting values that have no real order, so the result depends on a hidden rule nobody chose.

<< >> >>> shifts

Shift a whole number's bits left or right. >> keeps the sign of a signed number, and >>> always fills with zeros. A shift count outside the type's width faults.

i32 up = 1 << 4      # 16
i32 down = -8 >> 1   # -4
i32 logical = -1 >>> 1   # 2147483647
i32 count = 32
i32 bad = 1 << count   # faults: the count reaches the width

Bug it prevents: C's undefined result from shifting by 32 or more, which differs from one computer to the next.

==, !=

== asks whether two values are equal, and != whether they differ; each gives a bool. Both sides must be the same type, so comparing a number with text, or members of two different enums, refuses.

i32 n = 4
bool same = n == 4
bool other = n != 5
text word = "4"
bool mixed = n == word   # refuses: an i32 is never a text

Bug it prevents: comparisons like JavaScript's 4 == "4" being quietly true.

@ composition

Chains two transformations: m @ v applies matrix m to vector v, and q @ q applies one rotation after another. * never means that: it works part by part, or it refuses.

dmat2 grow = (2.0, 0.0, 0.0, 3.0)
dvec2 v = (1.0, 1.0)
dvec2 w = grow @ v        # (2, 3)
dquat spin = t.math.dquat.fromAxisAngle((0.0, 1.0, 0.0), 1.5707963)
dquat both = spin @ spin  # one turn after the other
dquat bad = spin * spin   # refuses: * is not composition

Bug it prevents: writing * and getting a part-by-part product when you meant to chain two rotations or apply a matrix.

a/

a/ is the folder that holds an application's source. Each.tri file under it is a module named by its path, so a/geometry/units.tri is module a.geometry.units, and a/main.tri (module a.main) is where the program starts. In a server-and-client app a/ holds the code both sides share.

Bug it prevents: two files claiming one module name, or a runner guessing which file to start; the path decides both.

aabb, daabb (axis-aligned bounding box)

An axis-aligned bounding box: the box, lined up with the x, y and z directions, that holds something. It is stored as its lowest corner, then its highest corner. Games use it for a quick "could these touch?" test. It may name a frame and a length unit. daabb stores more digits.

aabb crate = (0, 0, 0, 2, 3, 4)
aabb<frame=builtin:itrs,unit=km> zone = (-1, -2, 0, 1, 2, 3)
aabb<frame=builtin:itrs,unit=km> bad = (2, 0, 0, 1, 1, 1)   # refuses: the low corner is above the high one
bool less = crate < crate   # refuses: a box has no order

Bug it prevents: adding two boxes with + or ranking them with <. A box is a region, so joining boxes and testing what is inside are named calls instead.

Affine map

A transform that can turn, stretch, slant and slide shapes while keeping straight lines straight and parallel lines parallel. In 2D it takes six numbers: four for turning and stretching, two for the slide. Trident's mat2x3 and dmat2x3 hold one as two rows of three.

alias

Gives a module a short local name, like alias m = t.math. It only renames something the program already holds. It never downloads or searches for code.

alias m = t.math
f64 root = m.sqrt(2.0)
alias q = my.module   # refuses: no unit carries that id

Bug it prevents: a look-alike package from the internet loading in place of your own code. An alias that names nothing in the program refuses.

Allocation and allocator

Allocation is a program asking for a piece of memory to hold its data, and an allocator is the part of the system that hands those pieces out (in C, malloc). A Trident program never calls one: each kind of memory it uses has a limit the host declares, and a request past it is a typed fault.

Bug it prevents: a loop that keeps grabbing memory until the whole machine slows down or crashes.

almanac

One member of an almanac: a fixed lookup table whose members all share one set of properties and keep a permanent id number. The value stores only that id. Almanac source declarations, properties, module reaches, and retired-member rules work.

API

An API (application programming interface) is the set of functions and rules one piece of software offers so other code can use it, like the buttons on a vending machine. Trident's own libraries form one catalog, and each is reached by its full path, such as t.text.bytes.

application

An application is a Trident program of more than one file: a folder holding a/main.tri, its identity in _app/app.draft and its lend in _grant/grant.draft. tricli calc compiles every.tri under calc/a/ into one program. Folders whose names start with _ hold facts about how the program is built and run.

Arbitrary precision

An arbitrary-precision integer grows as its value needs more digits. Trident's varint is limited by admitted memory. Runtime work is not counted or priced.

varint x = 12345678901234567
varint y = 9876543210987654321
varint p = x * y   # exact, every digit kept

Bug it prevents: a hostile, giant input making one calculation run with no limit and freezing a server.

Arena

An arena is one big block of memory that a program fills piece by piece and then frees all at once, which is fast and simple. Trident's compiler uses a growable arena while it parses and compiles, and even that arena stops at its own declared limit.

Arity

Arity is how many arguments a function takes, or how many items a fixed group holds. Trident checks every call's count when it compiles, before any argument runs, and a fixed group such as i32[3] always holds exactly three.

i32 add(i32 x, i32 y) { return x + y }
i32 sum = add(1, 2)
i32 few = add(1)   # refuses: add takes two arguments
i32[3] rgb = [255, 128, 0]
i32[3] two = [1, 2]   # refuses: a group of three needs three
assert

A statement that checks a condition inside a function body and faults if it is false. The fault names the check and the values involved, like x == 4 with x = 3. It always runs.

i32 x = 3
assert (x > 0, "positive")
assert (x == 4, "is four")   # faults: assert 'is four' failed

Bug it prevents: a check compiled out of the shipped program, so the mistake it guarded against slips through.

Astronomical year numbering

Astronomers number years with a year 0: 1 BC is year 0, 2 BC is year -1, and so on. That keeps date math simple, since the usual calendar jumps straight from 1 BC to AD 1 with no year in between.

civildate start = 0000-01-01   # year zero exists
Async, await and coroutines

Async functions and coroutines let code pause while it waits, for example for a network reply, and pick up again later; await marks the pause. Trident has none: async, await, yield and spawn are refused for good, because they make when code runs depend on timing.

auth, access

auth declares a group of users and how they sign in, and a handler receives a one-time proof of who is calling that cannot be copied or rebuilt. A trove can then state who may read each record.

Authenticated encryption

Encryption that also proves nobody changed the message, like a sealed envelope with a tamper-proof seal. Opening it either gives back exactly what was sealed or fails; it never hands back altered data. Cryptographers call it AEAD.

backend

A backend is the operating-system layer MerTek is built for: win32 (Windows), posix, linux, darwin (macOS), and wasm (WebAssembly, for browsers). MerTek's same C source builds for each, and a Trident program runs the same on all of them.

Bare metal

Bare metal means a program runs directly on a device's hardware with no ordinary operating system underneath, as in a microwave or a small sensor. Trident's answer is BenthOS, written in Cutlass, where MerTek itself supplies the device's operating system.

BenthOS

BenthOS is the setup where MerTek supplies a device's whole operating system, starting up straight into its own filesystem. It is a project, to be written in Cutlass.

bf16

"Brain float": a 16-bit number with the same range as f32 but only 2 or 3 significant digits. Machine learning uses it because range matters more than precision there. It is a different format from f16.

bf16 weight = 1.5
f16 half = weight   # refuses: two formats, no silent change

Bug it prevents: reading bfloat16 bytes as half precision, or the reverse, which gives a wildly wrong number.

binary64, binary32 and binary16

The IEEE 754 names for floating point numbers 64, 32 and 16 bits wide, often called double, single and half precision. More bits keep more digits: about 16 for binary64, 7 for binary32 and 3 for binary16. Trident's f64, f32 and f16 are these, and moving a value between them is a precision conversion.

f64 wide = 0.1
f32 narrow = wide   # refuses: binary32 cannot hold every binary64
f32 cast = f32(wide)   # faults: 0.1 is not on the binary32 grid

Bug it prevents: a 64-bit value quietly losing half its digits when it is stored in a 32-bit variable.

Binding (variable)

A binding gives a name to a value so the program can use it later, like a label on a jar; many languages call it a variable. Every Trident binding writes its full type, and the value can change later only at that same type.

i32 count = 3
count = count + 1
count = "four"   # refuses: count is an i32 for its whole life
bivector

A bivector is a flat patch of plane with a size and a turning direction, like a spinning coin seen from above. In 3D it has three numbers (xy, xz, yz). Every step with it is a named call; exp, for one, turns a patch into a rotation.

bivector<signature=euclid3> patch = (0, 0, 0.5)
dquat spin = t.math.geometricAlgebra.exp(patch)
dvec3 arrow = patch   # refuses: a turning patch is not an arrow
bivector<signature=euclid3> twice = patch + patch   # refuses: use a named call

Bug it prevents: treating a bivector as an ordinary arrow because both have three numbers in 3D. They mean different things, so they never mix.

bool

A yes-or-no value: true or false. Use it for questions like "is the door open?" It is not a number, so it has no order and no math, only logic such as &&, || and !.

bool ready = true
bool both = ready && !ready   # false
bool odd = true < false   # refuses: a truth value has no order
i32 n = ready   # refuses: a bool is not a number

Bug it prevents: treating true as 1 and adding it to a count, or sorting by a yes/no flag, as C and JavaScript allow.

Bounds check

A bounds check makes sure an index points inside a list before reading it; without one, C can read whatever memory sits past the end. Trident refuses an index it can prove is outside when it compiles, and checks every other index when it runs, faulting instead of reading the wrong memory.

i32[] xs = [10, 20, 30]
i32 first = xs[0]
i32 far = xs[7]   # refuses: every possible index is outside

Bug it prevents: reading past the end of an array and getting some other value's bytes, the root of many security holes.

Boxed value (untyped dictionary)

A boxed value is wrapped so it can hold anything, like a box with no label, and an untyped dictionary is a map whose values can be any type. Code that reads one has to guess what is inside. Trident has neither: every value has a complete type, and its documents are typed chests.

Bug it prevents: reading a number out of a box that held text, a failure that shows up far from where the wrong value went in.

Branch cut

Some math functions, like the square root of a complex number, have more than one possible answer. Mathematicians pick one by drawing a line on the number plane, the branch cut, where the chosen answer jumps. Two inputs on either side of that line, however close, get very different results.

break, continue

break leaves the innermost loop at once, and continue skips to its next pass. Both work only inside a loop.

i32 found = 0
repeat (10, i32 i = 1) {
    if (i == 2) { continue }
    if (i > 4) { break }
    found += i
}
break   # refuses: there is no loop to leave
Broadcasting

The rules that let two arrays of different shapes combine entry by entry, by stretching the smaller one to match. Adding a single number to a table adds it to every cell.

buffer

A run of raw bytes, written in hexadecimal such as 0x68656c6c6f.

buffer raw = 0x68656c6c6f
text word = raw   # refuses: bytes become text only by decoding

Bug it prevents: bytes read as text in the wrong encoding, turning one accented letter such as "é" into two garbage characters.

bus

A message bus is a set of named topics that keep every message posted to them, plus subscriptions that each remember how far one reader has read. push posts a message and pump reads them. The compiler checks every topic name and handler so a misspelled topic refuses at compile.

bvec2, bvec3, bvec4

A group of 2, 3 or 4 true-or-false values, one per lane, such as which of x, y and z passed a test. They combine lane by lane with & (and), | (or) and ! (not). They take no arithmetic.

bvec3 hit = (true, false, true)
bvec3 other = (false, false, true)
bvec3 both = hit & other
bool first = hit.x
bvec3 sum = hit + other   # refuses: true and false are not numbers

Bug it prevents: adding true to true and getting 2, which languages that treat a truth value as a number allow.

bytecode and the compiled image

Bytecode is the compact list of simple instructions the compiler makes from Trident source. The compiled image (the bytecode plus its records) carries fingerprints of the source and of the library catalog it was checked against, and it is verified before it is installed and run.

c/

The folder for client-only code, the part that runs on a user's own device. It builds only into the client, and it reaches the server only through send, which is checked at compile.

Call site

A call site is the exact spot in the code where a function is called. Trident keeps a call readable there: each function has one signature, with no defaults and no overloads, so the line shows exactly which function runs and what it gets.

Call stack

The call stack is the pile of function calls a program is in the middle of: each call adds a frame and each return removes one. Trident counts call depth against a declared limit inside its own virtual machine, so runaway recursion stops with a typed fault instead of crashing the process.

i64 factorial(i64 n) {
    if (n <= 1) { return 1 }
    return n * factorial(n - 1)
}
i64 f = factorial(10)

Bug it prevents: a stack overflow that kills the whole program with no message.

Canonical form

Canonical means the one standard way to write something when several ways mean the same thing, like always writing 1/2 instead of 2/4. Trident keeps one canonical spelling for its values and types, so a program prints its own types back in one form and a rewrite of the source comes out the same every time.

capsule2, dcapsule2

A capsule2 is a flat pill shape, like a running track seen from above: a straight segment with a radius around it. dcapsule2 stores more digits.

capsule, dcapsule

A capsule is a pill shape: a straight segment with a radius around it, so both ends are rounded. Games often use one for a character's body. dcapsule stores more digits.

text describe(capsule<frame=builtin:itrs> body) {
  point3<frame=builtin:itrs> top = body.end
  f32<family=length> thickness = body.radius
  return t.text.of(top)
}
Cast

A cast turns a value of one type into another, like a big whole number into a smaller one, and in many languages it quietly cuts off a value that does not fit. Trident writes the conversion as the target kind's name, such as i32(x): exact, or a fault. A loss on purpose is a named call.

i64 small = 7
i64 big = 3000000000
i32 fits = i32(small)
i32 wrapped = t.math.castWrapping(big, "i32")
i32 lost = i32(big)   # faults: 3000000000 does not fit an i32

Bug it prevents: a narrowing cast that turns 3000000000 into -1294967296 without a word.

catalog

The catalog is the fixed list of every library and member a program is compiled against, identified by a digest. A compiled image records the catalog digest it was checked against, so a name in the program always means the same code.

ceiling

A ceiling is a limit the host sets on one kind of memory or work, such as bytecode bytes, array elements or call depth (maxCallDepth). Going past it, or running out of memory below it, stops the work at once with a typed status, TRIDENT_LIMIT or TRIDENT_OUT_OF_MEMORY, and nothing grows beyond it.

Bug it prevents: runaway recursion or a huge array eating all of the machine's memory and taking other programs down with it.

cellid

The id of one cell in a grid laid over the Earth or a flat map (a discrete global grid), written resolution/index such as 9/74565. The grid is part of the type, so ids from two grids never mix.

cellid<grid=hexicosa> cell = 9/74565
cellid<grid=quadhilbert> other = cell   # refuses: two different grids
bool first = cell < cell   # refuses: a cell id has no order

Bug it prevents: reading a cell number from one grid system in another, where the same number names a different place.

check row

A rule inside a trove that ties its columns together, such as a start that must not come after its end. A literal that breaks the rule refuses at compile, and a later write that breaks it faults.

trove Window { i64 start, i64 end, check ordered = start <= end }
Window w = { start = 1, end = 5 }
Window bad = { start = 9, end = 2 }   # refuses: breaks ordered

Bug it prevents: a time window that ends before it starts being saved and passed around as if it were valid.

Chemical species

One particular chemical substance as a chemist counts it: a kind of molecule, ion or atom, such as water (H2O) or a sodium ion (Na+). Two samples of the same species are made of the same kind of particle.

chest

A document value: a bundle of named fields, each with its own type, that is saved and loaded whole. Trident uses it for records and configuration.

chest sample = {
    u16 code = 7
    text label = "ready"
}
text label = sample.label
Chest

Chest is MerTek's one document format: typed fields that carry their own description, so a reader needs no separate schema. The same bytes serve as a value in memory, a file, a database row and a network message. Trident's chest kind is a value in this format.

ciphertext

An encrypted value plus the header that says how to open it. It has no ==, no order and no conversion to text, so secret data cannot be compared or printed by accident. Its literal stays quoted, because its text is machine-written base64url and a short one would read as a name.

bool same(ciphertext left, ciphertext right) { return left == right }   # refuses: no == on secrets
text show(ciphertext box) { return text(box) }   # refuses: never turned into text

Bug it prevents: an encrypted field printed into a log or compared byte for byte as if it were ordinary data.

circle, dcircle

A circle on a flat surface: a center point and a radius, in an app's own frame. dcircle stores more digits.

civil

A full calendar label: a date, a time of day and, if known, the offset from its calendar body's coordinated time, such as 1987-03-05T14:30:00+02:00. It is what a wall clock and calendar say, not an instant, and a label with no recorded offset stays that way. A label under another calendar names it in the type; under civil<calendar=darian> the zero offset is Coordinated Mars Time and the time of day counts the sol.

civil utc = 1987-03-05T14:30:00Z
civil local = 1987-03-05T14:30:00+02:00
civil nozone = 1987-03-05T14:30:00
bool same = utc == local   # false
bool before = utc < nozone   # faults: the offsets differ

Bug it prevents: treating a wall-clock time with no known time zone as UTC, which can be off by hours.

civildate

A calendar date such as 1987-03-05, with no time of day and no time zone. Use it for birthdays and due dates, which are days, not instants.

civildate day = 1987-03-05
civildate next = 1987-03-06
bool before = day < next
civildate later = day + 1   # refuses: date math is a named operation

Bug it prevents: a birthday shifting to the day before when a date is stored as midnight UTC and shown in another time zone.

civiltime

A time of day on a clock face, such as 14:30:00, with no date and no time zone. A time of a sol carries its calendar as civiltime<calendar=darian>. Use it for opening hours and alarms.

civiltime open = 09:00:00
civiltime close = 17:30:00
bool ok = open < close
civiltime late = 24:00:00   # refuses: hours run 0 to 23
Closure

A closure is a function made inside another function that remembers the local variables around it, even after the outer function has returned. Trident has no closures, by design: every function is declared at the top level of a module and captures nothing from where it is called.

text main(text[] argv) {
    i32 base = 10
    i32 addBase(i32 n) { return n + base }   # refuses: a function is a module-level declaration
    return "ok"
}

Bug it prevents: a callback that quietly reads a variable after it changed, or keeps a large object alive long after it should be gone.

Co-registered fields

Two grids of data are co-registered when their samples sit at exactly the same places, like two see-through sheets lined up hole for hole. Only then can they be added or subtracted sample by sample. Trident faults when two fields' axes differ.

Code point and code unit

Computers store text as numbers. A code point is the number Unicode gives one character, and a code unit is one storage piece of an encoding, so one character can take several code units. A Trident rune holds one Unicode scalar, and code that counts text must say which unit it means.

Coherent unit

A unit built from base units with no extra number in front. The newton is coherent, because it is exactly one kilogram meter per second squared, while the kilonewton carries a factor of 1000. When Trident multiplies two amounts, the result comes out in the coherent unit.

f64<length> side = 3m
f64<unit=m2> area = side * side   # meters times meters: square meters
color

A color with its color space (the system its numbers are measured in) in the type, such as color<srgb>. Moving to another space is a conversion, never a relabel. Under a bare color, whose type names no space, the literal stays quoted as "srgb(1, 0.5, 0, 1)", because the value must name its space and srgb(…) is a call's shape.

color<srgb> orange = (1,0.5,0,1)
color<oklch> soft = (0.7,0.2,50,1)
color<oklch> wrong = orange   # refuses: a space change is a conversion
color<srgb> mix = orange + orange   # refuses: a color takes no arithmetic

Bug it prevents: reading the numbers of one color space as another, which shifts the color the viewer sees.

Color space

The rulebook that says what a color's numbers mean: which exact red, green and blue (its primaries), which white, and how numbers map to brightness. The same three numbers are a different orange in sRGB and in Rec. 2020. Trident puts the space in a color's type.

color<srgb> orange = (1,0.5,0,1)
color<oklch> soft = orange   # refuses: a space change is a conversion
vec4 lanes = orange   # refuses: four numbers do not say which space

Bug it prevents: numbers measured in one color space shown as if they were another, so colors look washed out or too strong.

compile (stage)

The compile stage turns checked source into bytecode for the virtual machine. Most of Trident's refusals happen here, so a mistake stops the program before a single line runs and nothing is left half done.

i32 count = 3
text label = "items: " + count   # refuses: write text(count)
text fixed = "items: " + text(count)

Bug it prevents: a wrong line that only fails, or prints nonsense, when it finally runs, perhaps in front of a customer.

Compile time and run time

Compile time is when the compiler checks and translates a program; run time is later, while the program actually runs. A mistake caught at compile time never reaches a user. Trident moves every check it can to compile time, and the rest become typed faults at run time.

i32 small = 5
i64 total = small   # refuses: caught at compile time
i32 zero = 0
i32 q = small / zero   # faults: caught at run time
Compiler

A compiler is a program that reads source code and translates it into a form a machine can run, checking it for mistakes on the way. Trident's compiler turns source into a bytecode image, and it refuses a program with a known mistake before any line of it runs.

complex64, complex128

A complex number: a real part and an imaginary part, stored as two f32 values (complex64) or two f64 values (complex128). Engineers use them for waves, signals and circuits.

complex128 z = 3.0 + 4.0i
complex128 w = z * z
complex64 small = (1, 2)
bool less = z < w   # refuses: complex numbers have no order

Bug it prevents: sorting complex numbers by an order that means nothing, such as their real parts alone.

cone, dcone

A cone is a party-hat shape: the center of its round base, a turn, a height, and the base's radius. dcone stores more digits.

text describe(cone<frame=builtin:itrs> hat) {
  point3<frame=builtin:itrs> base = hat.baseCenter
  f32<family=length> tall = hat.height
  return t.text.of(tall)
}
conicalfrustum, dconicalfrustum (conical frustum)

A conical frustum is a cone with its tip sliced off flat, like a bucket or a lampshade: a base center, a turn, a height, a base radius and a top radius. dconicalfrustum stores more digits.

text describe(conicalfrustum<frame=builtin:itrs> bucket) {
  f32<family=length> bottom = bucket.baseRadius
  f32<family=length> rim = bucket.topRadius
  return t.text.of(rim)
}
Conjugate

A number with the sign of its imaginary part flipped: the conjugate of 3 + 4i is 3 - 4i. For a quaternion of length 1, flipping its three axis parts gives the reverse rotation. Trident names the conjugate as its own operation instead of spelling it with a minus sign.

const

Marks a value that can never change after it is set. At the top of a file every value must be const and a plain literal, so no code runs before main starts.

const i32 limit = 3
const f64 half = t.math.pi() / 2.0   # refuses: a module constant is a literal
i32 tries = limit
limit = 4   # refuses: nothing writes a const

Bug it prevents: C++'s startup-order bug, where one global value reads another before it is set, and hidden work running when a file loads.

Value constraint

A rule in a complete type that limits its values. For example, f64<range=[0,1]> admits only numbers from zero through one. Each destination of that type enforces the rule before it publishes a value.

f64 input = 0.75
f64<range=[0,1]> chance = input
# chance = 2.0 faults; chance stays 0.75.

Read the constraint examples and comparison.

Value preparation

An explicit type policy can change a private candidate during a source write, then check it before publication. For example, outside=clamp moves an out-of-range amount to the nearest admitted boundary. A received value is only validated; it is never repaired.

i32<range=[0,100],outside=clamp> score = 120
# score holds 100.
coord2, coord3 (and dcoord2, dcoord3)

A plain list of 2 or 3 coordinates that are always ordinary finite numbers (never infinity or not-a-number), used when nobody has shown yet whether they are a point, a direction or a normal. They take no arithmetic and no order. The only change allowed is between a coord and its d twin, which stores more digits.

coord3 raw = (1, -2.5, 0.5)
dcoord3 wide = dcoord3(raw)
coord3 sum = raw + raw   # refuses: coordinates with no role take no math
coord3 broken = (infinity, 0, 0)   # refuses: every coordinate is a finite number

Bug it prevents: an infinity or not-a-number slipping into a list of coordinates and then spreading through every later calculation.

Coprime

Two whole numbers are coprime when no number bigger than 1 divides both of them, like 8 and 15. A fraction is in lowest terms exactly when its top and bottom are coprime.

Correlation

How much two uncertain values move together. Two readings taken with the same slightly-off ruler are wrong in the same direction, so their errors add up instead of canceling. When Trident combines measurements, the call must say whether they are independent or correlated, and how much.

measurement rod = (1.5, 0.25, 9)
measurement[] parts = [rod, rod]
f64[] weights = [1.0, 1.0]
f64[] linked = [1.0, 1.0, 1.0, 1.0]
measurement sum = t.math.uncertainty.combine(parts,
    weights, "correlation", linked, 3.0, 9.0)   # (3, 0.5, 9)
measurement twice = rod + rod   # refuses: a sum must state its correlation

Bug it prevents: counting one rod twice as if its two errors were independent, which reports 0.354 where the honest answer is 0.5.

Coverage factor and expanded interval

A standard uncertainty covers the true value only about two times in three. Multiplying it by a coverage factor, often 2, gives a wider expanded interval that covers it about 95 times in 100: 1.5 ± 0.25 becomes 1.5 ± 0.5.

Cross product (of two fractions)

A way to compare two fractions without dividing: a/b is less than c/d when a × d is less than c × b. So 1/3 is less than 1/2 because 1 × 2 is less than 3 × 1. It uses only whole-number math, so the answer is always exact.

rational oneHalf = 1//2
rational third = 1//3
bool ordered = third < oneHalf   # compares 1 * 2 with 3 * 1
curvepoint

A point on an elliptic curve, stored in that curve's one standard encoding, with the curve in the type: curvepoint<curve=ed25519>. Digital signatures and key exchange use them. Its literal stays quoted, because a run of hexadecimal digits would read as a name or a numeral.

curvepoint<curve=ed25519> base = "5866666666666666666666666666666666666666666666666666666666666666"
curvepoint<curve=p256> other = base   # refuses: two different curves

Bug it prevents: feeding a point from one curve into math for another, which produces garbage keys.

cylinder, dcylinder

A cylinder is a can shape: a center, a turn, half its length, and a radius. dcylinder stores more digits.

text describe(cylinder<frame=builtin:itrs> can) {
  f32<family=length> halfHeight = can.halfLength
  f32<family=length> size = can.radius
  return t.text.of(halfHeight)
}
Data race

A data race happens when two threads use the same memory at the same moment and at least one is writing, so the result depends on luck and timing. Trident shares no mutable data: every copy is a value, and a job handler runs on its own runtime, sharing nothing.

Bug it prevents: two threads adding to one counter at once and losing updates, a bug that appears only sometimes and is hard to reproduce.

dd128, td192, qd256

Extra-precise numbers made of two, three or four f64 parts that add up to one value (the "double-double" family). dd128 holds about 32 digits, td192 about 48 and qd256 about 64. Use them when f64 rounding is too coarse.

dd128 x = (1.0, 0.0)
td192 y = (1.0, 0.0, 0.0)
td192 wide = x + y   # works at the wider kind, exactly
dd128 bad = (1, 1)   # refuses: the second part must be far smaller
dd128 zero = (0.0, 0.0)
dd128 q = x / zero   # faults: no infinity is made

Bug it prevents: a precise value quietly rounded down to a plain double in the middle of a long calculation.

Debugger

A debugger lets a person pause a running program, step through it line by line and look at its values; a breakpoint is a line where it stops.

decimal

An exact base-10 number with a fixed count of digits after the point, set in its type: decimal<scale=2> always has two. Use it for prices, rates and anything people count in tenths and hundredths.

decimal<scale=2> price = 0.10
decimal<scale=2> total = price + 0.20   # exactly 0.30
decimal<scale=2> odd = 0.125   # refuses: three places at scale 2
decimal<scale=1> three = 3.0
decimal<scale=2> third = price / three   # faults: not exact at two places

Bug it prevents: 0.1 + 0.2 printing 0.30000000000000004 on a price, or a division rounding a cent away without saying so.

Default argument

A default argument is a value a function fills in when the caller leaves that argument out. It saves typing, but the call no longer shows everything the function receives. Trident has no defaults: every call names every argument, and the count is checked when it compiles.

i32 area(i32 w, i32 h) { return w * h }
i32 room = area(3, 4)
i32 strip = area(3)   # refuses: every argument is named at the call
Definite assignment

Definite assignment is the rule that a variable must surely hold a value before any code reads it. Trident's compiler proves it on every path, and an exhaustive when counts: if every arm assigns a local, the local is set after the when.

Degrees of freedom

How much evidence stands behind an uncertainty: roughly how many independent readings it came from, minus what the estimating used up. With few degrees of freedom the uncertainty itself is shaky. A Trident measurement keeps it as its third part; it must be above zero and may be infinite.

measurement rod = (1.5, 0.25, 9)   # nine degrees of freedom
measurement known = (1.5, 0.25, infinity)   # an uncertainty known exactly
measurement none = (1.5, 0.25, 0)   # refuses: zero degrees of freedom
Denial of service

Hostile input can exhaust time or memory. Declared counts and exact allocation limits bound storage. Deadlines and cancellation use coarse polls; the operating system owns CPU quotas.

Dependency

A dependency is code a program relies on that someone else wrote, usually fetched by a package manager within a version range and pinned in a lock file. A Trident program's dependencies are fixed by its bundle and its build: there is no version range or lock file, and nothing that runs later can add one.

Dependency-confusion attack

A dependency-confusion attack tricks a package manager into installing an attacker's public package that has the same name as a company's private one. A running Trident program never looks a library up by name, and tripkg (still prelaunch) binds each prefix to exactly one registry source.

Bug it prevents: installing an attacker's look-alike package that runs its own code inside your build.

deployment

A deployment is a compiled program together with what it launches with, such as its configuration and starting data. It has its own digest, separate from the program's, so changing a setting changes how a program runs but never what the program is.

Derivative

How fast one quantity changes when another changes: the slope of a curve at one point. For y = 4 × x × x, the derivative at x = 3 is 24, so a tiny nudge to x moves y about 24 times as far. Trident's dual kind computes derivatives directly, with no guessed step size.

dual x = (3.0, 1.0)   # x is 3, and we track change along x
dual c = (4.0, 0.0)   # a constant does not change
dual y = x * x * c
f64 slope = t.math.dual.tangent(y)
assert (slope == 24.0, "the derivative")
Deterministic

A deterministic program gives the same output every time it gets the same input, with no hidden dependence on the clock, random numbers or timing. Trident is built for it: there is no ambient clock, time arrives through an explicit call or field, and async and await are refused for good.

Bug it prevents: a test that passes on one run and fails on the next because the code read the clock.

Diagnostic

A diagnostic is the message a compiler prints when it finds a problem: where it is, what is wrong, and ideally how to fix it. Trident's diagnostics name the edit to make, such as the conversion i32(digits), so the fix is in the message rather than in your head.

dict<K,V> dictionary

A typed lookup table from keys to values, like a phone book: dict<text,i32> maps text to whole numbers. Reading a missing key with m[k] faults; get lets you name a fallback instead.

dict<text,i32> scores = {"b" = 2, "a" = 1}
scores["c"] = 3
i32 fallback = scores.get("missing", 0)
i32 gone = scores["zzz"]   # faults: absentKey

Bug it prevents: a missing key quietly read as zero or as undefined, and a table that holds values of any type at all.

A dictionary layer also states length, distinctValues and its full write policy inside the same frame, after V. Lowest/highest key order defines its first/last entries. Read the collection rules.

Dictionary (map)

A dictionary, also called a map, stores values under keys, like a phone book that maps names to numbers. Trident's is dict<K,V>, with a declared type for its keys and its values; reading a key it does not hold is a fault, and get supplies a fallback instead.

dict<text,i64> prices = { "apple" = 50, "banana" = 30 }
i64 apple = prices["apple"]
bool hasPear = prices.has("pear")
i64 pear = prices["pear"]   # faults: the dictionary holds no key "pear"

Bug it prevents: a missing key quietly read as zero or undefined and carried into a total.

Diff

A diff shows the lines that changed between two versions of a file, and most code review reads one. Because every Trident binding writes its full type, a diff shows the same facts an editor would, and nothing in another file can quietly change what a line means.

Dimension (in physics)

What kind of thing a number measures, whatever its unit: length, time, mass, or a mix such as speed, which is length divided by time. Meters and feet share one dimension. Trident tracks seven dimension exponents, one per SI base quantity, so mass times acceleration comes out as a force by itself.

f64<mass> load = 5kg
f64<acceleration> g = 9.81m/s^2
f64<unit=kg*m/s^2> push = load * g   # a force
f64<mass> bad = load + push   # refuses: a mass plus a force

Bug it prevents: adding meters to seconds, or reading pound-force seconds as newton seconds, the mix-up that lost the Mars Climate Orbiter.

dir2, ddir2

A dir2 is a free arrow on a flat surface: a direction, or a change in position, with no fixed starting spot. It may be zero and may carry a quantity such as length. Like every flat kind, it uses an app's own frame. ddir2 stores more digits.

dir3, ddir3

A dir3 is a free arrow in 3D: a direction, or a change in position, with no fixed starting spot. Unlike a point it may be zero, and it may carry a quantity such as length or speed. Its frame is part of its type. ddir3 stores more digits.

dir3<frame=builtin:itrs,family=length> step = (0, 0, 2)
dir3<frame=builtin:itrs> still = (0, 0, 0)
dir3<frame=builtin:icrs,family=length> other = step   # refuses: a different frame

Bug it prevents: confusing where something is (a point) with which way it goes (a direction), which most libraries store as the same three-number vector.

Discrete global grid

A way of covering the whole Earth with numbered tiles, often hexagons, so that any place maps to one cell id. H3 is a well-known example. Trident's cellid kind carries which grid it belongs to in its type, so ids from two grids never mix.

Draft

Draft is the plain-text way to write a Chest document, one typed line per field, such as i32 since = 1. Grant files, app identity files and endpoint records are Draft, so the compiler reads them as checked, typed data rather than as loose strings.

dual

A dual number: a value paired with its rate of change (its derivative). Arithmetic carries both along, so a calculation hands back its own slope, with no separate calculus step.

dual x = (3.0, 1.0)   # x = 3, changing at rate 1
dual y = x * x
f64 slope = t.math.dual.tangent(y)   # 6.0, the slope of x*x at 3
dvec2 v = x   # refuses: same bytes, different meaning

Bug it prevents: treating the pair as a plain 2D vector, which multiplies the parts separately and loses the product rule.

duration

A length of time, stored exactly, such as 90s or 1h30m. Use it for timeouts, delays and how long something took. The unit is part of the literal, so a bare number is never a time.

duration step = 1h30m
duration twice = step + step
duration triple = step * i64(3)
duration wait = 90   # refuses: 90 of what?
duration month = 1mo   # refuses: a month has no fixed length

Bug it prevents: sleep(90) meaning 90 seconds to one author and 90 milliseconds to another.

duty

A block that runs only while this copy of the program holds a named duty, such as being the one "conductor" among several machines. Its else arm runs otherwise.

Dynamic typing

In a dynamically typed language, such as Python or JavaScript, a value's type is checked only while the program runs, so a mismatch shows up when that line executes. Trident is statically typed instead: every type is known and checked before the program starts.

Elementwise (lane by lane)

Doing an operation separately on each matching pair of entries: (1, 2) times (3, 4) lane by lane is (3, 8). That is right for a list of numbers and wrong for a quaternion, whose four numbers make one rotation together. Trident's * on a vector, matrix or tensor is lane by lane.

dvec2 left = (1.0, 2.0)
dvec2 right = (3.0, 4.0)
dvec2 both = left * right   # (3, 8), lane by lane
dquat spin = (1.0, 0.0, 0.0, 0.0)
dquat bad = spin * spin   # refuses: four lanes are one rotation

Bug it prevents: multiplying two quaternions lane by lane, which runs and gives a rotation that is quietly wrong.

ellipse, dellipse

An ellipse is a flat oval: a center, a turn, and two half-widths (its semi-axes). dellipse stores more digits.

ellipsoid, dellipsoid

An ellipsoid is a stretched or squashed ball, like an egg or a football: a center, a turn, and three half-widths (its semi-axes). Earth itself is close to one. dellipsoid stores more digits.

text describe(ellipsoid<frame=builtin:itrs> egg) {
  vec3<family=length> halfWidths = egg.semiAxes
  return t.text.of(halfWidths)
}
Embedding a language

Embedding a language means running it inside a bigger application, the way many games run Lua scripts; the application is the embedder. Trident is built to be embedded: the host decides what a program may reach, and the program runs behind a declared boundary with its own limits.

endpoint

An endpoint is one address a server answers, such as GET /items/{id}. Trident declares routes in the language, so the compiler checks the route, the method and each parameter's type before the program runs.

Bug it prevents: two handlers claiming the same route, or an id read as text instead of a number, found only when a real request hits it.

Entry point

The entry point is the one function where a program starts running, often called main. A Trident source file runs nothing until the runner calls its entry point; a script's is main(text[] argv).

enum

A type with a fixed list of named members, such as Status.Ok and Status.Error. A member is a name, not a number, and two enums never mix even when their members share a name.

enum Status { Ok = 0, Warn = 5, Error = 10 }
enum Health { Ok = 1, Hurt = 2 }
Status s = Status.Ok
Health h = Status.Ok   # refuses: two enums never mix

Bug it prevents: passing a Health where a Status was expected because both are secretly integers.

enum declaration

Declares a type with a fixed list of named choices, each with its own number, like enum Status { Ok = 0, Warn = 5 }. A member written with its type name, as in Status.Ok, works anywhere; where the spot already expects a Status, the bare name Ok works too, and anywhere else a bare name is refused.

enum Status { Ok = 0, Warn = 5, Error = 10 }
Status now = Status.Warn
Status later = Warn   # the spot expects a Status
i32 raw = Status.Warn   # 5
i32 bad = Warn   # refuses: a member needs its enum name

Bug it prevents: comparing members of two unrelated lists. Status.Ok == Priority.Ok refuses instead of being silently true, as plain numbers in C would be.

Environment variable

Environment variables are named settings the operating system hands a program when it starts, such as a home folder. In most languages any line can read them. Trident has no ambient environment: a launch computes its configuration once, in on configure, before any store opens.

Bug it prevents: a setting read deep inside a function that is missing in production and fails only when that line first runs.

Epsilon

A tiny number programmers add, or compare against, to cover up rounding errors, as in "close enough". An epsilon-repaired value is one nudged by such a fudge so that a later step does not fail. Trident's math publishes no such value; it faults and names the problem instead.

Euclidean

Ordinary flat geometry, the kind taught in school: parallel lines never meet, a triangle's angles add up to 180°, and the Pythagorean theorem holds. Euclidean space is the usual 2D or 3D space of rulers and protractors.

Eval

Eval runs code that a program builds as text while it runs, and the new code gets the program's full access. Trident can compile source bytes it holds with t.code.compile, but it cannot freely run the result: only one function that a checker proves touches no clock, files or network may run.

Bug it prevents: running text an attacker supplied as code, a classic way to take over a server.

exact

An exact number that can include powers of pi: a fraction, times a power of ten, times a power of pi. It holds values such as Planck's constant over 2 pi with no rounding.

exact hbar = exact((662607015, 2, 42, -1))   # 662607015/2 x 10^-42 / pi
exact bad = exact((1, 0, 0, 0))   # refuses: zero denominator
Exact arithmetic

Math with no rounding at all: the answer is the true answer, as when you work fractions by hand. Trident's whole-number, rational, decimal, money and measure kinds compute exactly, and they fault or refuse where an exact answer cannot be kept instead of rounding behind your back.

decimal<2> dime = 0.10
decimal<2> twenty = 0.20
decimal<2> thirty = 0.30
assert (dime + twenty == thirty, "no rounding happened")

Bug it prevents: 0.1 + 0.2 printing 0.30000000000000004 on a price.

Exception

An exception is an error signal that jumps out of the running code until something catches it, usually with try and catch. Trident uses catchable faults instead: an on error handler is written before the statements it guards, and retry, resume or fail says what happens next.

i32 zero = 0
i32 result = 0
on error { resume }
result = 10 / zero
result = result + 1

Bug it prevents: a handler swallowing a mistake that should have stopped the program, because only run-time faults can be caught.

execute (stage)

The execute stage is when the program's bytecode actually runs. A fault here, such as dividing by zero, is the only kind a program can catch with on error or task, because the earlier stages finish before any handler exists.

i32 zero = 0
i32 lost = 1 / zero   # faults: division by zero

Bug it prevents: a catch block swallowing a compile or setup error as if it were an ordinary problem with one run.

Exhaustive check

An exhaustive check makes sure a choice covers every possible case. In Trident a when over an enum is exhaustive by default: it must name every member and takes no else, so adding a member flags every when that forgot it.

enum Light { Red = 0, Yellow = 1, Green = 2 }
Light now = Light.Red
text go = when (now) {
    is Light.Red { use "stop" }
    is Light.Yellow { use "slow" }
    is Light.Green { use "go" }
}
text bad = when (now) { is Light.Red { use "stop" } }   # refuses: Yellow and Green have no arm

Bug it prevents: a new case quietly falling into a default branch someone wrote years ago.

observation histogram

A cumulative distribution with fixed bounds, exact bucket counts, a sum, and a generation. Count comes from the bucket sum. Bounds are checked at the sum's type and must project exactly to binary64. The application declaration compiles and verifies.

Bug it prevents: merging unrelated bucket layouts or publishing a count that disagrees with its buckets.

metric reducer

A permitted way to combine readings, such as total, rate, maximum, or bucket counts. Each metric class has its own typed reducer set. A reducer works only with compatible types, populations, units, and time coverage.

Bug it prevents: combining values with a rule that does not match their metric class.

metric temporality

Whether a publication describes a cumulative total or one delta batch. The approved class word fixes this choice: histogram is cumulative and event is delta. There is no separate authored temporality property.

Bug it prevents: adding cumulative totals as if each were a fresh batch.

histogram bound

A fixed dividing value between buckets. The selected rule puts equality in the bucket that starts at the bound. Bounds are nonempty, finite, strictly ascending, and exactly projected.

Bug it prevents: placing the same boundary value in different buckets on different collection paths.

observationPublications

The completed-call return array of source/document rows. Source selects one compiled observation declaration. Document supplies the actual record. Host admits or reports loss after the call; building this array alone emits nothing.

Bug it prevents: treating a local telemetry document as proof that a collector or durable Store received it.

metric publisher

The complete record identifying the population that supplied a reading. The application example uses a shard field. Host separately supplies its actual runtime and call context; an application label grants no native identity or authority.

Bug it prevents: joining readings from unrelated producers because their display labels look alike.

metric rollup

Retained sufficient statistics for a declared time window and tier. The record keeps covered time, gaps, consumed ranges, and needed evidence. Reading tiers merge integrals and durations; histogram tiers merge counts.

Bug it prevents: averaging averages or merging percentiles after the facts needed for a correct answer are lost.

collection epoch

Host's collection continuity period. A changed collection plan starts a new epoch and fences old batches. It is separate from the application's tally generation and the session's composition generation.

Bug it prevents: joining values across a policy change or source restart as one uninterrupted interval.

metric history query

A typed request for one qualified collection, series, interval, resolution, and exactness choice, under row, byte, and cursor bounds. The constructor performs no Store read. Host later returns an available page or an unavailable result.

Bug it prevents: mistaking request construction for an immediate or authorized database read.

history cut

The exact Store scope and publication versions a page reads. It includes the collection anchor, owner, and ordered stripe positions. Multiple stripes keep their actual positions; there is no invented global version number.

Bug it prevents: presenting a partly applied publication as complete history.

tally baseline

The first accepted total in one compatible tally segment. Later readings measure change from that segment. A new generation starts a new baseline, and a fall under the same generation creates a gap.

Bug it prevents: computing a negative rate across a reset or assuming an earlier value was zero.

observation declaration

A module declaration that names metrics from one supplied record. Its paths, types, units, reducers, and scopes are checked before execution. Reading, tally, histogram, balance, and delta metadata work.

observation orders(Publication supplied) { ... }

Bug it prevents: a metric that reads the wrong field type or changes its unit between readings.

reading metric

A value at one moment. No generation field. A new reading replaces the current value. The canonical declaration and reducer domain compile.

Bug it prevents: using a reducer or restart rule from a different metric class.

tally metric

A cumulative total that only rises within one generation. A separate u64 generation marks a restart. A fall without a new generation is a gap. The canonical declaration and reducer domain compile.

Bug it prevents: using a reducer or restart rule from a different metric class.

balance metric

A signed cumulative total that can rise or fall. A separate u64 generation marks a restart. A fall in one generation is valid change. The canonical declaration and reducer domain compile.

Bug it prevents: using a reducer or restart rule from a different metric class.

delta metric

Activity since the previous completed call. No generation field. Each call supplies its own batch. Zero is valid; a missing batch is unknown. Delta declarations and their reducer domain compile.

Bug it prevents: using a reducer or restart rule from a different metric class.

tally generation

A nonnullable scalar u64 field that marks one continuous tally segment. Zero is valid. It is separate from the total; a new generation starts a new segment.

Bug it prevents: calculating a rate across two unrelated tally segments.

when unguarded

A partial enum selection. It may omit members and may have an else arm. Duplicate members still refuse. An ordinary enum when must cover all admitted members.

Bug it prevents: confusing an intentionally partial selection with a complete one.

guard: an older statement name

Trident's older guarded field read is now spelled expect. the old guard statement is refused. A selector on a check row is a separate record constraint.

Bug it prevents: using a retired statement spelling or mistaking a record constraint for a field read.

expect … else

Reads named fields from a document at complete declared types, all or nothing. A missing field or wrong type takes the required leaving else arm. The older spelling was guard. Whole-value expect has a proved trove-contract slice.

text describe(chest doc) {
    expect { text name, i64 age } = doc else {
        return "incomplete"
    }
    return name + " is " + text(age)
}
chest who = { text name = "Ada" }
text said = describe(who)   # "incomplete"

Bug it prevents: a missing field read as zero or empty, as Go's JSON decoding does, and code carrying on with a half-filled record.

Exponent rail

A floating point number is stored as digits times a power of two, and each format has a smallest and a largest power it allows. Those limits are its exponent rails. A result past the bottom rail is too tiny to keep at full precision, and Trident's expansion kinds fault there rather than quietly lose digits.

f16

A small floating-point (decimal-point) number in 16 bits, the standard "half precision" format: about 3 significant digits, and nothing above 65,504. Use it to save space in graphics and machine-learning data where rough precision is enough.

f16 h = 1.5
f16 top = 65504
f16 over = 65520   # refuses: past the largest f16

Bug it prevents: a value too big for the format turning into infinity without a word.

f32

A 32-bit floating-point (decimal-point) number with about 7 significant digits. Use it for graphics, games and sensor data, where size and speed matter more than the last digits.

f64 half = 0.5
f32 single = f32(half)   # exact, so it converts
f64 tenth = 0.1
f32 near = t.math.castRounding(tenth, "f32")   # rounding, by name
f32 lost = f32(tenth)   # faults: 0.1 is not exact in an f32

Bug it prevents: digits lost without warning when an f64 is squeezed into an f32.

f64

A 64-bit floating-point (decimal-point) number with about 16 significant digits, the usual choice for science and measurement. In Trident an overflow or a divide by zero is a fault, not a quiet infinity.

f64 rate = 1.5
f64 far = 1.0e308 * 10.0   # faults: overflow
f64 zero = 0.0
f64 bad = rate / zero   # faults: divides by zero

Bug it prevents: a NaN ("not a number") or an infinity spreading quietly through a calculation until the final answer is garbage.

fail

Raises a fault on purpose, with an optional message: fail "no user row". A handler can catch it. Inside a handler, a bare fail passes the caught fault on unchanged.

i32 n = -1
if (n > 0) { fail "positive input" }
if (n < 0) { fail "negative input" }   # faults: fail raises a fault

Bug it prevents: a bad value carrying on silently because the code that spotted it had no clear way to stop.

Fallthrough

Fallthrough is when a switch runs the matching case and then keeps going into the next case unless a break stops it. Trident's when never falls through, and one is arm can list several values, which covers the usual reason to want it.

i32 day = 6
text kind = "weekday"
when (day) {
    is 6, 7 { kind = "weekend" }
    else { kind = "weekday" }
}

Bug it prevents: a missing break in C that runs the next case's code too.

false start

A false start is a launch that stops before main runs because a start-up step failed: the program did not compile, its grant and lend disagree, a store would not open, or on start said no. The program never limps along half set up.

Bug it prevents: a server starting with a missing database or a wrong setting and failing an hour later on the first real request.

fault

A fault is a typed error raised while a program runs, such as text that is not a number or a division by zero. The work stops at that line, and no made-up value takes its place. A program can catch an execute-stage fault with on error or a task block.

text digits = "twelve"
i32 n = i32(digits)   # faults: twelve is not an i32

Bug it prevents: a bad value such as NaN, or a number that wrapped around, traveling silently into every later result.

field

Values sampled over a grid, such as temperatures across a map or readings over time, with each axis described. Two fields combine sample by sample only when their grids match exactly. Its literal stays quoted, because its header holds braces and =.

field<dtype=u8> heights = "u8{x=2;c=1}0x0102"
field<dtype=u8> doubled = heights * u8(2)
u32 axes = t.quantity.rank(heights)   # 2

Bug it prevents: adding two maps whose grid points sit in different places, which mixes values from different spots.

fieldelem

A number in a finite field: math that wraps around at a fixed prime number, as cryptography uses. The prime is part of the type, such as fieldelem<modulus=p256>.

fieldelem<modulus=l25519> k = 7
fieldelem<modulus=l25519> minus = -7   # refuses: field values are never negative
bool less = k < k   # refuses: a finite field has no order

Bug it prevents: mixing numbers reduced by two different primes, such as the P-256 field prime and its group order.

Finite-difference gradient

The old way to estimate a slope: nudge the input by a small step h and see how far the output moves. Too big a step measures the curve's bend; too small a step lets rounding errors swamp the answer. A gradient is the slope in every input direction at once.

Bug it prevents: a slope that is mostly rounding noise, with nothing to warn you that the step size was wrong.

flagset

A set of named on/off switches packed into one value, such as file permissions. You combine sets with |, & and ~ and test one with has. A set is not a number.

flagset Access { Read = 1, Write = 2, Exec = 4 }
Access rw = Access.Read | Access.Write
bool canRead = rw.has(Access.Read)
Access more = rw + 1   # refuses: a set is not a number

Bug it prevents: adding 1 to a set of flags and flipping a permission nobody meant to grant.

flagset declaration

Declares a set of named on/off flags kept in one 64-bit word, like file permissions. Flags combine with set operators (|, &, ^, ~) and are tested with has, never with arithmetic. Where the spot already expects that flagset, as the argument of has does, a flag may drop its type name, as in rw.has(Write).

flagset Access { Read = 1, Write = 2, Exec = 4 }
Access rw = Access.Read | Access.Write
bool canRead = rw.has(Access.Read)
bool canWrite = rw.has(Write)   # Write reads as Access.Write
u64 bits = rw   # 3, only at a u64
Access more = rw + Access.Exec   # refuses: a set is not a number

Bug it prevents: ~ flipping all 64 bits and switching on flags nobody named, or adding flags like numbers so a bit lands in the wrong place.

fleet

A fleet is all the running copies (runtimes) of one deployment that share one store, on one machine or many. The fork and duty statements start runtimes in the fleet and give one of them a named role, and a program whose lend has no fleet line cannot fork.

Floating point

The way computers store numbers that have a fractional part, like scientific notation in binary: some digits times a power of two. It reaches huge and tiny numbers, but most decimals, such as 0.1, can only be stored approximately, so small rounding errors creep in.

f64 sum = 0.1 + 0.2
bool same = sum == 0.3   # false: both sides were rounded to binary
Floor division

Division that always rounds down, so the remainder is never negative: -7 divided by 2 is -4 with remainder 1. C rounds toward zero instead (-3 with remainder -1), which breaks calendar math for negative years. In Trident, // is not floor division: it builds an exact fraction.

rational half = 7 // 2   # 7/2 as a fraction, not 3
foreach … in

Runs a block once for each element of an array, or each key and value of a dictionary. It walks the collection as it was when the loop began, so changing it inside the loop never changes the walk.

i32[] values = [1, 2, 3]
i32 sum = 0
foreach (value in values) {
    sum += value
}
foreach (i32 v in values) { sum += v }   # refuses: the binding states no type

Bug it prevents: changing a list while looping over it, which crashes in Java and silently skips or repeats items in other languages.

Foreign function interface (FFI)

A foreign function interface lets a program call code written in another language, usually a C library. It is powerful, but that code runs with none of the language's safety checks. A Trident program has no FFI: it reaches only the t.* libraries the host links in.

fork

A reserved word for a statement that starts another copy of the running program, on this machine or another one in its group (the fleet), which talks back only through the shared data store. The word is kept so no program can use it as a name.

i32 forks = 1
i32 fork = 1   # refuses: a future keyword is not a name
Format string

A format string is text with placeholders, like "%f kN", that a function such as printf fills with values. Trident has no format specifiers: each hole in an interpolated text is text(x), and how a value looks is a named call, such as t.measure.format.

Bug it prevents: a unit typed by hand into a format string that no longer matches the number it prints.

Forward-mode differentiation

A way for a computer to work out derivatives: every number carries its value and its rate of change, called its tangent, side by side, and each operation updates both by the rules of calculus. Nothing is estimated with small steps. Trident's dual kind is this pair, a first-order Taylor pair.

frameid (frame)

A frame is the point of view the numbers are measured from, like "from the center of the Earth, turning with it" or "from the camera". A frameid is the name of one frame. The built-in frames cover Earth, the sky and the Moon, including the lunar frame lcrs, and an app can name its own. Spatial types carry their frame in the type.

frameid earth = builtin:itrs   # turns with the Earth
frameid sky = builtin:icrs   # fixed to distant stars
bool same = earth == sky
bool less = earth < sky   # refuses: a frame name has no order

Bug it prevents: mixing coordinates from two frames, such as using a camera-space point as a world-space point. The frame is part of the type, so the mix refuses.

Framework

A framework is a large library that provides the skeleton of an app, such as a web framework that sends each request to your code. Where other languages reach for a web framework, Trident declares endpoints in the language itself, so a routing mistake is refused when it compiles.

Function declaration

A function starts with the type it returns, then its name and its typed parameters: i32 add(i32 x, i32 y). There is no function keyword. Every call passes exactly the listed arguments, with no defaults or overloads.

i32 add(i32 x, i32 y) {
    return x + y
}
i32 sum = add(2, 3)
i32 few = add(2)   # refuses: a call names every argument

Bug it prevents: a call with a missing or extra argument that only misbehaves once the program runs.

Function value (callback)

A function value is a function stored in a variable or passed to another function. A callback is one handed over to be called later, and a comparator is one that tells a sort which item comes first. Trident has no function values, by design: every call names a declared function.

i32 twice(i32 n) { return n * 2 }
i32 thrice(i32 n) { return n * 3 }
i32 scale(i32 mode, i32 n) {
    if (mode == 2) { return twice(n) }
    return thrice(n)
}
i32 six = scale(2, 3)
fxrate

An exchange rate with both currencies in its type: fxrate<EUR,USD,4> is the price of one euro in dollars, to four places. A conversion is a named call that rounds once, and a rate works only in the direction it names.

fxrate<EUR,USD,4> rate = 1.0850
money<EUR> price = 10.00
money<USD> zero = 0.00
money<USD> cost = t.finance.fx.convert(price, rate, zero, "halfEven")
money<EUR> back = t.finance.fx.convert(cost, rate, price, "halfEven")   # refuses: the rate runs EUR to USD

Bug it prevents: applying a euro-to-dollar rate backwards and charging the wrong amount.

Gamut

The set of colors a screen, a printer or a color space can actually show. A color outside it has to be pulled inside, which is called gamut mapping.

Garbage collection (GC)

Garbage collection frees memory automatically: a collector finds data nothing uses anymore and takes it back. A tracing GC scans everything still reachable, which can pause the program. Trident runs no collector at all; a scope's memory reservation is refunded when the scope ends.

Bug it prevents: a game frame that stutters while the collector stops to clean up.

Generic function

A generic function is written once for several types. Trident has no source-level generic function. An outline parameter admits records that satisfy its structural contract.

i32 twice(i32 n) { return n * 2 }
i64 twiceWide(i64 n) { return n * 2 }
i32 eight = twice(4)
i64 wideEight = twiceWide(i64(4))
i64 wrong = twice(i64(4))   # refuses: twice takes an i32
Geodesic

The shortest path between two points along a curved surface, like an airliner's route over the globe. On the Earth it is not a straight line on a flat map, which is why subtracting two latitude and longitude pairs does not give the distance between them.

Bug it prevents: subtracting two latitude/longitude pairs and treating the result as a distance, which is off by about cos(latitude) east to west.

geodetic (geodetic coordinates)

A place on Earth: latitude and longitude (angles, in radians) plus height in meters, measured on a named model of Earth's shape called a datum, such as wgs84, the one GPS uses. It refuses order, arithmetic and conversion: a place has no order, and a distance or a change of datum is a named calculation.

geodetic<datum=wgs84> home = (0.7, -1.3, 120)
geodetic<datum=wgs84> work = (0.71, -1.31, 80)
geodetic<datum=wgs84> gap = home - work   # refuses: distance on a curved Earth is a named calculation

Bug it prevents: subtracting two latitude and longitude pairs and calling the result a distance, which gets more wrong the farther you are from the equator.

Geodetic datum

The agreed model of the Earth's shape and position that latitudes and longitudes are measured against, such as WGS 84, which GPS uses. One spot can have slightly different coordinates under two datums. Moving coordinates between them is a datum transform, a named call in Trident (t.geo.carto.datumShift).

Geometric algebra

One algebra that treats directions, flat patches (bivectors), rotations and reflections as things you can add and multiply, where each piece has a grade: 1 for a direction, 2 for a flat patch. The even grades hold rotations; in 3D that even subalgebra is the quaternions. Trident spells its products as named t.math.geometricAlgebra calls, never as operators.

Ghost fill

In grid simulations, a block of cells keeps an extra border of "ghost" cells copied from its neighbors, so a calculation at its edge can look one step past it. Ghost fill is copying values into that border.

Glint

Glint is Thalassa's shader language: small programs that run on the graphics card to shape and color what is drawn. Its written form follows Trident's grammar wherever both languages share a construct and a companion page compares it with other shader languages.

grant

The grant is the exact, fixed list of libraries and services a running program may call. The host builds it at launch from what the source reaches and what the lend allows, and it never changes while the program runs. Every t.* call is grant-gated: a call the grant does not admit is denied.

Bug it prevents: a program reaching the network or a key store that nobody approved, hidden somewhere among its imports.

Grapheme cluster

A grapheme cluster is what a reader sees as one character, even when it is stored as several Unicode scalars, like a letter plus an accent mark, or a flag emoji. Trident asks which count you mean: t.text.graphemes counts these, while t.text.scalars and t.text.bytes count the others.

text word = "héllo"
i64 n = word.count()   # refuses: say which unit you are counting

Bug it prevents: cutting a name in half in the middle of an accented letter or an emoji.

Greatest common divisor

The biggest whole number that divides two numbers evenly. For 12 and 18 it is 6. Dividing the top and bottom of a fraction by it puts the fraction in lowest terms: 12/18 becomes 2/3.

rational r = 12//18
assert (r == 2//3, "kept in lowest terms")
guid

A 128-bit id written as 32 hexadecimal digits (0-9 and a-f) in five groups, like a serial number nobody else ever reuses. Use it to name records and objects. It is never a number and never plain text.

guid id = 00000000-0000-0000-0000-000000000000
guid copy = id
bool same = id == copy
bool first = id < copy   # refuses: an id has no order
u128 n = id   # refuses: an id is not a number

Bug it prevents: doing math on an id, or comparing an id with any string that happens to look like one.

Half-open range

A range that includes its start but not its end, like "from 9:00 up to, but not including, 10:00". Back-to-back ranges then meet with no gap and no overlap, so every instant belongs to exactly one. A Trident timerange is half-open.

Handler

A handler is a block of code that runs when something happens, such as an error or an incoming request. Trident declares its handlers ahead of time: on error before the statements it guards, and on request, on event and on job at module level. No program can add a handler while it runs.

Hash and digest

A hash function turns any data into a short, fixed-size fingerprint called its digest. Change one byte and the digest changes completely, and finding two inputs with the same digest is practically impossible. So naming a file by its digest guarantees you get exactly those bytes back.

Bug it prevents: a library swapped for different code under the same name and version.

Heap

The heap is the large pool of memory a program draws from for data whose size or lifetime is not known ahead of time; a garbage collector or the programmer must give it back. A Trident program never manages it: each kind of memory has a declared limit, and a scope's reservation is refunded when the scope ends.

host

The host is the program that runs a Trident program, such as tricli or tri. It opens files, windows, stores and the network, decides what the program may touch, compiles the program and calls it. A Trident program reaches the outside world only through what its host lends it. The host configuration defines those permissions.

Bug it prevents: a program grabbing a file, the clock or the network that nobody gave it.

HTTP

HTTP is the set of rules web browsers and servers use to send requests and answers, such as GET a page or POST a form. Trident builds HTTP routing into the language with on request, and its outbound HTTP client is the t.net.http library.

hvec2, hvec3, hvec4

A vector of 2, 3 or 4 half-size decimal numbers (binary16), using half the memory of a vec. It suits storing many colors or surface arrows for a graphics card, where fewer digits are fine. Mixing it with a full-size vector needs an explicit conversion.

hvec3 small = (1, -2.5, 0.5)
vec3 full = vec3(small)
hvec3 mixed = small + full   # refuses: convert one side first
hvec2 tiny = (70000, 0)   # refuses: too big for a half-size number

Bug it prevents: mixing half-size and full-size numbers in one sum and losing digits at a spot nobody chose.

i8

A whole number stored in 8 bits, from -128 to 127. Pick it for small signed values when space matters, such as a tiny offset.

i8 top = 127
i8 low = -128
i8 over = 128   # refuses: 128 does not fit in an i8
i8 next = top + 1   # faults: past the largest i8

Bug it prevents: 127 plus 1 silently wrapping around to -128.

i16

A whole number stored in 16 bits, from -32,768 to 32,767. Use it for audio samples and other compact signed data.

i16 sample = -32768
i16 lower = sample - 1   # faults: past the smallest i16
i16 over = 40000   # refuses: 40000 does not fit in an i16

Bug it prevents: a loud audio sample wrapping from the top of the range to the bottom and turning into a click.

i32

A whole number stored in 32 bits, from about -2.1 billion to 2.1 billion. It is the everyday choice for counts and ordinary whole numbers.

i32 count = 3
i64 total = count   # refuses: a destination never widens
i64 wide = i64(count)   # the conversion is written out
i32 big = 2147483647
i32 over = big + 1   # faults: past the largest i32

Bug it prevents: a number silently changing size, or wrapping past its largest value to a large negative one.

i64

A whole number stored in 64 bits, up to about 9.2 quintillion either way. Use it for large counts and byte sizes. It is also the width a plain numeral takes when nothing else says.

i64 bytes = 3000000000
i32 small = bytes   # refuses: the conversion must be written
duration step = 2s
duration longer = step * i64(3)   # a duration scales by an i64

Bug it prevents: a large value silently chopped down to fit a smaller integer.

i128

A whole number stored in 128 bits, up to 39 digits long either way. Use it when even an i64 could run out, as in exact totals over huge data.

i128 huge = 170141183460469231731687303715884105727
i128 next = huge + 1   # faults: past the largest i128
I/O

I/O (input and output) is any way a program talks to the world outside it: files, the network, the screen, the keyboard. In Trident, I/O happens only through granted library calls, and a module constant can never do I/O while the program loads.

IDE and language server

An IDE (integrated development environment) is an editor built for code that shows errors and types as you work; a language server is the helper program that feeds it those facts.

IEEE 754

The worldwide rulebook, first published in 1985, for how computers store floating point numbers and round their results; nearly every processor follows it. It also defines special values: infinity, NaN and a negative zero. Trident uses its formats but faults where the rulebook would hand back an infinity or a NaN.

f64 zero = 0.0
f64 x = 1.0 / zero   # faults: IEEE 754 would answer infinity

Bug it prevents: a division by zero turning into infinity, then NaN, and showing up far away as a report full of "nan".

if, elseif, else

Runs a block only when a condition is true; elseif tries another condition and else catches the rest. The condition must be a bool. Written where a value belongs, an if can also produce one with use.

i32 n = -3
text sign = "zero"
if (n < 0) {
    sign = "negative"
} elseif (n > 0) {
    sign = "positive"
}
if (n) { sign = "?" }   # refuses: a condition is a bool

Bug it prevents: C's if (x = 5), which assigns instead of comparing, and numbers or empty text quietly counting as true or false.

Immutable and mutable

An immutable value can never change after it is made; a mutable one can be changed in place. In Trident a const binding refuses every write, and every copy of a value is its own, so changing one copy never changes another.

const i32 limit = 10
i32 room = limit - 1
limit = 11   # refuses: a const binding is never written
Implicit conversion

An implicit conversion is a type change the language makes for you without being asked, like turning a number into text. Trident allows exactly one: two integer widths inside an operator meet at the smallest width that holds both. A destination never converts.

i32 left = 3
u32 right = 4
i64 product = left * right
i64 copy = left   # refuses: a destination never converts
f64 wide = 1.5
f32 narrow = wide   # refuses: float widths never mix

Bug it prevents: "5" + 3 becoming "53", or a float quietly losing digits as it is squeezed into a smaller one.

Import

An import brings code from another file or library into the current one, and in many languages importing a file runs its code. Trident's one declared import is alias, which only shortens a name the compile already holds, and every import is settled before any function body compiles.

alias tm = t.math
alias missing = my.module   # refuses: no unit in this bundle has that id
infinity, nan

Reserved words for a float's special values: infinity is infinity and nan means "not a number." You can write them on purpose, but arithmetic on them faults instead of spreading a bad value into later math.

f64 top = infinity
f64 bottom = -infinity
f64 more = top + 1.0   # faults: an infinity is not finite
f64 infinity = 1.0   # refuses: infinity is a reserved word

Bug it prevents: a NaN from one bad calculation silently flowing into every number computed after it.

Infinity

A floating point value meaning "larger than any number", which IEEE 754 makes when a result is too big to store or when you divide by zero. In Trident arithmetic never makes one: it faults. You can write f64(infinity), which still orders above every number, but computing with it faults.

f64 top = f64(infinity)
bool above = top > 1.0e308   # true: an infinity keeps its order
f64 far = 1.0e308 * 10.0   # faults: too big for binary64

Bug it prevents: an overflow quietly becoming infinity and then NaN a few steps later.

ingot

The exact, immutable record type: a value carries exactly the listed fields, and every field states a default. A program declares one as ingot Name { Type field = default }, and no statement writes into its value.

Integer join

Trident's one automatic conversion. When two whole numbers of different widths meet inside an operator, they meet at the smallest width that holds both, exactly: an i32 times a u32 is an i64. It never happens on assignment, and floats never mix.

i32 count = 300
u32 scale = 2
i64 product = count * scale   # the two widths meet at i64
i32 bad = count * scale   # refuses: the product is an i64

Bug it prevents: C's signed and unsigned mix-ups, where -1 can compare as larger than 1.

Interface (trait)

An interface, which Rust calls a trait, is a promise that a type has certain methods, so code can work with any type that keeps the promise. Trident has no methods or interfaces, and interface, trait and class are reserved words no program can use as a name.

Interpreter

An interpreter runs a program step by step, reading each instruction and carrying it out, instead of first turning the whole program into machine code. Trident compiles source to a bytecode image, and its virtual machine interprets that image while counting each call's depth and work.

interval

A range of numbers, low to high, that is sure to contain the true answer. Every step rounds outward, so rounding error can never push the answer outside the range.

interval lo = (0, 1)
interval hi = (1, 3)
interval s = lo + hi   # (1, 4)
interval bad = (2, 1)   # refuses: the low end is above the high end
bool less = lo < hi   # refuses: overlapping ranges have no order

Bug it prevents: trusting a floating-point answer whose rounding error has quietly grown larger than the answer.

ip

One IP address: IPv4 like 192.0.2.1 or IPv6 like 2001:db8::1. The type can pin the version, so ip<v4> holds only IPv4. It is neither text nor a number.

ip host = 2001:DB8::1
ip same = 2001:db8::1
bool equal = host == same   # true: one address
ip mapped = ::ffff:192.0.2.1
ip<v4> four = 2001:db8::1   # refuses: not an IPv4 address

Bug it prevents: comparing addresses as text, where "2001:DB8::1" and "2001:db8::1" look like two different machines.

ipprefix

A block of IP addresses: a network address and a length, such as 192.0.2.0/24. A prefix whose address has bits set past its length, such as 10.0.0.1/8, is refused rather than quietly rounded.

ipprefix lan = 192.0.2.0/24
ipprefix six = 2001:db8::/32
ipprefix bad = 10.0.0.1/8   # refuses: bits are set past /8

Bug it prevents: a firewall rule that covers a different range of addresses than its text seems to say.

ISO 8601 and RFC 3339

Two standards for writing dates and times as text, such as 1987-03-05T14:30:00Z: year, month and day, then the time and its offset from UTC. RFC 3339 is a stricter internet profile of ISO 8601. They differ on small points, such as what an offset of -00:00 means.

Iterator

An iterator is the marker a loop uses to walk through a collection one item at a time. Changing the collection mid-walk can crash the loop or make it skip items. Trident's foreach walks a snapshot taken when the loop began, so adding items inside the loop never extends the walk.

i32[] xs = [1, 2, 3]
foreach (x in xs) {
    xs.add(x)
}
assert (xs.count() == 6, "walked_three_items")

Bug it prevents: a loop that never ends because it keeps adding to the list it walks, or a C++ loop reading through an iterator after the list moved.

ivec2, ivec3, ivec4

A group of 2, 3 or 4 whole numbers that may be negative (each an i32), such as a tile's column and row on a game map. Math works lane by lane, and every lane is checked so it cannot overflow.

ivec2 tile = (3, -4)
ivec2 right = tile + (1, 0)
i32 row = tile.y
ivec2 huge = tile * 1000000000   # faults: a lane would overflow

Bug it prevents: a grid position quietly wrapping around to a wrong tile when one of its numbers grows too big.

JIT compilation

JIT (just-in-time) compilation turns code into machine code while the program runs, to speed up the busiest parts. It adds speed and also complexity. Trident has no JIT: it interprets bytecode, and its design refuses a JIT tier until measurements ask for one.

job

A job is a piece of work the host runs in the background for a program, such as a nightly report. MerJob records each job, so it keeps one identity through every retry, whether it runs here or on another machine.

Bug it prevents: a retried task running twice as two different jobs.

JSON

JSON is a common text format for data, written with braces and quotes, like {"name": "Ada"}. It carries no real types, so every program must check what it reads. Trident's documents are typed chests whose field types travel with them, and expect reads one at declared types.

Keel

Keel is MerTek's core library of math and computing routines, written in C. t.math and many other t.* libraries call into it. It is deterministic, so the same inputs give the same answer every time.

Kind(value) conversion

Converts a value by writing the target type like a function call: f32(x), i64(n), text(n). The result is exact, or the program faults and names what would be lost. On text it parses, so i32("42") is 42. This form is called an introducer.

f64 half = 0.5
f32 single = f32(half)   # exact
text digits = "42"
i32 n = i32(digits)      # 42, or a fault
f64 tenth = 0.1
f32 lost = f32(tenth)   # faults: 0.1 is not on the f32 grid

Bug it prevents: a silent cast that drops digits or wraps a number, as C, Java and Go allow. When losing data is the goal, a named call such as t.math.castRounding says so.

Leap second

An extra second, written 23:59:60, that is sometimes added to UTC because the Earth's spin is slightly uneven. 27 have been added since 1972. Trident's civil labels accept second 60 as a leap label and never roll it over into the next minute.

civil leap = 2016-12-31T23:59:60Z   # a real leap second

Bug it prevents: a timestamp of 23:59:60 crashing a date parser or being shifted into the next minute.

lend

A lend is the list the person running a program hands it of what it may use, like a hall pass: libraries and their settings, stores, folders, network endpoints and size limits. An application keeps it in _grant/grant.draft. The host works out what the code needs and checks it against the lend both ways.

Bug it prevents: forgotten permission. A launch stops if the code reaches a library the lend does not cover, or if the lend allows something no code uses.

lex, parse, verify, install (stages)

Every Trident fault names the step where it stopped. lex reads characters into words, parse builds the sentence structure, compile makes bytecode, verify double-checks that bytecode, install loads it into the runtime, and execute runs it. Other steps include grant, where permissions are checked.

Lexicographic order

Ordering the way a dictionary does: compare the first parts, and only if they tie, compare the next ones. "cat" comes before "cub" because the c's match and a comes before u. Trident orders its expansion kinds this way over every lane, so the whole value decides, not only the first lane.

Limb (of a big number)

A big integer stores its digits in fixed-size chunks called limbs. Operand size determines required storage. Trident reserves memory, but it does not count limb products or impose a runtime work budget.

line2, dline2

A line2 is a straight line on a flat surface that goes on forever both ways. It is stored as a normal (which side it faces) and an offset (how far it sits from the origin). dline2 stores more digits.

line3, dline3

A line3 is a straight line in 3D that goes on forever both ways, stored as one point on it and a direction. dline3 stores more digits.

text describe(line3<frame=builtin:itrs> track) {
  point3<frame=builtin:itrs> onIt = track.origin
  dir3<frame=builtin:itrs> heading = track.direction
  return t.text.of(heading)
}
Linking

Linking joins separately built pieces of a program, and its libraries, into one thing that can run. Trident's linking is closed-world: everything is resolved before the program runs, with no search path and no resolver a running program calls, so the whole call graph can be checked first.

Linter and syntax highlighter

A linter reads code and warns about likely mistakes or messy style; a syntax highlighter colors code so it is easier to read. Trident keeps its grammar plain for both: # starts the one line comment, so even a shebang line needs no special case.

Literal

A literal is a value written straight into the code, like 42, 1.5 or "hello". In Trident a literal reads at the type its destination declares, so i64 total = 0 needs no suffix, and a literal that does not fit is refused when the program compiles.

i64 total = 0
f32 ratio = 1.5
u8 level = 300   # refuses: 300 does not fit in a u8
lock, lock?

Runs a block while holding a lock on a named key, under a policy such as exclusive or shared use. The lock is released however the block ends. lock faults if its wait runs out; lock? runs its else arm instead.

text key = "ledger"
chest policy = t.sync.policy.exclusive(0ms, 1s, false)
lock (key, policy) {
    # held here, and released however the block ends
}
lock? (key, policy) { } else { }
lock (key) { }   # refuses: a lock names its policy

Bug it prevents: a lock left held forever because an early return or a fault skipped the unlock.

main

The function where a script starts: text main(text[] argv). Opening or reading a source file runs nothing; code runs only when the runner calls main. argv holds the words typed after -- on the command line.

text main(text[] argv) {
    if (argv.count() == 0) {
        return "no arguments"
    }
    return "first: " + argv[0]
}

Bug it prevents: importing a file only to read it and having its top-level code run, as Python, Ruby or JavaScript allow.

mat2, mat3, mat4 (and dmat2, dmat3, dmat4)

A matrix: a square grid of numbers (2 by 2, 3 by 3 or 4 by 4) that stretches, turns or moves vectors. The @ operator applies a matrix to a vector or chains two matrices, while * works lane by lane. The numbers are listed column by column. The d versions store more digits.

mat2 stretch = (2, 0, 0, 3)
vec2 step = (1, 1)
vec2 stretched = stretch @ step   # (2, 3)
mat2 twice = stretch @ stretch
bool less = stretch < twice   # refuses: a matrix has no order

Bug it prevents: writing * where you meant the matrix product. Here * is always lane by lane and @ is always the product, so the two never get confused.

mat2x3, dmat2x3

A matrix with 2 rows and 3 columns: a flat (2D) turn or stretch plus a move, like placing a sticker on a page. Apply it with @ to a vec3 whose last number is 1 to get the moved vec2. dmat2x3 stores more digits.

mat2x3 slide = (1, 0, 0, 1, 20, 30)   # move by (20, 30)
vec3 spot = (1, 2, 1)
vec2 moved = slide @ spot   # (21, 32)
mat3x4, dmat3x4

A matrix with 3 rows and 4 columns: a turn or stretch plus a move, for 3D. Apply it with @ to a vec4 whose last number is 1, and you get the moved vec3. Its type may name the frame it maps from and the frame it maps to. dmat3x4 stores more digits.

mat3x4 slide = (1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 0, 0)   # move 5 along x
vec4 spot = (1, 2, 3, 1)
vec3 moved = slide @ spot   # (6, 2, 3)
measure

An exact amount with a unit and a kind of quantity attached, such as 90deg:angle or 2m:length. Its math stays exact, even with pi in it, and units must agree. Use it when an answer must be exact, not rounded.

measure quarter = 90deg:angle
measure inRad = t.measure.to(quarter, "rad")   # exactly pi/2
measure d = 2m:length
measure secs = 3s:time
measure bad = d + secs   # faults: meters plus seconds

Bug it prevents: adding meters to seconds, or getting 1.5707963267948966 where the true answer is exactly pi/2.

measurement

A reading and its uncertainty as one value: the estimate, its standard uncertainty (how far off it may be) and the degrees of freedom (how much data that uncertainty rests on). Combining readings is a named call that says how their errors relate.

measurement rod = (1.5, 0.25, 9)
f64 estimate = rod.estimate
f64 spread = rod.standardUncertainty
measurement two = rod + rod   # refuses: a sum must state how errors relate

Bug it prevents: adding two readings and assuming their errors are unrelated, which can understate the real uncertainty.

Memory management

Memory management is how a program gets memory for its data and gives it back. Most languages use a garbage collector, manual frees or ownership rules. Trident uses none of these: there is no free and no pointer, each kind of memory has a limit the host declares, and a scope's reservation is refunded when the scope ends.

MerDB

MerDB is MerTek's database engine. It stores the troves of typed records that t.db reads and writes. browser storage is its storage layer for web browsers, where the browser supplies the storage that a file supplies elsewhere.

MerJob

MerJob is MerTek's durable job ledger: it keeps each scheduled or submitted job as rows in the store, so a job survives a restart and keeps one identity across retries. It holds no clock; every operation takes the time reading its caller passes in.

MerTekTypes

MerTekTypes is MerTek's one table of every kind (data type), with its units, text rules and spellings. Chest, MerDB and Trident all read this one table, so a type means exactly the same thing in a file, a database and a program.

Bug it prevents: several copies of a type table drifting apart, which the previous generation had to police with a test.

Metaprogramming and macros

Metaprogramming is code that writes or changes other code, often while compiling; a macro is a common form that expands into more code first. Trident has none: no macros, no compile-time code and no text splicing like C's #include, so the code you read is the code that runs.

Bug it prevents: a macro that expands into code nobody can see in the source, hiding where a bug lives.

Metric signature

The recipe that picks which geometric algebra you are in: how many basis directions square to +1, how many to -1, and how many to 0. euclid3 is (3,0,0), ordinary 3D space; projective3 is (3,0,1); conformal3 is (4,1,0). Trident puts it in the type, so two signatures never mix.

bivector<euclid3> xy = (0, 0, 1)
dvec3 v = xy   # refuses: a plane of rotation is not a direction
rotor<euclid3> r = (1, 0, 0, 0)   # refuses: that rotor ships as dquat
Migration

A migration is a step that moves stored data from an old shape to a new one, such as renaming a column. In Trident's design each migration step is one file checked when the program compiles, and the final schema must equal the declared one.

Module

A module is one file or unit of code with its own names, so a large program can be split into parts. A Trident module's id is its path under the application root, so a/geometry/units.tri is a.geometry.units, and other modules reach its names through that path.

Modulus

The number you wrap around at in clock arithmetic: on a 12-hour clock the modulus is 12, so 9 + 5 lands on 2. Cryptography does this math with huge prime moduli. A fieldelem carries its modulus in its type, so numbers under two different moduli never mix.

money

An exact amount of one currency, with the currency in the type: money<USD>. The number of decimal places comes from the currency, and two currencies never add.

money<USD> price = 12.50
money<USD> total = price + 1.25   # 13.75, exact
money<EUR> other = 12.50
money<USD> sum = price + other   # refuses: two currencies
money<JPY> yen = 100   # the yen has no decimal places

Bug it prevents: adding dollars to euros because both were plain decimals with the currency kept in a separate field.

Motor

In projective geometric algebra, a motor is a rotor that turns and slides an object in one step, like a screw driven into wood. rotor<projective3> holds one in eight lanes, and two motors combine through the named call t.math.geometricAlgebra.motorCompose, never through @.

f64 tolerance = 0.000000001   # how far from length 1 a motor may drift
rotor<projective3> still = (1, 0, 0, 0, 0, 0, 0, 0)   # no motion
rotor<projective3> twice = t.math.geometricAlgebra.motorCompose(still, still, tolerance)
rotor<projective3> both = still @ still   # refuses: motors combine by a named call
MerTek

The current generation of MerTek is one all-in-one software stack written in C: its data types, document format, database, renderer, programming language (Trident) and the hosts that run them. Each part may depend only on parts below it.

multivector

A multivector holds every piece geometric algebra has at once: a plain number, arrows, plane patches and more. In 3D that is 8 numbers. Its steps, such as the geometric product, are named calls in t.math.geometricAlgebra, never operators.

multivector<signature=euclid3> one = (1, 0, 0, 0, 0, 0, 0, 0)
multivector<signature=euclid3> square = t.math.geometricAlgebra.product(one, one)
bool same = square == one
multivector<signature=euclid3> sum = one + one   # refuses: use a named call
Named argument

A named argument is passed by its parameter's name instead of its position, like area(width: 3, height: 4) in some languages. Trident has none: arguments go in the order the signature lists them, and their count and types are checked when the program compiles.

Namespace

A namespace is a family of names kept apart from others, so two things can share a short name without clashing. In Trident each enum and each module is its own: two enums may both declare Ok, and Status.Ok and Health.Ok stay separate.

enum Status { Ok = 0, Failed = 1 }
enum Health { Ok = 0, Sick = 1 }
Status s = Status.Ok
Health h = Health.Ok
NaN (not a number)

The special floating point value that means "no answer", such as 0 divided by 0 or the square root of -1, and in most languages it quietly spreads through every later step. Trident never computes one: the operation faults instead. You can still write f64(nan) as a stored marker.

f64 zero = 0.0
f64 bad = zero / zero   # faults: no NaN is made
f64 marker = f64(nan)

Bug it prevents: the square root of a negative number quietly making a NaN that turns every later total into "nan".

Narrowing and widening

Widening moves a value into a bigger type, like i32 into i64; narrowing moves it into a smaller one, where it may not fit. Many languages do both silently on assignment. In Trident a destination never converts either way: the program writes i64(n) or i32(n), and a narrowing that loses the value faults.

i32 n = 5
i64 wide = i64(n)
i64 big = 9
i32 back = i32(big)
i32 narrow = big   # refuses: a destination never narrows
Newline ends a statement

A statement ends at the end of its line, or at the } that closes its block. There is no semicolon to type, and ; is refused, so two statements never share one line.

i32 width = 4
i32 height = 3
i32 area = width * height   # one statement per line
i32 depth = 2;   # refuses: no semicolon

Bug it prevents: a second statement hidden at the end of a long line, or C's stray ; after an if, which makes the next block run every time.

Newton and newton second

The newton is the SI unit of force, about the weight of a small apple; a kilonewton (kN) is 1000 newtons. A newton second measures impulse, a push kept up over time. The pound-force second is the US unit for the same thing and equals about 4.45 newton seconds.

f64<force> push = 2kN   # 2000 newtons

Bug it prevents: reading pound-force seconds as newton seconds, the mix-up that lost NASA's Mars Climate Orbiter in 1999.

Nominal typing

Nominal typing means two types are different because they have different names, even when they hold the same fields. Trident's enums are nominal: an enum's identity is minted from the module that declares it, so two enums never mix, even when their members match.

enum Status { Ok = 0, Failed = 1 }
enum Health { Ok = 0, Sick = 1 }
Status s = Status.Ok
Health h = Health.Ok
bool same = s == h   # refuses: two enum types never compare

Bug it prevents: comparing a status code to a health code and getting a silent true, because both were plain integers underneath.

nonfinite

A floating point value that is not an ordinary number: an infinity or a NaN. nonfinite is also the name of the fault Trident raises when one reaches arithmetic or a comparison, so a stored infinity or NaN can be kept and checked but never computed with.

f64 top = f64(infinity)
f64 more = top + 1.0   # faults: nonfinite
normal2, dnormal2

A normal2 is an arrow sticking straight out of a flat edge, like an arrow showing which side of a fence on a map is "outside". It can never be all zeros, and it uses an app's own frame. dnormal2 stores more digits.

Bug it prevents: a zero normal, which points nowhere and breaks every later test of which side something is on.

normal3, dnormal3

A normal is an arrow sticking straight out of a surface, like a pin pushed into a ball. It says which way the surface faces. It can never be all zeros, and its frame is part of its type. dnormal3 stores more digits.

normal3<frame=builtin:itrs> up = (0, 0, 1)
normal3<frame=builtin:itrs> flat = (0, 0, 0)   # refuses: a zero normal points nowhere

Bug it prevents: a zero normal, which points nowhere and turns lighting or bounce math into not-a-number garbage.

Nuclide

One exact kind of atomic nucleus, set by its number of protons and neutrons. Carbon-12 and carbon-14 are two nuclides of the same element: both have 6 protons, but one has 6 neutrons and the other 8.

Null

Null is a special value that means "no value here." Reading a null as if it were a real value is one of the most common crashes in programming. In Trident only a T? type may hold null, and a read is refused until a null test, such as if (x != null), proves the value is present.

Bug it prevents: a null pointer crash from reading a missing value as if it were there.

Number literal

A number written in code, like 42, 1.5, 0xff (base 16) or 1e3 (1 times 10 to the 3rd). It takes the type of the place it lands, so there are no width suffixes like C's L. A number too big for that type refuses.

i64 wide = 3000000000
i32 mask = 0xff   # 255
f32 narrow = 1.5
i8 small = 0xff   # refuses: 255 does not fit in i8

Bug it prevents: a forgotten suffix that silently cuts a number short, or a value too large for its type wrapping around.

obb2, dobb2

An obb2 is a flat box that can be turned to any angle: a center, a turn, and half its size along each of its own edges, like a tilted card on a table. dobb2 stores more digits.

obb, dobb (oriented bounding box)

An oriented bounding box is a box that can be turned to any angle: a center, a turn (a quaternion), and half its size along each of its own edges. It hugs a tilted object more tightly than an axis-aligned box. dobb stores more digits.

text describe(obb<frame=builtin:itrs> crate) {
  quat tilt = crate.rotation
  vec3<family=length> halfSize = crate.halfExtents
  return t.text.of(halfSize)
}
Oklab and OkLCh

Color spaces published in 2020 and built so that equal steps in the numbers look like equal changes to the eye. OkLCh describes the same colors as lightness, chroma (how colorful) and hue (the angle around the color wheel), which helps when picking smooth gradients and palettes.

on configure

A handler in the entry file that runs at most once, before any store opens, and returns the settings the launch adds. A mistake there stops the launch cleanly instead of leaving a half-set-up program running.

Bug it prevents: a wrong setting read from an environment variable that only breaks at whichever line first uses it.

on endpoint

An endpoint declared as its own file, whose name gives its method and route, handled by on endpoint with typed path and query parts. It replaces on request.

on error

Sets up a handler for the statements after it in the same block. If one of them faults while running, the handler runs and can retry, resume or fail. Mistakes found before the program runs never reach a handler.

i32 zero = 0
text trail = "start"
on error (caught) {
    trail = "handled"
    resume
}
i32 lost = 1 / zero

Bug it prevents: a handler swallowing a mistake that should have stopped the program; only real run-time faults can be caught.

on event

Declares the handler for a named event that the system running the program (the host) delivers, such as "Damage", along with its payload's type. Each event name gets exactly one handler.

on event("Damage", const i32 amount) {
    i32 doubled = amount * 2
}
on event("Damage", const i32 hits) { }   # refuses: one handler per name

Bug it prevents: two handlers registered for one event, with the order they fire in left to chance.

on exit

Declares cleanup that runs exactly once whenever the block is left, by a normal return or by a fault. Several run newest first. Cleanup cannot return, so it can never change the answer.

text trail = "start"
on exit {
    trail = trail + "|exit"
    return "early"   # refuses: cleanup cannot change the answer
}
trail = trail + "|body"

Bug it prevents: Java's return inside finally, which silently throws away the real result or an error in flight.

on job

Declares the handler for a named background job. Each job runs as one call with set limits, on its own worker, sharing nothing with other jobs.

on job("sumSamples", const chest context, const chest input) {
    return input
}
text main(text[] argv) {
    return "ok"
}
on request

Declares a web endpoint and its handler in the language itself. The compiler checks the route, the method and each parameter's type, so the {id} in a path arrives already as a number. A duplicate or mistyped route refuses before the program runs.

on request({ method = "GET", pattern = "/items/{id}",
    parameters = [{ name = "id", source = "path",
        typeSpelling = "u64" }] }, const p) {
    u64 id = p.id
    chest out = {}
    out.id = id
    return out
}

Bug it prevents: a routing mistake, like two handlers for one path, that only shows up when a real request hits it in production.

operator profile

An operator profile is a document, written by the person who runs a program (the operator), that lists which web servers and settings outbound HTTP may use. A script names a profile and its digest on the command line; it can never describe one itself.

Bug it prevents: code widening its own network access by editing a list of addresses it controls.

Operator spacing (whitespace law)

Trident reads spacing to tell what an operator is. A two-sided operator has a space on each side, as in 7 - 5, and a one-sided one is glued to its value, as in -x. That is how 5m/s stays one unit while a / b divides.

i32 d = 7 - 5
i32 neg = -d
i32 bad = 7-5   # refuses: write 7 - 5

Bug it prevents: code that a reader takes one way and the compiler another. A glued minus refuses and names the spaced form.

ORM

An ORM (object-relational mapper) is a library that lets code treat database rows as ordinary objects. Trident's typed database is its t.db linked library, and a trove declaration gives each row's columns a type.

outer assembly

An outer assembly (also called a composition root) is one finished program file that MerTek builds by linking a chosen set of its parts: tricli, tri, desktop, browser, xr, worker and render. All of them run the same Trident; they differ in which libraries and abilities they include.

outline

An open record type: the fields a value must carry, with extra fields allowed. A function taking an outline accepts any record that has those fields. Outline declarations, include rows, checks, and module-path reaches work.

Outward rounding

Rounding an interval's low end down and its high end up, so the range still contains the true answer after rounding. The interval may get a little wider, but it is never wrong. Trident works out that rounding from an exact remainder, without touching the processor's rounding mode.

Overflow

When an answer is too big for the space it has to fit in, like a car's odometer rolling from 999999 back to 000000. Many languages quietly wrap around to a wrong number. Trident faults instead, for whole numbers and floats alike; overflow is that fault's name, and an on error handler can catch it.

i32 big = 2147483647
i32 over = big + 1   # faults: overflow

Bug it prevents: a counter or a balance silently wrapping from the largest value to a negative one.

Overloading

Overloading lets several functions share one name and differ by their parameters, and the compiler picks one for each call. Trident has no overloading: a name is one function with one signature, and a call matches it or is refused when the program compiles.

i32 size(i32 n) { return n }
i32 size(i64 n) { return 1 }   # refuses: one name is one function
Ownership and borrowing

Ownership is Rust's memory rule: each value has one owner that frees it, and other code may only borrow it for a while, checked by the compiler. Trident does not use it: from a program's view every copy is its own value, and memory is reserved against declared limits instead.

P-256

A standard elliptic curve, a curve whose points can be "added" together, used to secure websites and passkeys. It comes with two huge numbers: the prime its coordinates are computed modulo, and the group order, the number of points on the curve. Math under one must never be mixed with the other.

Package manager and registry

A package manager downloads libraries other people published to a public registry and installs them into a project. A running Trident program has no package manager or registry to call. tripkg, still prelaunch, fetches a library and checks its exact bytes before a launch, never during one.

Parsing (lexer and parser)

Parsing reads text and works out its structure, the way you might diagram a sentence. A lexer first splits code into tokens, and then a parser builds the structure from them. Trident names the stage that refused a program (lex, parse or compile), and a parse refusal names what it found.

Partial application

Partial application calls a function with only some of its arguments and gets back a new function that waits for the rest, a habit of Haskell. Trident has no function values, so a call that leaves out an argument is refused when the program compiles.

Pattern matching and destructuring

Pattern matching checks a value against a shape and, in the same step, pulls its parts out into names, which is called destructuring. Trident has a partial form: expect reads named fields from a document at declared types and binds them, or takes its else arm if any field is missing or mistyped.

chest doc = { text name = "Ada", i64 age = 36 }
expect { text name, i64 age } = doc else { return "missing" }
i64 next = age + 1
PHC string

A standard one-line text format, from the Password Hashing Competition, for a stored password check: the method (such as Argon2id), its cost settings, a random salt and the resulting hash. It never contains the password itself. Trident's pwhash kind is written this way.

phrase

Declares a translatable message. A call like welcome(user.name, unread) builds a value that carries the message and its inputs, so each reader sees it in their own language. A retired phrase keeps old values readable.

Physical constant

A fixed number of nature, like the speed of light. Some are exact by definition (the meter is defined so that light travels exactly 299,792,458 meters per second), while others are measured, carry an uncertainty, and are republished in new editions as labs improve.

plane, dplane

A plane is a flat surface that goes on forever, like an endless floor. It is stored as a normal (the way it faces) and an offset (how far it sits from the origin). dplane stores more digits.

text describe(plane<frame=builtin:itrs> floor) {
  normal3<frame=builtin:itrs> facing = floor.normal
  f32<family=length> distance = floor.offset
  return t.text.of(facing)
}
point2, dpoint2

A point2 is a spot on a flat surface, like a map or a game board, measured in an app's own frame. Every built-in frame is three-dimensional, so a flat point refuses them. As with point3, two points cannot be added. dpoint2 stores more digits.

# an app's own frame, written by its long ID
point2<frame=static:0000000000000000000000000000000000000000000000000000000000000001:1> spot = (1, -2.5)
point2<frame=builtin:itrs> lost = (1, -2.5)   # refuses: a flat point needs an app's own frame
point3, dpoint3

A point3 is a spot in 3D space, in meters from the origin of a named frame, and the frame is part of its type. Two points cannot be added, because "home + school" means nothing. dpoint3 stores more digits.

point3<frame=builtin:itrs> home = (1, 2, 3)
point3<frame=builtin:itrs> school = (4, 5, 6)
bool same = home == school
point3<frame=builtin:itrs> nonsense = home + school   # refuses: a point plus a point means nothing
point3<frame=builtin:icrs> sky = home   # refuses: a different frame

Bug it prevents: adding a point to a point, or using a camera-space point as a world-space point. Both refuse before the program runs.

Pointer

A pointer is a value that holds the memory address of other data, like a note saying which locker to open. Pointers are fast but risky, because they can point at freed or wrong memory. Trident has no pointer type at all, so a program never holds a raw address.

pose, dpose

A pose is where something is and which way it faces: a move (in meters) plus a turn (a quaternion). Its type names two frames, from and to. dpose stores more digits.

pose<from=builtin:itrs,to=builtin:icrs> arm = (1, 0, 0, 0, 0, 0, 1)
vec3<family=length> offset = arm.translation
quat facing = arm.rotation
pose<from=builtin:itrs,to=builtin:icrs> broken = (1, 0, 0, 0, 0, 0, 0)   # refuses: an all-zero turn faces nowhere

Bug it prevents: a pose whose turn is all zeros, which faces no direction at all and turns every later calculation into garbage.

Precondition and postcondition

A precondition is what a function requires to be true when it is called; a postcondition is what it promises about its result. Trident writes them as require and ensure clauses on the signature. They always run, and a broken one is a catchable fault naming the clause and the function.

Bug it prevents: a function quietly returning a wrong answer for an input it was never meant to take.

prelaunch

Before MerTek's first public release. In this period the language's rules can change, because no outside program depends on them.

private

Keeps a function, constant, enum, flagset, almanac, trove, outline, ingot or named type inside the file that declares it. Other files cannot reach it, and the runner cannot call a private function.

private const i32 limit = 3
private i32 helper() {
    return limit
}
private alias m = t.math   # refuses: private has no place on an alias
i32 got = helper()

Bug it prevents: another file starting to depend on a helper that was meant to stay internal.

Product rule and chain rule

Two rules of calculus: the product rule gives the rate of change of two changing things multiplied together, and the chain rule gives it for one function inside another, like the area of a circle whose radius is growing. Forward-mode arithmetic applies both at every step.

program bundle

A program bundle is the exact, ordered list of source files a host compiles into one program. Each row holds a module id, the source text and its SHA-256 fingerprint. Names are looked up only inside the bundle, so there is no search path.

Bug it prevents: a program picking up a stray file that happens to have the right name in some folder on a search path.

programReplace, childSpawn

Rows a session program can return to ask its host for a change. programReplace asks the host to compile named source and replace the program between calls; childSpawn asks it to start a separate, narrower child runtime. The host acts only after the call returns, so a program never swaps its own code.

Projective and conformal geometric algebra

Two larger geometric algebras for 3D. The projective one, signature (3,0,1), adds a direction that squares to zero, so one rotor can turn and slide at once; the conformal one, (4,1,0), adds two more, so spheres and circles become elements too. Robotics and graphics use both.

Proleptic Gregorian calendar

Our everyday calendar, the Gregorian one adopted in 1582, used for dates before it existed, as if it had always been in force. Every past date then gets one clear label, without switching to older calendar rules.

civildate old = 1000-03-01   # before 1582, by today's rules
provider grant

A provider is a service the host plugs in, such as a clock, a log, a job runner or a key store. A provider grant admits one by name. t.clock, t.log, t.job and t.security.crypto each need one, so a program reads the time or a key only when the host chose to provide it.

pwhash

A stored password check (an Argon2id hash): the settings, random salt and hash that a login attempt is tested against. It cannot be compared with ==, sorted or turned into text; checking a password is a named library call. Its literal stays quoted, because the stored text opens with $, which starts no token, and only the hashing call ever writes one.

bool same(pwhash left, pwhash right) { return left == right }   # refuses: never compared with ==
text show(pwhash stored) { return text(stored) }   # refuses: never turned into text
bool missing(pwhash? stored) { return stored == null }

Bug it prevents: a password hash written to a log, or checked with a plain == that can never match a fresh hash.

quantity

A physical amount with its unit, whose type also names how the number is stored, such as quantity<amount=f64>. Sums check that the dimensions match, and products build new ones: meters times meters is square meters.

quantity<amount=f64> d = 2.5m:length
quantity<amount=f64> area = d * d   # square meters
quantity<amount=f32> small = 2.5m:length
quantity<amount=f64> bad = d + small   # refuses: two number formats

Bug it prevents: adding a 32-bit amount to a 64-bit one and silently rounding one of them.

quat, dquat (quaternion)

A quaternion: four numbers (x, y, z, w) that together describe one turn in 3D space. The @ operator chains two turns, and a quat @ a vec3 turns the vector. dquat is the twin that stores more digits; it chains turns with @, and turning a dvec3 is a named call.

quat quarterTurn = (0, 0, 0.7071068, 0.7071068)   # 90 degrees around z
vec3 east = (1, 0, 0)
vec3 north = quarterTurn @ east
quat halfTurn = quarterTurn @ quarterTurn
quat wrong = quarterTurn * quarterTurn   # refuses: four lanes are one turn

Bug it prevents: a quaternion multiplied lane by lane. Most vector libraries run it, and the result is a turn that is slightly wrong and drifts over time.

Radian

A way to measure angles using the circle's own radius: a full turn is 2π radians (about 6.28), so a right angle is π/2. Math functions like sine and cosine expect radians, which is why mixing them up with degrees is a classic mistake.

measure quarter = 90deg:angle
measure turn = t.measure.to(quarter, "rad")   # exactly pi/2

Bug it prevents: passing 90, meant as degrees, to a function that reads it as 90 radians, more than 14 full turns.

Rank (of a tensor)

How many indexes it takes to find one number in a tensor. A single number is rank zero, a list is rank one, a table (a matrix) is rank two, and a stack of tables is rank three.

rational

An exact fraction such as 1//3, always kept in lowest terms. Use it when a fraction must stay exact, as in musical timing or a recipe scaled by thirds.

rational third = 1//3
rational half = 1//2
rational sum = third + half   # 5//6, exact
rational same = rational(2//4)   # stored as 1//2
rational zero = 2//0   # refuses: zero denominator

Bug it prevents: 1/10 + 2/10 coming out as 0.30000000000000004 instead of exactly 3/10.

ray2, dray2

A ray2 is a flat ray: a starting point plus a direction, going on forever one way across a flat surface, like a flashlight beam on a map. It uses an app's own frame. dray2 stores more digits.

ray, dray

A ray is a starting point plus a direction, going on forever one way, like a laser beam. Games use rays to ask "what does this line of sight hit?" Its direction cannot be zero. dray stores more digits.

text describe(ray<frame=builtin:itrs> beam) {
  point3<frame=builtin:itrs> start = beam.origin
  dir3<frame=builtin:itrs> aim = beam.direction
  return t.text.of(aim)
}

Bug it prevents: a ray with a zero direction, which points nowhere and makes every hit test answer nonsense.

Rec. 2020, Rec. 2100, scRGB, ACEScg and CMYK

More color spaces: Rec. 2020 and Rec. 2100 are the standards for ultra-HD and high dynamic range (HDR) television, with far more colors than sRGB, and scRGB stretches sRGB past its usual 0 to 1 range. ACEScg is the film industry's working space for rendering. CMYK describes printing ink: cyan, magenta, yellow and black.

rect, drect

A rect is a flat rectangle lined up with the x and y directions, stored as its lowest corner and its highest corner, like a window on a screen. drect stores more digits.

Reference counting

Reference counting frees memory by counting how many places use each value and freeing it when the count reaches zero; a cycle collector cleans up values that only point at each other. A Trident program never sees a count, though inside the runtime a shared block of bytes stays charged until its last holder lets go.

Reflection

Reflection lets a program look at its own structure while it runs: list a type's fields, find a function by its name, or inspect the runtime's state. Trident has no reflection over the runner's own state, and a program's whole call graph is fixed before its first line runs.

refusal

A refusal is Trident saying no to a program, with a message that points at the exact spot and, when it can, the exact edit that fixes it. Most refusals happen at compile, before anything runs; others stop a launch before main, or arrive as faults while the program runs.

text digits = "12"
i32 bad = digits   # refuses: the message names i32(digits)
i32 n = i32(digits)

Bug it prevents: an error you have to look up or decode. This one names the line to write, i32(digits), instead of an error code.

Release build and debug build

Many languages build a program two ways: a debug build with extra checks for testing, and a release build that drops checks to run faster, so the program you ship is not the one you tested. Trident's checks are the same in every build: contract clauses always run, and overflow always faults.

i32 x = 3
assert (x == 3, "x_is_three")
assert (x == 4, "x_is_four")   # faults: an assert runs in every build

Bug it prevents: code that stopped on overflow in testing and silently wrapped in the shipped release.

repeat

Runs a block a set number of times, like repeat (3). An optional counter, repeat (5, i32 i = 0), can be read but never changed. Every loop states its limit before it starts; a search that may stop early uses a bound and break.

i32 seen = 0
repeat (3) { seen++ }
repeat (5, i32 i = 0) {
    seen += i
}
repeat (3, i = 0) { seen++ }   # refuses: the counter states its type

Bug it prevents: a loop that never ends because its condition never turns false.

REPL

A REPL (read-eval-print loop) is an interactive prompt where you type a line of code and see its result at once. tricli has one, but the host itself compiles and installs each line, never a call the program can reach, so even the REPL cannot make a program run another program.

replay

Replay runs a program again against a tape, answering every outside read from the recording and comparing each result with what happened the first time. The recording and replay pieces exist.

require, ensure, result

Promises written on a function's signature. require checks the arguments when a call starts; ensure checks the answer, named result, at each return. A broken promise raises a fault naming the clause, and the checks always run, release builds included.

i32 halve(i32 n) require (
        n > 0, "positive") ensure (
        result <= n, "no larger") {
    return n / 2
}
i32 four = halve(8)
# halve(-2) faults: require 'positive' failed in halve

Bug it prevents: a safety check that silently disappears in a release build, as assert does in C, Java and Python.

Resampling

Making new samples of a field at different places than the old ones, by estimating values in between the known samples, like resizing a photo.

Reserved words

Words the language keeps for itself, like if, return and null, so no variable can use them as a name. Future keywords are a second list, such as fork and while, kept for features that plan rows own or for other languages' habits.

i32 count = 1
i32 if = 1   # refuses: a reserved word is not a name
i32 fork = 1   # refuses: a future keyword is not a name

Bug it prevents: a variable named after a word that a new feature turns into a keyword, which breaks the program.

resume

Used inside an on error handler: carries on with the statement after the one that faulted, so a handler can repair things and continue instead of giving up the whole block.

i32 zero = 0
text trail = "start"
on error (caught) {
    resume
}
i32 lost = 1 / zero
trail = "carried on"
resume   # refuses: only a handler can resume
retired

A retired enum, flagset, or almanac member remains readable in supported predicates and stored values, but new code cannot construct it.

enum Plan { Free = 0, retired Trial = 1, Paid = 2 }
Plan p = Plan.Paid
bool wasTrial = p == Plan.Trial
Plan q = Plan.Trial   # refuses: a retired member is never chosen

Bug it prevents: deleting an option and breaking every stored record that still holds it, or new code quietly writing an option that is being phased out.

retry

Used inside an on error handler: runs the protected statements again from the start, for example to give a flaky step a few more tries.

i32 tries = 0
i32 zero = 0
on error (caught) {
    if (tries < 3) { retry }
    resume
}
tries++
i32 lost = 1 / zero   # the handler retries until tries is 3

Bug it prevents: retry logic hidden in a while (true) loop around a try, where a reader has to guess the intent.

return

Ends a function and hands back its result. A function that declares a result must return one, and a void function must not return a value.

i32 twice(i32 n) {
    return n * 2
}
void note(i32 n) {
    return n   # refuses: a void function returns nothing
}
i32 four = twice(2)

Bug it prevents: a function that runs off its end and hands back garbage. In Trident that faults instead.

Rigid motion

Moving an object without bending or stretching it: a turn plus a slide. Every distance and angle inside the object stays the same, the way a chair stays a chair as you carry it across a room. Trident's rigid carriers (pose, twist and wrench) describe such a motion, its speed and the push behind it.

rotor

A rotor is geometric algebra's way to write a turn, or a turn plus a slide (called a motor). Its signature, in the type, names which kind of space it lives in. In plain 3D space a rotor is exactly a quaternion, so that spelling refuses and you use dquat.

rotor<signature=projective3> still = (1, 0, 0, 0, 0, 0, 0, 0)
rotor<signature=projective3> both = t.math.geometricAlgebra.motorCompose(still, still, 0.000000001)
rotor<signature=euclid3> plain = (1, 0, 0, 0)   # refuses: in plain 3D, use dquat

Bug it prevents: combining a turn from one kind of space with a value from another. The signature is part of the type, so the mix refuses.

Round half to even

A rule for ties: when a value sits exactly halfway between two choices, pick the even one, so 2.5 rounds to 2 and 3.5 rounds to 4. Ties go up half the time and down half the time, so long sums do not drift. In Trident, nearestEven is a snap policy a number type can state.

i32<multiple=4,snap=nearestEven> step = 0
i32 x = 10
step = x   # 10 is 2.5 steps of 4, so it lands on 8
assert (step == 8, "the tie went to the even step")
Rounding

Rounding replaces a number with a nearby one that fits, like 2/3 becoming 0.67. Done silently, small rounding errors can pile up into big ones. Trident never rounds inside an assignment: f32(x) is exact or faults, and a program that wants rounding names it with t.math.castRounding.

f64 wide = 0.1
f32 near = t.math.castRounding(wide, "f32")
f32 quiet = wide   # refuses: a rounding is never silent
f32 strict = f32(wide)   # faults: 0.1 has no exact f32 value

Bug it prevents: a rounded tenth of a second that adds up over a hundred hours, as in the 1991 Patriot missile failure.

Rounding mode

The rule for which nearby number to keep when an exact answer does not fit: the nearest one, or always toward zero, up or down. Many languages keep one hidden setting on the processor that every calculation reads. Trident has no such hidden setting; a call that rounds says how.

Bug it prevents: an interval library flipping the processor's rounding setting and silently changing unrelated math on the same thread.

Routing

Routing decides which code answers a web request, based on its path and method, like a mail sorter. Most languages leave it to a framework that finds a mistake only when a request arrives. Trident checks its on request declarations when the program compiles, so a duplicate route or a wrong parameter type is refused.

Bug it prevents: two handlers claiming the same route, found only when a real request hits it in production.

rune

One Unicode scalar: a single character code, written like U+0041 for "A". Runes have an order but no math, because a character plus one is not always a character.

rune first = U+0041
rune second = U+0042
bool before = first < second
rune next = rune(u32(first) + 1)   # the step is written out
rune bad = first + 1   # refuses: a character is not a number

Bug it prevents: stepping a character into a code that is not a real character, which breaks the text around it.

runtime

The runtime is the engine inside a host that holds one installed Trident program and runs its calls, counting every byte and step against the host's limits. More loosely, "at runtime" means while a program is running, as opposed to when it is compiled.

s/

The folder for server-only code in an application that has a server and a client. Code under s/ builds only into the server. The client reaches it only with send, which the compiler checks against the server's handle by name and type.

Bug it prevents: server-only code or secrets shipping inside the app that customers or players download.

Saturation

Handling a too-big answer by stopping at the largest value that fits, like a speedometer needle pinned at the top, instead of wrapping around. Trident never saturates on its own. When clamping is what you want, t.math.castSaturating says so out loud.

i64 big = 5000000000
i32 top = t.math.castSaturating(big, "i32")   # 2147483647
i32 fits = i32(big)   # faults: it does not fit
Scalar

In math, a scalar is a single plain number, as opposed to a vector or a matrix. A scalar factor scales every part of a value by the same amount, like doubling every ingredient in a recipe.

Schema

A schema describes the shape of some data: which fields it has and the type of each. A Trident chest carries its schema in its own bytes, and a trove declares its columns, so a misspelled column is refused when the program compiles.

Scope

A name's scope is the part of the program where it can be used, usually the block or file it was declared in. In Trident a name resolves only inside the unit that wrote it, a block's local ends with its block, and a scope's memory reservation is refunded when the scope ends.

script mode

Script mode runs one.tri file: tricli main.tri compiles exactly that file as module a.main and calls main(text[] argv). The person who writes it is the person who runs it, so most local libraries are granted, but the network and saved credentials stay off unless the command line names them.

Bug it prevents: a downloaded script reaching the internet only because you ran it. Network access needs an explicit --http profile and its digest.

Search path and resolver

A search path is a list of folders a language looks through to find a module by name, and a resolver (or solver) picks which version of each library to use. Both decide at the last moment what code you get. A running Trident program has neither: the runtime never opens a source file.

secret

Marks a value, like a password or a key, that must never reach an ordinary output, such as a log or a plain variable. Anything computed from a secret is secret too.

private i32 digits(secret text pin) {
    secret text kept = pin
    text shown = pin   # refuses: a secret reaches a plain local
    return 4
}

Bug it prevents: a password copied into an ordinary string and then printed, logged or sent back by mistake.

segment2, dsegment2

A segment2 is a straight piece of line on a flat surface, from a start point to an end point, like a wall on a floor plan. It uses an app's own frame. dsegment2 stores more digits.

segment3, dsegment3

A segment3 is a straight piece of line in 3D, from a start point to an end point, like a stick. dsegment3 stores more digits.

text describe(segment3<frame=builtin:itrs> stick) {
  point3<frame=builtin:itrs> first = stick.start
  point3<frame=builtin:itrs> last = stick.end
  return t.text.of(first)
}
send, handle

In an app with a client and a server, send calls a server handle by name, and the compiler matches the two, so a mismatched or dead call refuses before the program runs. retract takes back a row that clients have cached.

sensitive

Marks private data, like a session token, that code may still use and compare but must never write to a log. Values computed from it stay sensitive. It is one level below secret.

sensitive text token = "session-token"
bool same = token == "session-token"
sensitive text label = `token={token}`
t.log.info(label)   # refuses: a sensitive value never reaches a log

Bug it prevents: a user's token or personal details ending up in log files that many people can read.

Serialization

Serialization turns a value into bytes that can be saved or sent, and deserialization turns the bytes back. Usually a separate library does it, and the bytes carry no checked types. A Trident chest is already a typed, self-describing document, so it keeps its types after it leaves the program.

session

A session is a long-running host loop, as in tri or the desktop host, that keeps one runtime open and keeps delivering events, input and frames to the program. A one-shot run, by contrast, calls main once and exits.

sha256

A SHA-256 digest: a 32-byte fingerprint of some bytes, written as 64 hexadecimal digits. Use it to check that a file or message has not changed. Its literal stays quoted, because 64 digits with no separator would read as a name or a numeral.

buffer data = 0x616263
sha256 digest = t.buffer.sha256(data)
sha256 known = "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD"
bool same = digest == known   # true: case does not matter
bool first = digest < known   # refuses: a digest has no order

Bug it prevents: comparing a digest written in uppercase letters with the same one in lowercase and calling them different.

Shader

A shader is a small program that runs on a graphics card to draw pixels, shapes and light. Thalassa's shader language is Glint, which has its own companion comparison page.

Shadowing

Shadowing happens when a new name hides an older one with the same spelling, so the older one cannot be reached there. In Trident two enums may both declare Ok and neither shadows the other, because each member is reached through its own enum: by its enum's name, or bare where the spot already expects that enum.

Shared mutable data

Shared mutable data is one piece of data that several parts of a program can reach and change, so a change made through one name shows up through another. Trident has none: every copy is a value, so changing one copy never changes another.

i32[] mine = [1, 2]
i32[] yours = mine
yours.add(3)
assert (mine.count() == 2, "mine_kept_its_own_copy")

Bug it prevents: a function that changes a list it was handed, quietly changing the caller's list too.

SI (International System of Units)

The world's shared measuring system. Its seven base units are the meter, kilogram, second, ampere, kelvin, mole and candela, and every other unit, like the newton or the watt, is built from them. Each base unit is fixed exactly by a constant of nature.

Signed and unsigned integers

A signed integer can be negative; an unsigned one cannot, so it reaches higher with the same bits, like u8 holding 0 to 255. Mixing the two is a classic trap. In Trident i32 and u32 meet at i64 inside an operator, and a mix no width can hold is refused.

i32 debt = -3
u32 stock = 4
i64 net = debt + stock
u128 huge = 5
i64 bad = debt + huge   # refuses: no width holds both

Bug it prevents: C finding -1 bigger than an unsigned 1, because -1 was silently read as 4294967295.

Signed zero

Floating point numbers keep a plus or minus sign even on zero, so +0.0 and -0.0 are two values. Usually you cannot tell them apart, but for a complex number the sign says which side of a branch cut it sits on, so a square root or a logarithm answers differently.

complex128 n = -4.0i   # (-0.0, -4.0)
complex128 s = 0.0 - 4.0i   # (+0.0, -4.0)
bool same = n == s   # false: the zeros have different signs
slab

Declares state the system keeps from one game tick to the next as fixed-size rows, such as game units or terrain cells, and says how much of it a client may see.

Smith's algorithm

A careful way to divide complex numbers, published by Robert Smith in 1962. It divides by the larger part of the divisor first, so the in-between numbers do not overflow or lose digits when one part is huge or tiny. Trident's complex division uses it.

sphere, dsphere

A sphere is a ball: a center point and a radius in meters, which is never negative. dsphere stores more digits.

text describe(sphere<frame=builtin:itrs> ball) {
  point3<frame=builtin:itrs> middle = ball.center
  f32<family=length> size = ball.radius
  return t.text.of(size)
}
sRGB

The standard color space of the web and most screens since 1996. Its numbers are not proportional to light: they follow a curve, its transfer function, because eyes notice changes in dark shades more than in bright ones.

Standard library

A standard library is the set of ready-made tools that ships with a language, like text, math and file functions. Trident's are its t.* linked libraries, part of the language's own catalog rather than installed packages, and a program can call one only when the host allows it.

Standard uncertainty

How far a measured value might be off, given as one typical spread (one standard deviation). A rod measured as 1.5 m with a standard uncertainty of 0.25 m is likely within about 0.25 m of 1.5. A Trident measurement carries it beside the estimate, and it can never be negative.

measurement rod = (1.5, 0.25, 9)   # estimate, uncertainty, degrees of freedom
measurement bad = (1.5, -0.25, 9)   # refuses: an uncertainty is never negative

Bug it prevents: a negative uncertainty being accepted and then squared into a variance that looks valid.

state3, dstate3

A state3 is a snapshot of something moving: a moment in time, where it is, and how fast it moves at that moment. Its type names the frame and the time scale (such as tt, one astronomers use). Satellites are tracked this way. It is written as a nested tuple. dstate3 stores more digits.

state3<frame=builtin:gcrs,domain=tt> satellite = (1700000000, (7000000, 0, 0), (0, 7500, 0))

Bug it prevents: pairing a position from one moment with a speed from another, because the time stamp travels inside the value.

Static initialization order

In C++, constants in different files can run setup code before main, in an order the language never defines, so one constant may read another that is still zero. Trident's module constants must be literals, so no code runs before main at all.

const i32 base = 10
const i32 doubled = base * 2   # refuses: a module constant is a literal
Static typing

A statically typed language checks every value's type before the program runs, so putting text where a number belongs is caught early; the tool that checks is a type checker. Trident is statically typed all the way: every binding states its type, and a mismatch is refused when the program compiles.

text digits = "12"
i32 count = i32(digits)
i32 wrong = digits   # refuses: text never becomes an i32 by assignment

Bug it prevents: the text "12" plus 1 printing 121, as JavaScript does.

stop, on stop

stop asks the system running the program to end it once the current call returns; the rest of that call, and its cleanup, still finish. An on stop handler sees every exit, and its cause shows when the program asked to stop.

void closeLaunch() {
    stop
}
closeLaunch()
text after = "this line still runs"
stop 1   # refuses: stop takes no operand
Stop-the-world pause

A stop-the-world pause is when a garbage collector freezes the whole program for a moment to clean up memory, which can make games stutter and servers lag. Trident has none: nothing ever collects, because memory is reserved and refunded on a budget instead.

stream

A reference to a large block of data kept somewhere else: the name of where it lives, a starting offset and a length. It does not hold the data itself, so a huge file can be passed around cheaply. Its literal stays quoted, because its value text is a call's shape around a quoted name.

stream clip = "stream(\"video\",offset=0,length=10,extent=100)"
stream copy = clip
Struct

A struct is a named group of fields stored together, like a form with labeled boxes. In C, Go or Rust each struct declaration is its own type. Trident's records are chest documents that carry their own field types, and a trove declares a fixed set of columns.

Structural typing

Structural typing treats two types as the same when they have the same shape (the same fields with the same types), whatever they are named. TypeScript works this way. Trident's record kinds outline and ingot are structural; outline and ingot declarations work.

Symbol table

A symbol table is the compiler's list of every name in a program and what each one refers to. A Trident enum's identity does not depend on it: it is 32 bytes minted from the declaring module, so it means the same thing outside the program.

Symmetric and triangular matrices

Shapes a square matrix can have: symmetric means it mirrors across its diagonal, lower triangular means every entry above the diagonal is zero, and upper triangular means every entry below it is. A Trident tensor can declare one of these as its structure, and every value is checked against it.

t.* libraries

Trident's built-in libraries all have names that start with t., such as t.math or t.log. Each is linked into the program that runs Trident, not downloaded. A program may call one only if its grant admits it, so everything a program can touch is on one list.

Bug it prevents: a program quietly using a library nobody approved, such as the network.

t.buffer

Immutable byte buffers: raw bytes that are not text. It builds and joins them, checks signatures, and computes hashes such as SHA-256, a 64-character fingerprint that changes if any byte changes.

buffer bytes = t.buffer.fromText("hello")
sha256 digest = t.buffer.sha256(bytes)
text shown = text(digest)

Bug it prevents: treating raw bytes as text, or text as bytes, without saying how they are encoded.

t.clock

Reads the time: t.clock.now() for the calendar clock and t.clock.monotonic() for a timer that never jumps backward. It is the only way a program learns the time, and only when the host provides a clock, so a test or a replay can supply a fixed time instead.

Bug it prevents: code that secretly reads the current time and so gives a different answer every run, making bugs impossible to reproduce.

t.code

Compiles Trident source that the program already holds as text and returns a checked report: the program's digest, the catalog it was checked against and its entry module. It never runs the result; only the host can install and run a program.

chest image = t.code.compile("i32 twice(i32 n) { return n * 2 }")
text entry = image.entryModule
assert (entry == "a.main", "the entry module")

Bug it prevents: eval running code the instant it is produced, with full access to everything the caller can touch.

t.collide

Collision geometry: whether a point sits inside a shape, whether two shapes overlap, where a ray hits, and how far apart two shapes are. Every answer comes out the same way on every run, so a game or simulation can be replayed exactly.

t.contract

A library that checks a document against a shape described while the program runs, for data whose fields no program declares, such as a file from outside. The provider member named on this page belongs to an earlier design that was withdrawn; a rule the application must decide comes back as a request the program answers.

t.db

The typed database library: read and write rows, run queries and follow a table's change feed, through a store slot the host grants. Each kind of action needs its own access (read, write, query and so on), so a program granted only read access cannot write.

Bug it prevents: a report meant only to read data accidentally deleting rows.

t.edit.action

The editing engine behind Trident's editor. It applies changes to source code, or to a code graph, as all-or-nothing steps with undo and redo. Like t.code, it never runs the code it edits.

t.finance.fx

Currency conversion. t.finance.fx.convert changes an amount from one currency to another at a rate whose type names both currencies, rounding once in the way you choose. Assigning euros to a dollar variable refuses instead.

Bug it prevents: euros quietly becoming dollars through an assignment, or a rate applied backwards; both stop the program from building.

t.finance.money

Exact money math and the list of world currencies. t.finance.money.allocate splits an amount by weights so the parts always add back up to the total, with the leftover cent placed on purpose instead of lost to rounding.

money<USD> total = 10.00
money<USD>[] parts = t.finance.money.allocate(total, [1, 1, 1])
assert (parts[0] + parts[1] + parts[2] == total, "no cent lost")

Bug it prevents: splitting $10.00 three ways as $3.33 each and losing a penny every time a bill is divided.

t.geo.grid

Global grids: the Earth's surface cut into numbered cells at many sizes. It finds the cell at a latitude and longitude, and a cell's parent, children, neighbors and area.

t.gfx

Graphics: turns text into shaped glyphs and builds the typed rows a program uses to describe a Thalassa scene. Thalassa, MerTek's renderer, does the drawing. A program can use it only when its grant admits t.gfx.

t.job

Builds a job's outcome (done, failed, a checkpoint or progress) and talks to the host's job runner to submit, cancel or inspect jobs. It needs a host that runs jobs, so the one-shot tricli runner does not grant it.

Bug it prevents: a retried background task running as a second, separate job.

t.log

Writes log messages at a level: trace, debug, info, warn or error. The host's grant decides which levels are allowed, which is why an application's lend names t.log with a threshold such as info.

t.log.info("starting")
bool chatty = t.log.enabled("debug")
t.math

The general math library: square roots, pi, clamping, rounding, vectors, matrices and more. Its answers are plain 64-bit floating-point numbers, computed the same way every run. It needs no line in a lend.

f64 root = t.math.sqrt(2.0)
f64 kept = t.math.clamp(5.0, 0.0, 1.0)
assert (kept == 1.0, "clamped to the top")
f64 bad = t.math.sqrt(-1.0)   # faults: no NaN takes its place

Bug it prevents: sqrt(-1) returning NaN, which then spreads silently through later math until a far-off number prints as nan.

t.math.castRounding, castSaturating, castWrapping

A plain conversion such as f32(x) is exact or it faults: t.math.cast: digits when the value cannot be stored exactly, t.math.cast: range when it is too big. When losing something is the point, you name the loss: castRounding rounds, castSaturating clamps to the largest value, castWrapping wraps around like a car's odometer.

f64 wide = 1.0e300
f32 capped = t.math.castSaturating(wide, "f32")
i64 big = 300
u8 wrapped = t.math.castWrapping(big, "u8")
assert (wrapped == 44, "300 wraps to 44")
f32 narrow = f32(wide)   # faults: t.math.cast: range

Bug it prevents: a narrowing cast that silently cuts off or wraps a number, as C and Go allow.

t.math.complex

Math on complex numbers (a real part plus an imaginary part): size, angle, square roots and logarithms. Complex numbers have no less-than, so to compare sizes you call t.math.complex.abs on each side, where a reader can see it.

complex128 z = 3.0 + 4.0i
f64 size = t.math.complex.abs(z)
assert (size == 5.0, "a 3-4-5 triangle")
complex128 w = 1.0 + 1.0i
bool less = z < w   # refuses: complex numbers have no order

Bug it prevents: z < w quietly comparing only the real parts, or only the sizes, and answering a believable but wrong true or false.

t.math.daabb

Members for daabb, a bounding box whose sides line up with the x, y and z axes. They answer whether a point is inside, whether two boxes overlap, and which box holds both. Boxes have no + or <, because neither has one honest meaning.

daabb box = (0,0,0, 1,1,1)
bool inside = t.math.daabb.contains(box, (0.5, 0.5, 0.5))
assert (inside, "the center is inside")
daabb both = t.math.daabb.union(box, box)

Bug it prevents: box + box compiling into something that looks reasonable but answers a question nobody asked.

t.math.dquat

Members for dquat, a quaternion: four numbers that together describe one 3D rotation. You can build one from an axis and an angle and rotate a point with it. Two rotations combine with @, never *, because the four numbers are one rotation, not four separate values.

dquat spin = t.math.dquat.fromAxisAngle((0.0,1.0,0.0), 1.5707963)
dvec3 turned = t.math.dquat.rotate(spin, (1.0, 0.0, 0.0), 1e-9)
dquat both = spin @ spin
dquat bad = spin * spin   # refuses: no lane-by-lane product

Bug it prevents: multiplying two quaternions part by part, which compiles in most languages and gives a meaningless rotation.

t.math.dual

Members for dual, a number that carries its own derivative (how fast it is changing) beside its value. Arithmetic on duals applies the calculus rules for you, and t.math.dual.value and t.math.dual.tangent read the two parts back.

dual x = (3.0, 1.0)
dual y = x * x
f64 value = t.math.dual.value(y)
f64 slope = t.math.dual.tangent(y)
assert (slope == 6.0, "the slope of x squared at 3")

Bug it prevents: estimating a slope from two nearby points with a step that is too big or too small, and nothing telling you which.

t.math.extended

Members for the extra-precision kinds dd128, td192 and qd256, which add two, three or four 64-bit floats together to hold more digits. t.math.extended.of builds one, and t.math.extended.toF64 reads it back as one plain f64, a name that admits it keeps only the leading part.

dd128 one = t.math.extended.of(1.0, "dd128")
f64 back = t.math.extended.toF64(one)
assert (back == 1.0, "round trip")
t.math.geometricAlgebra

Members for geometric algebra, a branch of math that handles rotations, reflections and intersections with one set of rules. It provides the geometric product, the wedge, exp and log, and applying a rotor or a motor. The algebra's kinds take no arithmetic operators, so every operation is a named call.

t.math.tensor

The dense number-array library: one block of numbers with a shape (rows, columns and more), plus members that build, read and reshape it. t.math.tensor.from builds a tensor from a list of numbers or a list of equal-length rows.

t.math.uncertainty

Tools for saying how well a measurement is known. combine merges the uncertainties of several measurements through a calculation and states how they are related; effectiveDegreesOfFreedom reports how much evidence the result rests on.

Bug it prevents: adding two uncertain measurements with + and silently assuming they are unrelated, which can hide how large the real error is.

t.measure

Members for measure, an exact physical amount with its unit, such as 90 degrees. t.measure.to converts exactly or refuses, so a quarter turn becomes exactly one half pi radians. t.measure.format prints in a unit it checked, never one typed into a format string.

measure quarter = 90deg:angle
measure inRad = t.measure.to(quarter, "rad")
assert (text(inRad) == "0.5pirad:angle", "exactly pi/2")

Bug it prevents: a report that prints "kN" from a format string while the number underneath is really in newtons.

t.net.http

Makes outbound web (HTTP) requests, but only to what an operator profile allows. A script gets no network at all unless the command line names a profile and its digest, so the code cannot decide on its own where to connect.

Bug it prevents: a library deep inside a program sending data to a web address nobody approved.

t.net.peer

Direct connections between running MerTek programs (peers) over named lanes: connect, send a message, watch and close. The operator lists which peers the program may reach and in what role, and any other endpoint is refused before a byte is sent. It grants no web (HTTP) access.

Bug it prevents: a program connecting to a machine the operator never listed.

t.nodecanvas

Keeps the state of a node canvas, a visual editor where boxes (nodes) connect by wires: which nodes exist, which are selected and where each one sits. It works out layout and drawing shapes the same way every time, and never runs the code it shows.

t.quantity

Members for numbers that carry a physical family and unit, such as a length in meters: convert units, take roots and change how the amount is stored. The square root of an area comes back as a length.

f64<family=area> square = 9.0m2
f64<family=length> side = t.quantity.root(square, 2)
f64 meters = t.quantity.inUnit(side, "m")
assert (meters == 3.0, "3 m on a side")

Bug it prevents: taking the square root of an area, forgetting it is now a length, and adding it to an area.

t.security

The security family: t.security.crypto for passwords, keys, signing and secure random numbers, and t.security.secret for comparing and sealing secret values. Each needs a security provider grant from the host, so code cannot reach keys on its own.

t.security.crypto

Password hashing and checking (passwordHash and passwordCompare), secure random numbers, named public keys, signing and certificate requests. A stored password hash (pwhash) can be checked only through passwordCompare.

Bug it prevents: comparing password hashes with ==, or turning one into text that ends up in a log; both refuse at compile.

t.slab

The library for slabs, blocks of fixed-size typed rows that the host keeps between ticks of a game or simulation, such as every unit's state.

t.sync

Named locks, which stop two pieces of work from changing the same thing at once. t.sync.policy builds the rule (exclusive, shared or counted, with a wait limit), and the lock statement holds it for one block and releases it however the block ends.

i32 count = 0
chest policy = t.sync.policy.exclusive(0ms, 1s, false)
lock ("ledger", policy) {
    count = count + 1
}

Bug it prevents: a lock left held forever because an error skipped the unlock line.

t.telemetry

Builds metric, histogram, timing-span and frame-profile records as plain documents. You pass every number and timestamp in, so the same inputs make the same record and a replay matches the original run.

chest hits = t.telemetry.metricDeclare("hits", "counter", [])
text kind = hits.kind
t.text

Turns values into text and back: the exact text of a value, a number shown with a set count of decimal places, and parsing text into a value. A conversion such as i32(digits) uses its parser and faults when the text is not a number.

text shown = t.text.display(2.5, 2, {})
assert (shown == "2.50", "two decimal places")

Bug it prevents: text such as "12abc" quietly read as the number 12.

t.text.bytes, t.text.scalars, t.text.graphemes

Three ways to count and index text. bytes counts the stored UTF-8 bytes, scalars counts Unicode code points, and graphemes counts what a reader sees as one character. "héllo" is 6 bytes but 5 letters, so a bare count() on text refuses and makes you pick.

text word = "héllo"
i64 bytes = t.text.bytes.count(word)
i64 letters = t.text.graphemes.count(word)
assert (bytes == 6, "six bytes")
assert (letters == 5, "five letters")

Bug it prevents: cutting a name in the middle of an accented letter or an emoji because the length counted bytes when you meant letters.

t.time

Exact dates, times and durations. Each time scale has its own library, such as t.time.unix, with add, sub and compare and a conversion to a calendar label. Nothing here reads the clock; you pass the instant in.

timepoint<unix> start = 0
timepoint<unix> later = t.time.unix.add(start, 90m)
civil label = t.time.unix.toCivil(later)
assert (text(label) == "1970-01-01T01:30:00Z", "ninety minutes in")
t.time.map

Converts an instant between time scales (Unix, atomic TAI, TT, GPS and UTC calendar labels) using pinned tables whose digest is checked. If the tables do not cover an instant, it refuses instead of guessing. A program must be granted it, and a plain script is not.

Bug it prevents: comparing a GPS time with a computer timestamp directly while they silently differ by leap seconds.

t.timer

Schedules an event for later: after a delay, every so often, or once things go quiet. The event arrives at an on event handler. Only a runner that keeps going after main returns can deliver it, so tricli refuses t.timer and points you to tri --headless.

T? nullable and null

A ? after a type means the value may be missing, which is written null. It can be used as a plain value only where a test, like if (x != null), proves it is there. The ? can sit at each level, so i32?[] and i32[]? differ.

i32? maybe = null
i32? seven = 7
i32 sure = seven   # proved present by its write
if (maybe != null) {
    i32 inside = maybe   # proved by the test
}
i32 unsure = maybe   # refuses: nothing proves it present

Bug it prevents: the null crash, reading a missing value as if it were there. A read that nothing proves refuses before the program runs.

tree: a hierarchical value

A collection whose nodes form ordered roots and children. Node values have one complete type. A layer can constrain root count, arity, depth, node count, distinctness and sibling order. A family adds its own invariant, such as a complete heap or a balanced search tree. Tree rules and examples.

tree<i32,roots=1,arity=[0,2]> values = 9 { 7, 4 }
graph: vertices and arcs

A collection of typed vertex values and arcs between indexed vertices. Arc values can be a scalar or record. Counts, degree, simplicity, direction, cycles, connectivity and bipartition are checked rules. Graph values are separate from execution resources such as a render graph. Graph rules and examples.

graph<text,acyclic> tasks = [ "parse" { 1 }, "compile" ]
Heap family: a complete priority tree

A complete binary tree: every level is full except possibly the last, which fills from the left. A max heap keeps each parent priority at least its children; a min heap reverses that comparison. Siblings need not be sorted. This is distinct from the memory allocator called a heap.

tree<job,family=heap,heap=max(priority)> work = []
distinctValues: one value per dictionary key

No two dictionary entries may hold equal values. A write equal to a different key's value refuses without changing the dictionary. Null equals null, so a nullable value occurs at most once. The invert operation also requires the value type to be key-eligible.

dict<text,u32,distinctValues> ids = { "one" = 1 }
full: a policy for a growing write

At a declared maximum, reject refuses the write, dropFirst removes the first items and dropLast removes the last. An array uses element order; a dictionary uses key order. Received data is validated without trimming. This policy never turns a null collection into a present one.

T[] and T[N] arrays

An ordered list of values of one type. i32[] can grow with add; i32[2] is a fixed group of exactly two that never grows. A foreach walks a snapshot, so adding during the walk never extends it.

i32[] values = [1, 2, 3]
values.add(4)
i32[2] pair = [10, 20]
i32 first = pair[0]
i32 bad = pair[2]   # refuses: index 2 is outside a pair

Bug it prevents: reading past the end of a list. An index the compiler can prove is out of range refuses, and any other is checked when it runs.

tape

A tape is a recording of a run: every input the program read from outside, such as input events, call arguments and clock readings, and what it did. A Trident program reads nothing a tape cannot record, so a tape can be replayed later and must give the same results.

Bug it prevents: a bug that happened once on someone's machine and can never be reproduced. The tape is the bug report.

task … else

Runs a block in a walled-off region. If anything inside faults, the block stops and its else arm runs once with the fault. A region can only tighten the time and memory limits it inherits, never loosen them. It does not run code in parallel.

text trail = "no fault"
task {
    i32 zero = 0
    i32 lost = 1 / zero
} else (raised) {
    trail = "the block faulted"
}

Bug it prevents: a library call deep inside quietly using more time or memory than its caller allowed.

Telemetry

Telemetry is data a program reports about itself while it runs, such as counts, timings and errors. Trident's t.telemetry is a pure, deterministic linked library.

Temperature point and temperature difference

A thermometer reading (20 °C) and a change in temperature (20 degrees warmer) are different things. Adding a change to a reading makes sense; adding two readings does not. Absolute zero, 0 kelvin or -273.15 °C, is the coldest possible temperature, and it is a point, not a difference.

tensor

A block of numbers with any number of dimensions (a list, a table, a cube and beyond), all of one element type such as f64. Science and machine learning use them. * works element by element and @ is the matrix product. A literal of its value text stays quoted, because its header, an element type and a bracket list, would read as a type and an index.

tensor<f64> m = t.math.tensor.from([[1.0, 2.0], [3.0, 4.0]], "f64")
tensor<f64> product = m @ m   # matrix product
tensor<f64> lanes = m * m   # element by element
tensor<f32> other = m   # refuses: the element type is part of the type

Bug it prevents: multiplying element by element when a matrix product was meant, or the reverse.

Tensor contraction

Combining two grids of numbers by multiplying entries along a shared axis and adding them up. Matrix multiplication and the dot product are both contractions. Trident's @ contracts along the trailing (last) axes.

Texel

One pixel of a texture, the image a 3D program wraps over a surface. Texels are often stored in special packed formats. A Trident binary16 vector holds plain IEEE half-precision numbers, not a texel format.

text

A string of characters, stored as strictly valid UTF-8, the standard way to write text as bytes. Use it for names, messages and anything people read. A number joins text only through an explicit text(n) or a {n} slot in a `...` string.

text name = "Ada"
i32 age = 36
text line = `{name} is {age}`
text joined = name + " " + text(age)
text bad = "age " + age   # refuses: a number is not text

Bug it prevents: a number glued into text by accident, like JavaScript's "1" + 1 giving "11".

Thalassa

Thalassa is MerTek's renderer and scene engine, written fully in C. A program describes a scene with t.gfx and Thalassa draws it. It never reads the wall clock on its own: a frame's time arrives as a field the host writes.

Thread and concurrency

A thread is one line of work inside a program; with several threads a program can do many things at once (concurrency), at the risk of data races.

Time scale (Unix, TAI, GPS, TT)

A rulebook for counting seconds. Unix time counts from 1970 as if no leap second ever happened; TAI averages atomic clocks and runs 37 seconds ahead of UTC; GPS time is TAI minus 19 seconds; TT, used in astronomy, is TAI plus 32.184 seconds. A Trident timepoint carries its scale in its type.

timepoint<unix> start = 0
timepoint<tai> atomic = start   # refuses: a scale change is a named map

Bug it prevents: subtracting a GPS time from a Unix time and silently missing the 18 leap seconds between them.

timepoint

One exact instant on a named clock, such as Unix time or atomic time (TAI). The clock, called the time domain, is part of the type: timepoint<unix>. timepoint<domain=tcl> counts Lunar Coordinate Time. Use it for timestamps.

timepoint<unix> start = 0
timepoint<unix> later = start + 1h30m
duration gap = later - start
timepoint<tai> atomic = start   # refuses: a clock change is a named step

Bug it prevents: subtracting timestamps from two different clocks, which can disagree by tens of seconds, as if they were one clock.

timerange

A span between two instants on one clock, written [start, end), the half-open bracket mathematics writes: it includes the start but not the end. So back-to-back ranges meet without overlapping.

timerange<unix> shift = [1700000000,1700028800)
timerange<unix> empty = [5,5)
bool same = shift == empty   # false
timerange<unix> bad = [2,1)   # refuses: the end is before the start

Bug it prevents: an event at exactly 9:00 being counted in both the 8-to-9 slot and the 9-to-10 slot.

Token

A token is the smallest meaningful piece of code, like a word in a sentence: a name, a number or an operator. In Trident a unit suffix is part of its number's token, so 5kg is one token, while 5 kg with a space is a number and a name, and is refused.

f64<mass> load = 5kg
f64<mass> gap = 5 kg   # refuses: 5 and kg are two tokens
Toolchain

A toolchain is the set of programs used to build software, such as a compiler, a linker and a build tool. Some languages need a different toolchain for each target. Trident runs the same source everywhere; what changes per target is which libraries are linked and which capabilities are lent.

Top-level code

Top-level code is code written outside any function, which many languages run as soon as a file is loaded or imported. Trident runs none: a source file holds only declarations, so opening one to read or lint it can never run anything.

const i32 limit = 10
i32 counter = limit + 1   # refuses: a module holds only declarations
text main(text[] argv) {
    return "ok"
}
torus, dtorus

A torus is a donut shape: a center, a turn, a major radius (from the center to the middle of the ring) and a minor radius (how thick the ring is). dtorus stores more digits.

text describe(torus<frame=builtin:itrs> donut) {
  f32<family=length> ring = donut.majorRadius
  f32<family=length> tube = donut.minorRadius
  return t.text.of(tube)
}
transaction

A block that gathers its writes and saves them all at once, or none at all. Nothing is held while it runs, and if another write conflicts at the end, its else arm runs. The storage design reserves the word.

Bug it prevents: half of a money transfer saved while the other half is lost.

Transfer function

The curve that turns a color's stored numbers into real light, and back. Screens use one because eyes notice small changes in dark shades far more than in bright ones, so more of the numbers go to the darks. Rec. 2100 has two, PQ and HLG, for high dynamic range (HDR) video.

Transpose

A matrix flipped across its diagonal, so its rows become its columns: a table 2 rows by 3 columns becomes 3 rows by 2. Some graphics libraries store the transpose of the same transform, so the same numbers line up the other way.

tri

tri is the windowed runner beside tricli. It runs the same scripts and applications the same way, and it can also open windows, sound and input devices and keep running to deliver events and timers (tri --headless does that with no window).

triangle2, dtriangle2

A triangle2 is a flat triangle given by its three corners, a, b and c, in an app's own frame. dtriangle2 stores more digits.

triangle3, dtriangle3

A triangle3 is a triangle in 3D given by its three corners, a, b and c. 3D models are built from many of them. dtriangle3 stores more digits.

text describe(triangle3<frame=builtin:itrs> face) {
  point3<frame=builtin:itrs> corner = face.b
  return t.text.of(corner)
}
tricli

tricli is the command-line runner for Trident. tricli main.tri compiles that one file and calls main; tricli calc compiles a whole application folder. It opens no window, and with no file named it starts a prompt that runs one line at a time.

trident_exec_promote

A runtime call that moves a value's memory charge from a call's short-lived scratch space into a longer-lived account, so the value can outlive the call. It moves the bookkeeping, not the bytes, and a refused move leaves both accounts as they were.

trident_program_compile, trident_runtime_install

The two C calls a host uses to compile a program bundle and install the result in a runtime. They belong to the host alone: no t.* library offers them, so a running Trident program can never start or swap in other code by itself.

Trigonometry

The math of angles and triangles. Sine, cosine and tangent turn an angle into ratios of a right triangle's sides, which is how programs aim, rotate, and draw circles and waves.

tripkg

tripkg is MerTek's package tool. Before a launch, it looks up a package by name, checks its bytes against a digest (a fingerprint), and copies it into the project as a stored library pinned by that digest. A running program can never fetch a library itself.

Bug it prevents: dependency confusion, where a public registry serves an attacker's package under your private package's name. Each prefix, mer or pkg.<publisher>, maps to exactly one source.

trove

Declares the named columns a record must have, like the header row of a table: trove user { text name, text email }. A local bound to it is still a flexible record, but a misspelled column refuses before the program runs. In the database a trove is also the collection of those records.

trove user { text name, text email }
user u = { name = "Ada", email = "ada@example.org" }
u.email = "ada@example.com"
u.emial = "x"   # refuses: no column of that name

Bug it prevents: a typo like emial quietly creating a new, unused field while the real one keeps its old value.

true, false

The two values of a bool: yes and no. A condition must be a real bool, so a number like 1 or 0 is never accepted in place of one.

bool ready = true
bool done = false
bool bad = 1   # refuses: a number is not a bool

Bug it prevents: a zero, an empty string or an empty list quietly counting as false.

Truthy and falsy

In some languages any value can stand in for true or false: 0, empty text or null count as false (falsy) and almost everything else as true (truthy). Trident's if takes a bool and nothing else, so an integer is never a condition.

i32 count = 1
if (count > 0) { count = 2 }
if (count) { count = 3 }   # refuses: an integer is never a condition

Bug it prevents: C's if (x = 5), which assigns instead of comparing and is always true.

Tuple

A tuple is a small, fixed list of values kept together in order, like (3, 4). Trident has no tuple type a program declares.

dquat spin = (0,0,0,1)
dquat twice = spin @ spin
dquat partial = (0,0,1)   # refuses: a dquat has four parts
twist, dtwist

A twist is how fast something moves and spins: a speed along x, y and z (meters per second) plus a spin rate around each (radians per second). Its type names what moves, what it moves relative to, and the frame of the numbers. dtwist stores more digits.

twist<moving=builtin:itrs,relative=builtin:icrs,frame=builtin:icrs> earthSpin = (0, 0, 0, 0, 0, 0.0000729)
vec3<family=speed> moving = earthSpin.linear
vec3<family=angularFrequency> turning = earthSpin.angular
vec3<family=speed> mixed = moving + turning   # refuses: a speed is not a spin rate

Bug it prevents: adding a speed to a spin rate because both are three numbers. The two halves read out as different quantities, so the sum refuses.

Type A and Type B evaluation

Two ways to work out an uncertainty. Type A uses statistics on repeated readings, such as measuring a rod ten times; Type B uses other knowledge, such as a maker's spec sheet or a calibration certificate. Both give a standard uncertainty.

Type erasure

Type erasure means a language checks types while compiling and then throws them away, so the running program no longer knows them; TypeScript's types are gone once it becomes JavaScript. Trident keeps a value's complete type while it runs and when it is stored, so a document read back still knows its types.

Type inference

Type inference lets the compiler work out a variable's type from its value, so you can write var x = 1. It saves typing but hides the type from the reader. Trident has none: every binding states its full type, and var is an ordinary name, not a keyword.

i64 total = 0
f32 ratio = 1.5
var count = 1   # refuses: write the type, i32 count = 1
Type name = value

Creates a named value and states its complete type first, like i32 count = 3. Trident never guesses a type, and a value never changes type on the way in: a different width needs a written conversion.

i32 count = 7
i64 total = 0            # the literal reads at i64
i64 wide = i64(count)    # the conversion is written out
i64 bad = count   # refuses: write i64(count)

Bug it prevents: a variable's type silently changing when code in another file is edited, or a value quietly widened or cut short on assignment.

Type system

A type says what kind of value something is, such as a whole number, a piece of text or an amount of money, and which operations make sense on it. A type system is a language's set of rules about types. In Trident's, the same bytes are never the same type: a guid is not a u128.

Type<...> type parameters

Settings in angle brackets after a type name, like f64<mass> or money<USD>, that make the setting part of the type. Each can be written by name or by position: f64<family=mass> and f64<mass> are one type.

f64<family=mass> heavy = 5kg
f64<mass> alsoHeavy = 5kg   # the same type, written short
f64<range=[0,1]> chance = 0.5
f64<range=[0,1]> bad = 1.5   # refuses: outside its range

Bug it prevents: a value in the wrong unit, or outside its allowed range, passing as an ordinary number.

u8

A whole number from 0 to 255, stored in one byte. Use it for bytes, color channels and small counts that can never be negative.

u8 level = 255
u8 bad = -1   # refuses: a u8 holds no negative number
i32 big = 300
u8 b = big   # refuses: the conversion must be written
u8 capped = t.math.castSaturating(big, "u8")   # 255, and it says so

Bug it prevents: 300 quietly turning into 44 when it is stored in a byte.

u16

A whole number from 0 to 65,535, stored in 16 bits. Use it for port numbers, small codes and compact counts that are never negative.

u16 port = 8080
u16 over = 70000   # refuses: 70000 does not fit in a u16
u32

A whole number from 0 to about 4.3 billion, stored in 32 bits. Use it for sizes, ids and counts that are never negative.

u32 size = 4294967295
u32 none = 0
u32 under = none - 1   # faults: a u32 holds no negative number

Bug it prevents: 0 minus 1 wrapping around to 4,294,967,295.

u64

A whole number from 0 to about 18.4 quintillion, stored in 64 bits. Use it for very large counts, sequence numbers and bit patterns.

u64 events = 18446744073709551615
u64 none = 0
u64 under = none - 1   # faults: a u64 holds no negative number
u128

A whole number from 0 up to a 39-digit number, stored in 128 bits. It is a number; an id of the same size is a guid, a different type.

u128 big = 340282366920938463463374607431768211455
guid id = 00000000-0000-0000-0000-000000000000
u128 n = id   # refuses: an id is not a number
U+0041 rune literal

Writes one Unicode character by its code number: U+0041 is the letter A. A rune can be compared but not added to; to step to the next character, convert through u32 and back.

rune first = U+0041
rune next = rune(u32(first) + 1)   # B
rune bad = first + 1   # refuses: no rune arithmetic

Bug it prevents: adding to a character code and landing on a number that is not a valid character.

Unbounded growth (memory leak)

Unbounded growth is memory use that keeps climbing while a program runs, often from a leak: memory that is never given back, until the machine runs out. In Trident each kind of memory has a declared limit, so growth stops at a typed fault instead of taking the whole machine down.

Undefined behavior

Undefined behavior is what C and C++ call an operation the language gives no meaning, such as signed integer overflow. The compiler may then do anything, even delete your safety check. Trident has none of it: overflow, division by zero and the other bad operations are catchable faults.

Bug it prevents: a C compiler deleting the if (x + 1 < x) check written to catch an overflow.

Unicode

Unicode is the worldwide standard that gives a number to every character, from A to a Chinese character to an emoji. Trident's text is always valid Unicode stored as UTF-8, and code that counts or indexes text must say which unit it means: bytes, Unicode scalars or graphemes.

unit

A unit of measure as a value, such as m (meters) or kg. A program can store, pass and compare units, but a unit is not a number and takes no math. Its literal stays quoted, because m is a name and m/s would read as two names and a division.

unit meters = "m"
unit seconds = "s"
bool same = meters == seconds   # false
unit odd = meters + seconds   # refuses: a unit takes no arithmetic
Unwinding

Unwinding is what happens when an error leaves a function early: the program backs out through each call, running cleanup code as it goes. In Trident an on exit block runs on every leave, a fault's unwind included, and resume can continue after a faulting statement instead of unwinding past it.

use

Inside an if or when that produces a value, use hands back that branch's answer. Every branch must supply one of the destination's type, so a value if needs an else. Only the chosen branch runs.

i32 n = -3
text sign = if (n < 0) {
    use "negative"
} else {
    use "not negative"
}
text bad = if (n < 0) { use "negative" }   # refuses: needs an else

Bug it prevents: a variable left unset because one branch forgot to assign it.

Use-after-free

A use-after-free bug reads or writes memory after the program has given it back, when it may already belong to something else. It is a top cause of crashes and security holes in C and C++. Trident cannot express it: there is no free and no pointer type.

Bug it prevents: an attacker placing their own data where freed memory used to be, and the program trusting it.

UTC (Coordinated Universal Time)

The world's reference clock for everyday time: time zones are written as offsets from it, so +02:00 means two hours ahead and Z means UTC itself. It follows atomic time but adds a leap second now and then to stay in step with the Earth's spin. In Trident, UTC appears on civil labels.

civil utc = 1987-03-05T14:30:00Z
civil nozone = 1987-03-05T14:30:00
bool before = utc < nozone   # faults: an unknown offset cannot be placed
UTF-8 and UTF-16

UTF-8 and UTF-16 are ways to store Unicode characters as bytes: UTF-8 uses 1 to 4 bytes per character, and UTF-16 uses 2-byte units. Trident's text and its source files are strict UTF-8.

uvec2, uvec3, uvec4

A group of 2, 3 or 4 whole numbers that are never negative (each a u32), such as an image's width and height in pixels. Math works lane by lane, and a lane that would drop below zero stops the program.

uvec2 size = (640, 480)
uvec2 doubled = size * 2
uvec2 small = size - (700, 0)   # faults: 640 - 700 would go below zero

Bug it prevents: a size that subtracts past zero and wraps around to about four billion pixels.

Variadic function

A variadic function accepts any number of arguments, like C's printf. Trident has none: every function has a fixed parameter list, and a call names exactly that many arguments.

Variance and covariance

Variance is a spread measured in squared units: the standard uncertainty multiplied by itself. Covariance is the same idea for two values together, how much they vary in step. A positive covariance means that when one reads high, the other tends to read high too.

varint

A whole number with no fixed size: it grows to as many digits as it needs, up to 4,096 bits (about 1,200 digits). The cost of each step is counted before it runs, so a giant number cannot hang the program.

varint x = 12345678901234567
varint y = 9876543210987654321
varint p = x * y   # exact: no overflow
i64 small = p   # refuses: the conversion must be named

Bug it prevents: a product overflowing a 64-bit integer and wrapping to a negative number.

vec2, vec3, vec4 (and dvec2, dvec3, dvec4)

A short list of 2, 3 or 4 decimal numbers that travel together, like the x, y and z of an arrow. Games and graphics use them for positions, speeds and colors. Math works lane by lane (one number at a time). The d versions store more digits per number.

vec3 step = (1, 2, 3)
vec3 longer = step * 2.0
f32 height = step.y
dvec3 fine = (1.1, 2, 3)
vec3 rough = fine   # refuses: the d version never quietly loses digits
vec3 kept = vec3(fine)   # faults: 1.1 has no exact copy with fewer digits

Bug it prevents: a precise dvec3 losing digits as it is squeezed into a vec3 with nobody noticing. The plain copy refuses, and vec3(fine) stops the program rather than round.

virtual machine (VM)

A virtual machine is a program that runs bytecode one instruction at a time, like a player piano reading a paper roll. Trident's VM counts its call depth against a limit instead of using the computer's own stack, and it has no JIT (a mode that turns busy code into machine code while it runs).

Bug it prevents: deep recursion overflowing the real stack and crashing the whole process instead of raising a fault.

void

The result type of a function that returns no value. It can appear only there: no variable or parameter can be void.

void greet(text name) {
    text line = "hi " + name
}
greet("Ada")
void nothing = greet("Bob")   # refuses: void is not a binding type

Bug it prevents: using the result of a function that does not produce one.

WebAssembly (wasm)

WebAssembly is a compact code format that web browsers can run almost as fast as native programs.

webtricli

A build of the Trident runner that works inside a web browser as WebAssembly, with no window of its own.

Wedge product

A product of two directions that builds the flat patch they span, a bivector, with a turning direction. Its size is the area of the parallelogram the two directions outline. t.math.geometricAlgebra.wedge computes it for multivectors.

multivector<euclid3> e1 = (0, 1, 0, 0, 0, 0, 0, 0)   # the x direction
multivector<euclid3> e2 = (0, 0, 1, 0, 0, 0, 0, 0)   # the y direction
multivector<euclid3> patch = t.math.geometricAlgebra.wedge(e1, e2)   # the xy patch
when, is

Picks one branch by matching a value against is arms; an arm can list several values, and no arm falls through into the next. Over an enum, a when must name every member and takes no else. when unguarded is the deliberate partial form.

enum Light { Red = 0, Amber = 1, Green = 2 }
Light now = Light.Amber
text say = ""
when (now) {
    is Light.Red { say = "stop" }
    is Light.Amber, Light.Green { say = "go" }
    else { say = "?" }   # refuses: an enum when takes no else
}

Bug it prevents: a new enum member silently landing in an old default branch. Every when over it refuses until it handles the new case.

while (not in Trident)

Trident has no while loop. Every loop states how many passes it may take before it starts, so a loop that runs until something changes is written as repeat with a limit and break.

i32 left = 3
repeat (10) {
    if (left == 0) { break }
    left--
}
while (left > 0) { left-- }   # refuses: no while loop

Bug it prevents: an endless loop that hangs the program because its exit condition is never met.

wrench, dwrench

A wrench is a push plus a twisting push: a force along x, y and z (in newtons) and a torque around each (in newton-meters), in one frame. Robot arms and physics engines use it. dwrench stores more digits.

wrench<frame=builtin:itrs> shove = (10, 0, 0, 0, 0, 2)
vec3<family=force> push = shove.force
vec3<family=torque> twistingPush = shove.moment
vec3<family=force> mixed = push + twistingPush   # refuses: a force is not a torque

Bug it prevents: adding a force to a torque because both are stored as three numbers.

Other-language snippets are authored for this page and aim for idiomatic, not exhaustive, coverage.

Which names are protected?

A reservation protects a name. It does not add a feature. A Cutlass stage number states when the language accepts that word.

Binding reservations do not ban member names or segments after a dot. A contextual word may still be a local name. Trident's observation heads are reading, tally, balance, histogram, and delta. They stay contextual. Trident also keeps intent available as a binding, because the importer contract requires that exact parameter name. Future intent syntax must be contextual. Contract clauses, handler tails, and bus and role clause words also stay contextual. Cutlass keeps its existing type reservation for handle.

A protected peer word leaves room for shared terms. A future system type is compiler-owned and adds no native kind. guard is ordinary in both languages and is no alias for expect. Old metric names are no aliases. Machine types, GPU types, and package roots still need their own decisions before either language adopts the other's model.

Show every protected spelling (hover or focus for help)
WordTridentCutlass
aReserved root; own sourceNo global reservation in these tables
aabbNative type; binding reservedType name; binding reserved; stage 1
abstractUnavailable word; binding reservedNo global reservation in these tables
addressOfNo global reservation in these tablesLexer word; binding reserved; stage 0
aliasActive lexer word; binding reservedLexer word; binding reserved; stage 0
almanacNative type; binding reservedLexer word; binding reserved; stage 1
andUnavailable word; binding reservedNo global reservation in these tables
asUnavailable word; binding reservedNo global reservation in these tables
asmNo global reservation in these tablesLexer word; binding reserved; stage 1
asyncUnavailable word; binding reservedNo global reservation in these tables
atomicNo global reservation in these tablesType name; binding reserved; stage 1
awaitUnavailable word; binding reservedNo global reservation in these tables
bf16Native type; binding reservedType name; binding reserved; stage 1
bivectorNative type; binding reservedType name; binding reserved; stage 1
boolNative type; binding reservedType name; binding reserved; stage 0
breakActive positional syntax; binding reservedLexer word; binding reserved; stage 0
bufferNative type; binding reservedType name; binding reserved; stage 1
bvec2Native type; binding reservedType name; binding reserved; stage 1
bvec3Native type; binding reservedType name; binding reserved; stage 1
bvec4Native type; binding reservedType name; binding reserved; stage 1
cReserved rootNo global reservation in these tables
capNo global reservation in these tablesType name; binding reserved; stage 0
capsuleNative type; binding reservedType name; binding reserved; stage 1
capsule2Native type; binding reservedType name; binding reserved; stage 1
caseUnavailable word; binding reservedNo global reservation in these tables
catchUnavailable word; binding reservedNo global reservation in these tables
cellidNative type; binding reservedType name; binding reserved; stage 1
channelNo global reservation in these tablesType name; binding reserved; stage 1
chestNative type; binding reservedType name; binding reserved; stage 1
ciphertextNative type; binding reservedType name; binding reserved; stage 1
circleNative type; binding reservedType name; binding reserved; stage 1
civilNative type; binding reservedType name; binding reserved; stage 1
civildateNative type; binding reservedType name; binding reserved; stage 1
civiltimeNative type; binding reservedType name; binding reserved; stage 1
classUnavailable word; binding reservedNo global reservation in these tables
colorNative type; binding reservedType name; binding reserved; stage 1
complex128Native type; binding reservedType name; binding reserved; stage 1
complex64Native type; binding reservedType name; binding reserved; stage 1
computeProtected peer word; binding reservedNo global reservation in these tables
coneNative type; binding reservedType name; binding reserved; stage 1
conicalfrustumNative type; binding reservedType name; binding reserved; stage 1
constActive lexer word; binding reservedLexer word; binding reserved; stage 0
constructorUnavailable word; binding reservedNo global reservation in these tables
continueActive positional syntax; binding reservedLexer word; binding reserved; stage 0
contractProtected peer word; binding reservedLexer word; binding reserved; stage 1
coord2Native type; binding reservedType name; binding reserved; stage 1
coord3Native type; binding reservedType name; binding reserved; stage 1
coroutineNo global reservation in these tablesLexer word; binding reserved; stage 1
ctNo global reservation in these tablesLexer word; binding reserved; stage 1
curvepointNative type; binding reservedType name; binding reserved; stage 1
cutNo global reservation in these tablesReserved node root
cutarchNo global reservation in these tablesReserved node root
cutlassNo global reservation in these tablesReserved node root
cutrunNo global reservation in these tablesReserved node root
cylinderNative type; binding reservedType name; binding reserved; stage 1
daabbNative type; binding reservedType name; binding reserved; stage 1
dcapsuleNative type; binding reservedType name; binding reserved; stage 1
dcapsule2Native type; binding reservedType name; binding reserved; stage 1
dcircleNative type; binding reservedType name; binding reserved; stage 1
dconeNative type; binding reservedType name; binding reserved; stage 1
dconicalfrustumNative type; binding reservedType name; binding reserved; stage 1
dcoord2Native type; binding reservedType name; binding reserved; stage 1
dcoord3Native type; binding reservedType name; binding reserved; stage 1
dcylinderNative type; binding reservedType name; binding reserved; stage 1
dd128Native type; binding reservedType name; binding reserved; stage 1
ddir2Native type; binding reservedType name; binding reserved; stage 1
ddir3Native type; binding reservedType name; binding reserved; stage 1
debugNo global reservation in these tablesLexer word; binding reserved; stage 1
decimalNative type; binding reservedType name; binding reserved; stage 1
defUnavailable word; binding reservedNo global reservation in these tables
defaultUnavailable word; binding reservedNo global reservation in these tables
deferUnavailable word; binding reservedNo global reservation in these tables
deleteUnavailable word; binding reservedNo global reservation in these tables
dellipseNative type; binding reservedType name; binding reserved; stage 1
dellipsoidNative type; binding reservedType name; binding reserved; stage 1
deviceNo global reservation in these tablesLexer word; binding reserved; stage 1
dictType constructor; binding reservedType name; binding reserved; stage 1
dir2Native type; binding reservedType name; binding reserved; stage 1
dir3Native type; binding reservedType name; binding reserved; stage 1
discardProtected peer word; binding reservedNo global reservation in these tables
dispatchNo global reservation in these tablesType name; binding reserved; stage 1
dline2Native type; binding reservedType name; binding reserved; stage 1
dline3Native type; binding reservedType name; binding reserved; stage 1
dmat2Native type; binding reservedType name; binding reserved; stage 1
dmat2x3Native type; binding reservedType name; binding reserved; stage 1
dmat3Native type; binding reservedType name; binding reserved; stage 1
dmat3x4Native type; binding reservedType name; binding reserved; stage 1
dmat4Native type; binding reservedType name; binding reserved; stage 1
dnormal2Native type; binding reservedType name; binding reserved; stage 1
dnormal3Native type; binding reservedType name; binding reserved; stage 1
doUnavailable word; binding reservedNo global reservation in these tables
dobbNative type; binding reservedType name; binding reserved; stage 1
dobb2Native type; binding reservedType name; binding reserved; stage 1
dplaneNative type; binding reservedType name; binding reserved; stage 1
dpoint2Native type; binding reservedType name; binding reserved; stage 1
dpoint3Native type; binding reservedType name; binding reserved; stage 1
dposeNative type; binding reservedType name; binding reserved; stage 1
dquatNative type; binding reservedType name; binding reserved; stage 1
drayNative type; binding reservedType name; binding reserved; stage 1
dray2Native type; binding reservedType name; binding reserved; stage 1
drectNative type; binding reservedType name; binding reserved; stage 1
dsegment2Native type; binding reservedType name; binding reserved; stage 1
dsegment3Native type; binding reservedType name; binding reserved; stage 1
dsphereNative type; binding reservedType name; binding reserved; stage 1
dstate3Native type; binding reservedType name; binding reserved; stage 1
dtorusNative type; binding reservedType name; binding reserved; stage 1
dtriangle2Native type; binding reservedType name; binding reserved; stage 1
dtriangle3Native type; binding reservedType name; binding reserved; stage 1
dtwistNative type; binding reservedType name; binding reserved; stage 1
dualNative type; binding reservedType name; binding reserved; stage 1
durationNative type; binding reservedType name; binding reserved; stage 1
dutySyntax keyword; binding reservedNo global reservation in these tables
dvec2Native type; binding reservedType name; binding reserved; stage 1
dvec3Native type; binding reservedType name; binding reserved; stage 1
dvec4Native type; binding reservedType name; binding reserved; stage 1
dwrenchNative type; binding reservedType name; binding reserved; stage 1
elifUnavailable word; binding reservedNo global reservation in these tables
ellipseNative type; binding reservedType name; binding reserved; stage 1
ellipsoidNative type; binding reservedType name; binding reserved; stage 1
elseActive lexer word; binding reservedLexer word; binding reserved; stage 0
elseifActive lexer word; binding reservedLexer word; binding reserved; stage 0
embedNo global reservation in these tablesLexer word; binding reserved; stage 1
entryNo global reservation in these tablesLexer word; binding reserved; stage 1
enumActive lexer word; binding reservedLexer word; binding reserved; stage 0
exactNative type; binding reservedType name; binding reserved; stage 1
exclusiveNo global reservation in these tablesLexer word; binding reserved; stage 1
exportUnavailable word; binding reservedLexer word; binding reserved; stage 0
extendsUnavailable word; binding reservedNo global reservation in these tables
externUnavailable word; binding reservedLexer word; binding reserved; stage 0
f16Native type; binding reservedType name; binding reserved; stage 1
f32Native type; binding reservedType name; binding reserved; stage 1
f64Native type; binding reservedType name; binding reserved; stage 1
failActive lexer word; binding reservedLexer word; binding reserved; stage 0
failsNo global reservation in these tablesLexer word; binding reserved; stage 0
falseActive lexer word; binding reservedLexer word; binding reserved; stage 0
fieldNative type; binding reservedType name; binding reserved; stage 1
fieldelemNative type; binding reservedType name; binding reserved; stage 1
finalUnavailable word; binding reservedNo global reservation in these tables
finallyUnavailable word; binding reservedNo global reservation in these tables
flagsetActive lexer word; binding reservedLexer word; binding reserved; stage 1
forUnavailable word; binding reservedNo global reservation in these tables
foreachActive lexer word; binding reservedLexer word; binding reserved; stage 0
forkSyntax keyword; binding reservedNo global reservation in these tables
fragmentProtected peer word; binding reservedNo global reservation in these tables
frameidNative type; binding reservedType name; binding reserved; stage 1
funcUnavailable word; binding reservedNo global reservation in these tables
fxrateNative type; binding reservedType name; binding reserved; stage 1
geodeticNative type; binding reservedType name; binding reserved; stage 1
globalUnavailable word; binding reservedNo global reservation in these tables
gotoUnavailable word; binding reservedNo global reservation in these tables
graphType constructor; binding reservedNo global reservation in these tables
guidNative type; binding reservedType name; binding reserved; stage 1
handleNo global reservation in these tablesType name; binding reserved; stage 1
hvec2Native type; binding reservedType name; binding reserved; stage 1
hvec3Native type; binding reservedType name; binding reserved; stage 1
hvec4Native type; binding reservedType name; binding reserved; stage 1
i128Native type; binding reservedType name; binding reserved; stage 1
i16Native type; binding reservedType name; binding reserved; stage 0
i32Native type; binding reservedType name; binding reserved; stage 0
i64Native type; binding reservedType name; binding reserved; stage 0
i8Native type; binding reservedType name; binding reserved; stage 0
ifActive lexer word; binding reservedLexer word; binding reserved; stage 0
image2dGPU type; binding reservedNo global reservation in these tables
implUnavailable word; binding reservedNo global reservation in these tables
implementsUnavailable word; binding reservedNo global reservation in these tables
importUnavailable word; binding reservedNo global reservation in these tables
inActive lexer word; binding reservedLexer word; binding reserved; stage 0
infinityActive lexer word; binding reservedLexer word; binding reserved; stage 1
ingotActive lexer word; binding reservedType name; binding reserved; stage 1
inlineUnavailable word; binding reservedNo global reservation in these tables
instanceofUnavailable word; binding reservedNo global reservation in these tables
intentNo global reservation in these tablesLexer word; binding reserved; stage 0
intent!No global reservation in these tablesLexer word; binding reserved; stage 1
interfaceUnavailable word; binding reservedNo global reservation in these tables
interruptNo global reservation in these tablesLexer word; binding reserved; stage 1
intervalNative type; binding reservedType name; binding reserved; stage 1
intrinsicNo global reservation in these tablesLexer word; binding reserved; stage 0
ipNative type; binding reservedType name; binding reserved; stage 1
ipprefixNative type; binding reservedType name; binding reserved; stage 1
isActive lexer word; binding reservedLexer word; binding reserved; stage 0
ivec2Native type; binding reservedType name; binding reserved; stage 1
ivec3Native type; binding reservedType name; binding reserved; stage 1
ivec4Native type; binding reservedType name; binding reserved; stage 1
iwordNo global reservation in these tablesType name; binding reserved; stage 0
lambdaUnavailable word; binding reservedNo global reservation in these tables
letUnavailable word; binding reservedNo global reservation in these tables
line2Native type; binding reservedType name; binding reserved; stage 1
line3Native type; binding reservedType name; binding reserved; stage 1
lockActive lexer word; binding reservedProtected peer word; binding reserved
lock?Active attached keyword; no name positionNo global reservation in these tables
loopNo global reservation in these tablesLexer word; binding reserved; stage 0
macroUnavailable word; binding reservedNo global reservation in these tables
mat2Native type; binding reservedType name; binding reserved; stage 1
mat2x3Native type; binding reservedType name; binding reserved; stage 1
mat3Native type; binding reservedType name; binding reserved; stage 1
mat3x4Native type; binding reservedType name; binding reserved; stage 1
mat4Native type; binding reservedType name; binding reserved; stage 1
matchUnavailable word; binding reservedNo global reservation in these tables
measureNative type; binding reservedType name; binding reserved; stage 1
measurementNative type; binding reservedType name; binding reserved; stage 1
memoryNo global reservation in these tablesLexer word; binding reserved; stage 0
merReserved root; catalogNo global reservation in these tables
moduleUnavailable word; binding reservedNo global reservation in these tables
moneyNative type; binding reservedType name; binding reserved; stage 1
moveUnavailable word; binding reservedNo global reservation in these tables
multivectorNative type; binding reservedType name; binding reserved; stage 1
mutUnavailable word; binding reservedNo global reservation in these tables
namespaceUnavailable word; binding reservedNo global reservation in these tables
nanActive lexer word; binding reservedLexer word; binding reserved; stage 1
newUnavailable word; binding reservedNo global reservation in these tables
nilUnavailable word; binding reservedNo global reservation in these tables
normal2Native type; binding reservedType name; binding reserved; stage 1
normal3Native type; binding reservedType name; binding reserved; stage 1
notUnavailable word; binding reservedNo global reservation in these tables
nullActive lexer word; binding reservedLexer word; binding reserved; stage 1
obbNative type; binding reservedType name; binding reserved; stage 1
obb2Native type; binding reservedType name; binding reserved; stage 1
observationActive lexer word; binding reservedProtected peer word; binding reserved
onActive lexer word; binding reservedLexer word; binding reserved; stage 0
operatorUnavailable word; binding reservedNo global reservation in these tables
orUnavailable word; binding reservedNo global reservation in these tables
outlineActive lexer word; binding reservedType name; binding reserved; stage 1
overrideUnavailable word; binding reservedNo global reservation in these tables
packageUnavailable word; binding reservedNo global reservation in these tables
parallelNo global reservation in these tablesLexer word; binding reserved; stage 1
phraseFuture system type; binding reservedNo global reservation in these tables
pkgReserved root; catalogNo global reservation in these tables
planeNative type; binding reservedType name; binding reserved; stage 1
point2Native type; binding reservedType name; binding reserved; stage 1
point3Native type; binding reservedType name; binding reserved; stage 1
poolNo global reservation in these tablesType name; binding reserved; stage 1
poseNative type; binding reservedType name; binding reserved; stage 1
principalNo global reservation in these tablesType name; binding reserved; stage 1
privateActive lexer word; binding reservedLexer word; binding reserved; stage 0
probeNo global reservation in these tablesLexer word; binding reserved; stage 1
programNo global reservation in these tablesLexer word; binding reserved; stage 0
ptrNo global reservation in these tablesType name; binding reserved; stage 0
publicUnavailable word; binding reservedNo global reservation in these tables
pwhashNative type; binding reservedType name; binding reserved; stage 1
qd256Native type; binding reservedType name; binding reserved; stage 1
quantityNative type; binding reservedType name; binding reserved; stage 1
quatNative type; binding reservedType name; binding reserved; stage 1
rationalNative type; binding reservedType name; binding reserved; stage 1
rawNo global reservation in these tablesLexer word; binding reserved; stage 0
rayNative type; binding reservedType name; binding reserved; stage 1
ray2Native type; binding reservedType name; binding reserved; stage 1
rectNative type; binding reservedType name; binding reserved; stage 1
refNo global reservation in these tablesType name; binding reserved; stage 0
regionProtected peer word; binding reservedLexer word; binding reserved; stage 0
repeatActive lexer word; binding reservedLexer word; binding reserved; stage 0
resumeActive lexer word; binding reservedProtected peer word; binding reserved
retainNo global reservation in these tablesLexer word; binding reserved; stage 1
retryActive lexer word; binding reservedProtected peer word; binding reserved
returnActive lexer word; binding reservedLexer word; binding reserved; stage 0
revealProtected peer word; binding reservedLexer word; binding reserved; stage 1
rotorNative type; binding reservedType name; binding reserved; stage 1
runeNative type; binding reservedType name; binding reserved; stage 1
sReserved rootNo global reservation in these tables
sceneQueryGPU type; binding reservedNo global reservation in these tables
sealedUnavailable word; binding reservedNo global reservation in these tables
secretActive lexer word; binding reservedLexer word; binding reserved; stage 1
segmentNo global reservation in these tablesLexer word; binding reserved; stage 1
segment2Native type; binding reservedType name; binding reserved; stage 1
segment3Native type; binding reservedType name; binding reserved; stage 1
selfUnavailable word; binding reservedNo global reservation in these tables
sensitiveActive lexer word; binding reservedLexer word; binding reserved; stage 1
sha256Native type; binding reservedType name; binding reserved; stage 1
sizeofUnavailable word; binding reservedNo global reservation in these tables
slabSyntax keyword; binding reservedNo global reservation in these tables
spanNo global reservation in these tablesType name; binding reserved; stage 0
spawnUnavailable word; binding reservedNo global reservation in these tables
sphereNative type; binding reservedType name; binding reserved; stage 1
state3Native type; binding reservedType name; binding reserved; stage 1
staticUnavailable word; binding reservedLexer word; binding reserved; stage 0
storageGPU type; binding reservedNo global reservation in these tables
streamNative type; binding reservedType name; binding reserved; stage 1
structProtected peer word; binding reservedLexer word; binding reserved; stage 0
subscribeUnavailable word; binding reservedNo global reservation in these tables
superUnavailable word; binding reservedNo global reservation in these tables
switchUnavailable word; binding reservedNo global reservation in these tables
syscallNo global reservation in these tablesLexer word; binding reserved; stage 1
tReserved root; catalogNo global reservation in these tables
taskActive lexer word; binding reservedProtected peer word; binding reserved
td192Native type; binding reservedType name; binding reserved; stage 1
templateUnavailable word; binding reservedNo global reservation in these tables
tensorNative type; binding reservedType name; binding reserved; stage 1
textNative type; binding reservedType name; binding reserved; stage 0
texture2dGPU type; binding reservedNo global reservation in these tables
texture2dArrayGPU type; binding reservedNo global reservation in these tables
texture3dGPU type; binding reservedNo global reservation in these tables
textureCubeGPU type; binding reservedNo global reservation in these tables
thenUnavailable word; binding reservedNo global reservation in these tables
thisUnavailable word; binding reservedNo global reservation in these tables
threadNo global reservation in these tablesLexer word; binding reserved; stage 1
throwUnavailable word; binding reservedNo global reservation in these tables
timepointNative type; binding reservedType name; binding reserved; stage 1
timerangeNative type; binding reservedType name; binding reserved; stage 1
torusNative type; binding reservedType name; binding reserved; stage 1
traitUnavailable word; binding reservedNo global reservation in these tables
transactionSyntax keyword; binding reservedNo global reservation in these tables
trapsNo global reservation in these tablesLexer word; binding reserved; stage 1
treeType constructor; binding reservedNo global reservation in these tables
triangle2Native type; binding reservedType name; binding reserved; stage 1
triangle3Native type; binding reservedType name; binding reserved; stage 1
troveActive lexer word; binding reservedProtected peer word; binding reserved
trsRecord convention; binding reservedNo global reservation in these tables
trueActive lexer word; binding reservedLexer word; binding reserved; stage 0
tryUnavailable word; binding reservedNo global reservation in these tables
twistNative type; binding reservedType name; binding reserved; stage 1
typeActive lexer word; binding reservedLexer word; binding reserved; stage 1
typenameUnavailable word; binding reservedNo global reservation in these tables
typeofUnavailable word; binding reservedNo global reservation in these tables
u128Native type; binding reservedType name; binding reserved; stage 1
u16Native type; binding reservedType name; binding reserved; stage 0
u32Native type; binding reservedType name; binding reserved; stage 0
u64Native type; binding reservedType name; binding reserved; stage 0
u8Native type; binding reservedType name; binding reserved; stage 0
uniformGPU type; binding reservedNo global reservation in these tables
unitNative type; binding reservedType name; binding reserved; stage 1
unlessUnavailable word; binding reservedNo global reservation in these tables
unsafeUnavailable word; binding reservedNo global reservation in these tables
unsubscribeUnavailable word; binding reservedNo global reservation in these tables
untilUnavailable word; binding reservedNo global reservation in these tables
useActive lexer word; binding reservedLexer word; binding reserved; stage 0
usingUnavailable word; binding reservedNo global reservation in these tables
uvec2Native type; binding reservedType name; binding reserved; stage 1
uvec3Native type; binding reservedType name; binding reserved; stage 1
uvec4Native type; binding reservedType name; binding reserved; stage 1
uwordNo global reservation in these tablesType name; binding reserved; stage 0
variantProtected peer word; binding reservedLexer word; binding reserved; stage 0
varintNative type; binding reservedType name; binding reserved; stage 1
vec2Native type; binding reservedType name; binding reserved; stage 1
vec3Native type; binding reservedType name; binding reserved; stage 1
vec4Native type; binding reservedType name; binding reserved; stage 1
vertexProtected peer word; binding reservedNo global reservation in these tables
virtualUnavailable word; binding reservedNo global reservation in these tables
voidActive lexer word; binding reservedLexer word; binding reserved; stage 0
volatileUnavailable word; binding reservedNo global reservation in these tables
waitNo global reservation in these tablesLexer word; binding reserved; stage 1
weakUnavailable word; binding reservedNo global reservation in these tables
whenActive lexer word; binding reservedLexer word; binding reserved; stage 0
whereUnavailable word; binding reservedNo global reservation in these tables
whileUnavailable word; binding reservedNo global reservation in these tables
withActive positional syntax; binding reservedLexer word; binding reserved; stage 0
workgroupProtected peer word; binding reservedNo global reservation in these tables
wrenchNative type; binding reservedType name; binding reserved; stage 1
xorUnavailable word; binding reservedNo global reservation in these tables
yieldUnavailable word; binding reservedLexer word; binding reserved; stage 1
FURTH FORTUNE / ENGINEERING REFERENCEBack to top ↑