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.
| Type | Description |
|---|---|
Num | IEEE 754 64-bit floating point number. All numeric values in Turn are doubles. |
Str | UTF-8 encoded string. |
Bool | Boolean value: true or false. |
null | The explicit absence of a value. Returned by recall when a key does not exist. |
List | Ordered, growable collection. Supports indexing with list[i]. |
Map | String-keyed map. Access values with map["key"]. |
| Named struct | Named product type with typed fields, defined with struct Name { field: Type }. |
Pid | Process identifier. Returned by spawn and spawn_link. Used with send. |
Vec | Embedding vector for semantic similarity operations (~>). |
Identity | Opaque cryptographic capability handle. Returned by grant identity::*. Cannot be coerced to string. Used for Zero-Trust authentication. |
Any | Accepts any value. Use as the type argument to infer for free-form responses. |
Void | No 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.
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.
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.
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 VMRunning 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 kind | Example | What the error shows |
|---|---|---|
| Struct | WrongPayload passed where StripePayload required | Field-level layout diff |
| Primitive | "hello" passed where Num required | Expected Num, got Str |
| Generic container | List<Str> passed where List<Num> required | Inner 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.
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.