# Types

URL: /docs/tour/types
Markdown: /docs/tour/types.md

> Primitive types, records, variants, refinements, generics with derived constraints, inference, method-call resolution, and exhaustive pattern matching in Sakura Lang.

> **Pre-alpha**
>
> The source-built compiler runs the core language and much of M3 on JavaScript: contracts, typed effects and failures, std handlers, permissions, LSP and MCP. M3 is still in progress; no release or stable compatibility contract exists. See the [roadmap](/docs/roadmap) for implemented features and remaining gates.

Sakura Lang is statically typed with Hindley-Milner inference inside bodies. Top-level signatures are always explicit. There are no implicit conversions, no null, and no subtyping except three narrow cases: refined types coerce to their base, smaller error unions fit larger ones, and closed effect rows widen at function-value use sites.

## Primitive types [#primitive-types]

| Type | Values | Notes |
| --- | --- | --- |
| `Unit` | `()` | Result of statement-only functions. |
| `Bool` | `true`, `false` | `&&` and `\|\|` short-circuit. |
| `Int` | 64-bit signed | Checked: overflow is defect `D001`, division by zero is `D002`. `/` and `%` truncate toward zero. On the JS backend, values beyond ±(2^53−1) are defect `D003`, never silently wrong. |
| `Float` | IEEE-754 binary64 | `NaN != NaN`. Not `Hash`, so not a `Map` key. |
| `String` | immutable UTF-8 text | No integer indexing. `String.len` is the byte length; `String.chars` and `String.graphemes` give lists. |
| `Bytes` | immutable bytes | `Bytes.get(b, i) -> Option<Int>`. |
| `Never` | none | Type of `fail e`, `return e`, `break`, `continue`. |

Conversions are explicit functions: `Int.to_float`, `Float.round` (defect on NaN or out of range), `Float.to_int_checked -> Option<Int>`, `Int.to_string`, `String.parse_int -> Option<Int>`. Parsing never defects: `String.parse_int` returns `None` for text that is not a number and for a number the backend cannot represent, which on JavaScript means beyond ±(2^53−1). `Decimal` (std) is a library type for money with `Decimal.add` and friends; it has no operators in v0.

## Records [#records]

Records are nominal. Construction names the type, and a field whose value is a same-named local can be written once (punning). Update copies a record and overrides the listed fields.

```sakura
type User = { id: Int, email: String, name: String }

test "records" {
  let id = 7
  let u = User { id, email: "a@b.co", name: "Ada" }
  let moved = User { ..u, email: "ada@new.co" }
  expect_eq(moved.email, "ada@new.co")
  expect_eq(u.email, "a@b.co")
}
```

Every field must be supplied at construction. Every field named in an update must exist. There are no anonymous records in v0. When `u` is not used again, the native backend can reuse its memory for the update; see [Memory model](/docs/tour/memory).

## Tuples [#tuples]

`(A, B)` is a tuple type, `(1, "one")` a tuple value, and `.0`, `.1` its fields. `(e)` is grouping, `(e,)` is a one-element tuple, and `()` is `Unit`.

## Variants [#variants]

A variant type is a nominal sum. Payloads are positional. Constructors are functions: `Circle` has type `fn(Float) -> Shape`.

```sakura
type Shape = Circle(Float) | Rect(Float, Float) | Empty

fn area(s: Shape) -> Float {
  match s {
    Circle(r) => 3.14159 * r * r
    Rect(w, h) => w * h
    Empty => 0.0
  }
}
```

Adding a variant makes every `match` on `Shape` that does not handle it a compile error (`V4001`), so the change cannot be half applied.

## No null: Option [#no-null-option]

A value that may be absent has type `Option<T> = None | Some(T)`.

```sakura
fn find_user(users: List<User>, email: String) -> Option<User> {
  List.find(users, fn(u) { u.email == email })
}

fn display_name(users: List<User>, email: String) -> String {
  find_user(users, email).map(fn(u) { u.name }).unwrap_or("guest")
}
```

The `Option` module has `map`, `unwrap_or`, `is_some`, `or_fail` (turns `None` into a typed failure), and `expect`. `Option.expect` on `None` is defect `D030`; use it only where absence is a bug.

## Errors and Result [#errors-and-result]

`Result<T, E> = Ok(T) | Err(E)` exists, but a function that can fail declares `Fail<E>` in its effect row instead of returning `Result`. `E` is a union of nominal `error` tags.

```sakura
error ParseError(input: String)

fn parse_age(s: String) -> Int ! {Fail<ParseError>} {
  match String.parse_int(s) {
    Some(n) if n >= 0 => n
    _ => fail ParseError { input: s }
  }
}

test "bad age is a typed failure" {
  let r = try { parse_age("x") }
  expect_eq(r, Err(ParseError { input: "x" }))
}
```

`try { body }` without `catch` turns failures into a `Result`. Postfix `r?` turns a `Result` back into `Fail<E>`. Only error tags can be unioned: `type SignupError = EmailTaken | Banned`. Recovery with `try`/`catch` is covered in [Effects](/docs/tour/effects).

## Other built-in types [#other-built-in-types]

- **Collections**: `List<T>` (`[1, 2, 3]`), `Map<K, V>` and `Set<T>` (keys need `Hash`, iteration is insertion order), and `Range` (`0..n`, exclusive end).
- **Functions**: `fn(A, B) -> C ! row`. Without `!` the function is pure.
- **Handlers**: `Handler<Eff, r>`, a handler for `Eff` whose clauses perform `r`. See [Handlers](/docs/tour/handlers).
- **External types**: a `type` declared without a body, such as `pub type Scope`, has a host representation, no constructors, and no derived constraints. Only `@external` functions produce its values.
- **Resources**: `Fs.File`, `Sql.Conn`, `Http.Body`, `Task.Fiber<T>`. External types that are affine and scope-bound; see [Memory model](/docs/tour/memory#resources). `Dir` is an external capability type. Network attenuation uses `Http.scoped(hosts)`, not `NetScope`.
- **`Secret<T>` and `Untrusted<T>`**: see [Capabilities](/docs/tour/capabilities).
- **`Graph<N>`** with `NodeId` keys, for cyclic data; see [Memory model](/docs/tour/memory#cycles-use-graph).

## Refinement types [#refinement-types]

A refinement narrows a base type with a predicate. `it` is the value being checked.

```sakura
type Email = String where String.len(it) <= 254 && String.contains(it, "@")
type Percent = Int where it >= 0 && it <= 100
type NonEmpty<T> = List<T> where List.len(it) > 0
```

The predicate must be in the contract subset: pure, terminating, no loops or recursion ([design §7.1](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)). A refined type is a distinct nominal type with the same representation as its base. Values enter it through:

- `Percent.validate(n) -> Result<Percent, Violation>`, generated for every refined type. Use it for external input.
- `Percent.from(n) -> Percent`, a checked introduction that is defect `D010` on failure. Use it at trusted boundaries only; the planned `sakura audit` will list each use.
- Schema decoding (`Json.decode`, `Request.json`, `Llm.generate`), which checks nested refinements and reports a typed `DecodeError`, never a defect.
- Literals: `let p: Percent = 50` is checked at compile time where possible, otherwise at program start (warning `V5002`).

A refined value coerces implicitly to its base: a `Percent` works anywhere an `Int` does. The reverse never happens implicitly. Because no value can be mutated, a validated value stays valid. Contracts are covered in [Contracts](/docs/tour/contracts).

## Generics and constraints [#generics-and-constraints]

Generics are rank-1. Uppercase type parameters are type variables; lowercase ones are effect-row variables ([Effects](/docs/tour/effects)).

```sakura
type Pair<A, B> = { left: A, right: B }

fn swap<A, B>(p: Pair<A, B>) -> Pair<B, A> {
  Pair { left: p.right, right: p.left }
}

fn largest<A: Ord>(xs: List<A>) -> Option<A> {
  List.last(List.sort(xs))
}

fn render<A: Eq + Show>(xs: List<A>, highlight: A) -> String {
  List.join(List.map(xs, fn(x) { if x == highlight { "[{x}]" } else { "{x}" } }), ", ")
}
```

Constraints come from a fixed set that the compiler derives structurally. There are no traits, no interfaces, and no user-written instances in v0.

| Constraint | Derived for | Not derived for |
| --- | --- | --- |
| `Eq` | all data types, recursively | functions, handlers, resources, `Secret` |
| `Ord` | `Int`, `Float` (NaN last), `String`, `Bytes`, and tuples, records, variants of `Ord` parts (declaration order) | `Map`, `Set`, functions, `Secret` |
| `Hash` | `Eq` types except `Float`, `Map`, `Set` | as `Eq`, plus `Float` |
| `Show` | all data types; records print as `User { id: 1, email: "a@b" }` | functions, handlers, resources, `Secret` |
| `Schema` | data types made of `Schema` parts, refinements included (JSON Schema plus codec) | functions, handlers, resources, `Secret`, `Graph` |

An unconstrained type parameter can only be passed around and matched through concrete constructors. Each constraint unlocks a fixed set of operations: `Eq` gives `==` and `!=`; `Ord` gives `<`, `<=`, `>`, `>=` and implies `Eq`; `Hash` makes a type usable as a `Map` key or `Set` element; `Show` allows interpolation and `show`; `Schema` allows `Json.encode` and `Json.decode`. A concrete type that lacks a required constraint is `V2032`.

### Show output [#show-output]

Interpolation uses `Show`. Its output is valid Sakura Lang expression syntax, stable across backends, and the format `sakura test` snapshots use ([design §4.8](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)):

| Value | Shows as |
| --- | --- |
| `Int`, `Bool`, `Unit` | `42`, `true`, `()` |
| `Float` | always with `.` or an exponent: `2.0`, `0.1`, `1.0e21`, `-0.0`, `NaN`, `Infinity` |
| `String` | raw at the top level of an interpolation, quoted with escapes inside any container |
| tuple, `List` | `(1, "a")`, `(1,)`, `[1, 2]` |
| `Map`, `Set`, `Bytes` | `Map.of([("a", 1)])`, `Set.of([1])`, `Bytes.of([1, 2])` |
| record | `User { id: 1, email: "a@b" }` |
| variant | unqualified constructor: `None`, `Some("x")`, `Warn`, `String("x")` |
| error tag | `Forbidden`, `NotFound { id: "u1" }` |

So `"{name}"` with `name = "Ada"` prints `Ada`, while `"{[name]}"` prints `["Ada"]`.

## Inference [#inference]

- Top-level functions are fully annotated and the body is checked against the signature.
- `let`, `var`, and lambda parameters are inferred. Only syntactic values (lambdas, literals, constructors) are generalized; an effectful expression never is.
- A lambda is checked against the expected `fn` type when one is known, such as an argument position.
- Top-level functions may be mutually recursive. Local lambdas may not recurse (`V1020`).
- You must also annotate `pub let` constants, `property` parameters, and empty collections whose element type cannot be inferred (`V2010`). There are no call-site type arguments (`V0011`); a type-directed function gets its type from a binding annotation:

```sakura
import std/json.{DecodeError}

fn start() -> List<Int> {
  let xs: List<Int> = List.empty()   // not List<Int>.empty()
  List.push(xs, 1)
}

fn load(text: String) -> User ! {Fail<DecodeError>} {
  let u: User = Json.decode(text)?   // not Json.decode<User>(text)
  u
}
```

## Type aliases and distinct types [#type-aliases-and-distinct-types]

`type Meters = Float` is a transparent alias: `Meters` and `Float` are the same type. For a distinct type, use a refinement or a single-variant type:

```sakura
import std/http.{Request, Response}
import std/sql.{Sql}

type Meters = Float                              // alias
type UserId = UserId(Int)                        // distinct: UserId(7) is not an Int
type Route = fn(Request) -> Response ! {Sql, Log} // alias for a function type
```

A variant may share its type's name, as `UserId` does. Two refined types with identical predicates are still different types. Tuples, functions, lists, maps, sets, options, results, and ranges are structural in their parameters; everything else is nominal.

## Opaque types [#opaque-types]

`pub type` exports a type together with its constructors and fields. `pub opaque type` exports only the name: other modules can hold, pass, compare, and show its values, but constructing one or reading its fields outside the declaring module is `V1006`.

```sakura
// src/tokens.sakura
pub opaque type Token = Token(String)

pub fn issue(user: Int) -> Token ! {Random} {
  Token("{user}-{Random.uuid()}")
}
```

Other modules can only get a `Token` from `tokens.issue`. Std uses the same mechanism: `Uuid` comes only from `Uuid.parse` or `Random.uuid`, a SQL `Query<R>` only from the `sql"…"` template, and `Html` only from escaping code. Opaque types still derive `Eq`, `Ord`, `Hash`, `Show`, and `Schema`. To validate by predicate rather than by construction, use a refinement type.

## Method-call resolution [#method-call-resolution]

`e.f(args)` is resolved in this order; the first match wins ([design §4.9](https://github.com/icantcodefyi/sakura-lang/blob/master/docs/design.md)):

1. `e` is a record and `f` is a field: field access.
2. `e` names an effect: `Eff.op(args)` performs the operation on the innermost installed handler. If `op` is not an operation, `Eff.f` is a function of the effect's home module, which is how handler constructors such as `Console.host()`, `Clock.fixed(t)`, and `Users.memory([])` are found.
3. `e` names a type: `Type.f` is function `f` in the type's **home module** (the module that declared it; for compiler-known types, `std/<lowercase name>`, so `List.map` is `std/list.map`). `Type.Name` is a variant of the type or a type declared in its home module: `Json.String(s)`, `Level.Warn`, `Task.Fiber<T>`.
4. `e` names an imported module: `mod.f` is that module's exported `f`.
5. Otherwise `e` is a value of type `T`, and `e.f(args)` means `T.f(e, args)` with `f` looked up in `T`'s home module.

There are no methods, no `self`, and no extension methods. A function becomes callable with dot syntax on a type only by living in that type's module:

```sakura
// src/shapes.sakura, the home module of Shape
pub type Shape = Circle(Float) | Rect(Float, Float) | Empty

pub fn area(s: Shape) -> Float {
  match s {
    Circle(r) => 3.14159 * r * r
    Rect(w, h) => w * h
    Empty => 0.0
  }
}
```

```sakura
// src/report.sakura, in package `app`
import app/shapes.{Shape}

fn total_area(xs: List<Shape>) -> Float {
  List.fold(xs, 0.0, fn(acc, s) { acc + s.area() })   // s.area() is Shape.area(s)
}
```

On a generic `A` with no constraint, `x.len()` is an error with a fix suggesting a constraint; there is no structural lookup.

## Pattern matching [#pattern-matching]

`match` must be exhaustive. A missing case is `V4001` with a witness value; a redundant arm is `V4002`. Guards never count toward coverage.

```sakura
fn summary(xs: List<Int>) -> String {
  match xs {
    [] => "empty"
    [x] => "one item: {x}"
    [x, y] => "two items: {x}, {y}"
    [first, ..rest] => "{first} and {List.len(rest)} more"
  }
}

fn size(n: Int) -> String {
  match n {
    0 => "none"
    1 | 2 | 3 => "a few"
    _ if n < 0 => "negative"
    _ => "many"
  }
}

fn is_admin(u: User) -> Bool {
  match u {
    User { name: "root", .. } => true
    _ => false
  }
}
```

- Patterns: `_`, names, literals (including `-5`), constructors `Some(x)`, records `User { id, .. }`, tuples `(a, b)`, lists `[]`, `[x, ..rest]`, `[first, second, ..]`, and or-patterns `A | B`.
- Literal patterns on `Int`, `String`, or `Float` need a `_` arm.
- Record patterns need `..` to ignore remaining fields.
- All alternatives of an or-pattern must bind the same names with the same types (`V4003`).
- Matching on an error union is exhaustive over its tags.

## Tagged templates [#tagged-templates]

A tagged string `tag"…"` desugars to `tag.template(parts, values)`, where `tag` resolves to a module that exports `template`. Each interpolated value is checked against the tag's argument type, and the tag receives it as a value, never as spliced text.

```sakura
import std/sql.{Sql, SqlError}

fn find_by_email(email: Email) -> List<User> ! {Sql, Fail<SqlError>} {
  Sql.query(sql"select id, email, name from users where email = {email}")
}
```

Here `{email}` becomes a bound parameter. For `sql`, an `Untrusted<String>` or a refined string converts to `SqlValue`, but a raw table name does not. `html"<p>{name}</p>"` escapes text by construction. Because `Query<R>` and `Html` are opaque types, a template is the only way to build one. The prelude tags are `sql`, `html`, `url`, and `shell`. See [Capabilities](/docs/tour/capabilities).

## What the compiler checks [#what-the-compiler-checks]

- Every expression has one type. No implicit conversions, and `Int` and `Float` never mix.
- Matches are exhaustive (`V4001`) with no redundant arms (`V4002`).
- There is no null and no uninitialized value. Absence is `Option`.
- Record construction supplies every field.
- Refined values are checked at introduction: `validate` returns a `Result`, `from` defects (`D010`), decoding returns `DecodeError`. They are never silently trusted.
- Constraints are derived, never declared, so `Secret`, functions, and resources can never be shown, compared, or serialized by accident.

## Row parameters on types [#row-parameters-on-types]

Types can carry effect rows as well as ordinary type arguments: `Tool<e>`, `Agent<T, e>`, and `Router<e>`. A concrete row is written `Tool<{Http}>`; a lowercase `e` denotes a row parameter, not a type parameter. These parameters preserve callback effects through records and collections. Access to opaque constructors and fields outside the defining module is rejected with `V1006`.

## Next [#next]

[Effects](/docs/tour/effects): how Sakura Lang puts every side effect and error in the signature.
