"Reference: Type System"

Reference: Type System

Vox features a strongly-typed, expressive type system designed for technical unification between Rust (backend) and TypeScript (frontend). It is designed to be AI-readable, meaning the type signatures provide enough context for an LLM to generate correct code without hallucinating field names.

1. Core Philosophy: Zero-Null Discipline

In Vox, null and undefined do not exist. Absence must be modeled explicitly using Option[T], and fallible operations must use Result[T, E].

FeatureVox ImplementationBenefit
AbsenceOption[T]Forced handling of empty states; no "null pointer" crashes.
FailureResult[T, E]Errors are part of the type signature; cannot be ignored.
BranchingPattern MatchingCompiler ensures all cases (variants) are handled.

2. Primitive Types

TypeDescriptionRust EquivalentTS Equivalent
strUTF-8 StringStringstring
int64-bit Integeri64number / BigInt
float64-bit Floatf64number
boolBooleanboolboolean
UnitEmpty placeholder()void

3. Algebraic Data Types (ADTs)

Structs (Product Types)

A named collection of fields.

// vox:skip
@table type Task {
    id:       Id[Task]
    title:    str
    done:     bool
    priority: int
}

Enums (Sum Types / Tagged Unions)

Types that can be one of several variants, potentially carrying extra data.

type NetworkState = 
    | Disconnected
    | Connecting
    | Connected(address: str, port: int)

Vox uses the match keyword for exhaustive destructuring of ADTs. The compiler will reject a match expression that does not cover every possible variant.

fn handle_state(net_state: NetworkState) {
    match net_state {
        Disconnected -> print("offline")
        Connecting -> print("connecting...")
        Connected(address, port) -> print("connected to " + address)
    }
}

Option[T]

Used for values that might be missing.

// vox:skip
fn find_user(id: int) -> Option[User] {
    return db.User.find(id)
}

Result[T, E]

Used for operations that can fail.

// vox:skip
@server fn update_task(id: Id[Task], title: str) -> Result[Unit, str] {
    if title.len() == 0 {
        return Err("Title cannot be empty")
    }
    db.patch(id, { title: title })
    return Ok(())
}

Similar to Rust, the ? operator can be used to early-return on None or Err.

// vox:skip
fn get_user_email(id: int) -> Option[str] {
    let user = find_user(id)? // If None, returns None early
    return Some(user.email)
}

7. Bidirectional Type Inference

You rarely need Type annotations for local variables. Vox infers them from the right-hand side or from how the variable is used.

// vox:skip
let x = 10                  // inferred as int
let names = ["Alice", "Bob"] // inferred as list[str]
let result = add_task("Hi")  // inferred from add_task signature

Explicit types are required on:

  1. Function parameters
  2. Function return types
  3. @table and type definitions

8. Collection Types

list[T]

An ordered sequence of elements.

  • Usage: list[int]
  • Literals: [1, 2, 3]

map[K, V]

A collection of key-value pairs.

  • Usage: map[str, int]
  • Literals: { "key": 10 }

9. Next Steps