Types & Errors

Turn's type system is designed for cognitive safety when working with LLM inference, and explicit error handling for stochastic failures. Every value in Turn has a known type, and the runtime enforces type constraints at inference boundaries.


The Type System

Turn is dynamically typed at the value level, with structural type checking enforced at inference boundaries.

TypeDescription
NumIEEE 754 64-bit floating point number. All numeric values in Turn are doubles.
StrUTF-8 encoded string.
BoolBoolean value: true or false.
nullThe explicit absence of a value. Returned by recall when a key does not exist.
ListOrdered, growable collection. Supports indexing with list[i].
MapString-keyed map. Access values with map["key"].
Named structNamed product type with typed fields, defined with struct Name { field: Type }.
PidProcess identifier. Returned by spawn and spawn_link. Used with send.
VecEmbedding vector for semantic similarity operations (~>).
IdentityOpaque cryptographic capability handle. Returned by grant identity::*. Cannot be coerced to string. Used for Zero-Trust authentication.
AnyAccepts any value. Use as the type argument to infer for free-form responses.
VoidNo return value.

Cognitive Type Safety

LLM inference is inherently stochastic. A model might return malformed output, omit required fields, or produce values that violate structural constraints. Turn addresses this with the infer primitive.

infer Type

When you write infer Sentiment { prompt }, the Turn compiler generates a JSON Schema from the struct definition and sends it to the LLM as a response_format constraint. The LLM provider enforces the schema during generation. If the response still violates the schema (which can happen with weaker models), the VM automatically retries up to three times, injecting the validation error into the prompt as corrective feedback. This self-healing loop eliminates the need for manual JSON parsing and validation boilerplate.

confidence Expr

infer calls return an Uncertain value paired with a confidence score. The confidence operator extracts that score and lets you gate execution on the model's certainty:

let decision = infer Decision { "What is the next action?"; };
if confidence decision < 0.85 {
    call("echo", "Low confidence. Escalating.");
}

This is a bytecode instruction. The VM enforces that uncertain values cannot be used without explicitly acknowledging the uncertainty.


Compile-Time Type Enforcement

Turn's semantic analyzer runs before execution. Every program is type-checked at compile time. If an argument type does not match a function's declared parameter, Turn reports the exact location and the full type mismatch before a single bytecode instruction executes.

This is the guarantee that makes cognitive type safety complete: data flowing from infer into any downstream function must satisfy its schema contract at the compiler level. If it does not, the program is rejected.

type_error.tn
struct StripePayload { amount: Num, currency: Str };
struct WrongPayload  { amount: Str, currency: Num };

let charge = turn(payload: StripePayload) {
  call("echo", "Charging " + payload.amount);
};

let data = infer WrongPayload { "Charge $100 USD" };

call(charge, data); // Compile-time error, never reaches the VM

Running this program produces:

Type Analysis Errors:
  [10:1] Type mismatch: expected Struct("StripePayload", {amount: Num, currency: Str}),
                              got Struct("WrongPayload",  {amount: Str, currency: Num})

The error reports the full concrete field layout of both types, not just their names. This applies uniformly across all type categories:

Mismatch kindExampleWhat the error shows
StructWrongPayload passed where StripePayload requiredField-level layout diff
Primitive"hello" passed where Num requiredExpected Num, got Str
Generic containerList<Str> passed where List<Num> requiredInner type compared recursively

The analyzer resolves all type aliases and struct definitions before checking, so all mismatches surface in terms of concrete field layouts.

NOTE

Functions declared with Any as their parameter type, or called without type annotations, are excluded from this check. Turn uses gradual typing. Full enforcement requires explicit type annotations on function parameters.


Error Model

Turn has try/catch/throw for structured error handling within a process, and process exit signals for actor-level fault propagation.

try_catch.tn
let net  = use "std/net";
let json = use "std/json";

// Synchronous, recoverable failures
try {
  let id   = grant identity::network("public");
  let raw  = net.get({ "url": "https://api.example.com/data", "identity": id });
  let data = json.parse(raw);
  call("echo", data["name"]);
} catch(e) {
  call("echo", "Failed: " + e);
}

// Throw any value
if user_input == null {
  throw "Missing required input: user_input";
}

Process-level failures (unhandled throws, token budget exhaustion) propagate as exit signals through the supervision tree, following Erlang's "let it crash" philosophy. Errors are isolated to individual processes and handled structurally by supervisors rather than through deep stack unwinding.

NOTE

See Error Handling for the full supervision tree pattern using spawn_link and receive.


Next Steps