Types
Lattice is dynamically typed: values carry their type at runtime, and type annotations are optional constraints enforced at runtime rather than compile time. Every value belongs to exactly one of 15 value types.
Value Types
| Type Name | Description | Literal Syntax |
|---|---|---|
Int | 64-bit signed integer | 42, -7 |
Float | 64-bit double-precision float | 3.14, 0.5 |
Bool | Boolean | true, false |
String | Heap-allocated UTF-8 string | "hello", 'raw' |
Array | Dynamic array of values | [1, 2, 3] |
Map | Hash map of key-value pairs | Map::new() |
Tuple | Fixed-size ordered collection | (1, "a", true) |
Struct | Named record with typed fields | Point { x: 1, y: 2 } |
Enum | Tagged union variant | Color::Red |
Set | Unordered collection of unique values | Set::new() |
Closure | First-class function value | |x| { x + 1 } |
Channel | Concurrency communication channel | Channel::new() |
Range | Integer range | 1..10 |
Nil | Absence of a value | nil |
Unit | No meaningful value (void-like) | implicit |
Int
The Int type represents 64-bit signed integers (int64_t). Integers
support arithmetic, bitwise, comparison, and shift operations.
let a = 42 let b = -7 let c = a + b * 2 // 28 let d = a & 0xFF // bitwise AND
Float
The Float type represents 64-bit IEEE 754 double-precision floating point values.
Mixed arithmetic between Int and Float promotes the integer to float.
Bool
The Bool type has exactly two values: true and false.
Logical operators &&, ||, and ! operate on booleans.
Conditional expressions (if, while) require boolean conditions.
String
Strings are heap-allocated, immutable sequences of UTF-8 bytes. They support interpolation,
concatenation with +, and a rich set of methods (see Standard Library).
Array
Arrays are dynamically-sized, ordered collections of values. Elements may be of mixed types.
Arrays support index access, mutation via .push() and index assignment, and
higher-order methods like .map(), .filter(), and .reduce().
flux arr = [1, "two", 3.0] arr.push(4) // [1, "two", 3.0, 4] arr[0] = 99 // [99, "two", 3.0, 4] let len = arr.len() // 4
.push() and index assignment mutate in-place. However, arrays are
passed by value to functions — the callee receives a copy.
Map
Maps are hash maps from string keys to values. Maps are created via Map::new()
and manipulated with .set(), .get(), .has(), and other methods.
Tuple
Tuples are fixed-size, ordered collections created with parenthesized comma-separated expressions. Unlike arrays, tuples are intended for heterogeneous groupings and cannot be resized.
let pair = (1, "hello") let first = pair[0] // 1
Struct
Structs are named records with a fixed set of fields. Each field has a name, a value, and optionally a type annotation and a phase. Struct fields may hold closures, enabling object-like patterns. See Items: Structs for declaration syntax.
Enum
Enums are tagged unions with named variants. Each variant may optionally carry a payload of one or more values. See Items: Enums for declaration syntax.
enum Shape { Circle(Float), Rectangle(Float, Float), Point } let s = Shape::Circle(5.0)
Set
Sets are unordered collections of unique values, backed by a hash map. They support
standard set operations: .add(), .remove(), .has(),
.union(), .intersection(), and .difference().
Closure
Closures are first-class function values that capture their enclosing environment. They are created with pipe-delimited parameter lists and may have default parameter values or variadic parameters.
closure ::= "|" [ closure_params ] "|" ( expression | block ) closure_params ::= closure_param { "," closure_param } closure_param ::= [ "..." ] IDENT [ "=" expression ]
Unlike function declarations, closure parameters do not support type annotations. Parameters are bare identifiers with optional defaults or a variadic prefix.
Channel
Channels are typed communication primitives for structured concurrency. Values sent across channels must be in the crystal (frozen) phase to prevent data races. See Concurrency.
Range
Ranges represent a half-open interval of integers from a start value (inclusive) to an
end value (exclusive). They are primarily used in for loops.
for i in 0..10 { print(i) // 0, 1, 2, ..., 9 }
Nil
Nil represents the absence of a value. It is the result of operations that
fail gracefully (e.g., accessing a missing map key) and is used with optional chaining.
Unit
Unit is the type of expressions that produce no meaningful value, such as
assignment statements or functions with no explicit return. It is similar to void
in other languages. The REPL does not display Unit results.
Type Annotations
type_expr ::= [ phase_prefix ] type_name | "[" type_expr "]" phase_prefix ::= "~" | "*" type_name ::= "Int" | "Float" | "Bool" | "String" | "Array" | "Map" | "Tuple" | "Set" | "Fn" | "Channel" | "Range" | identifier
Type annotations appear after colons in bindings, function parameters, and return types.
They are enforced at runtime. The phase prefix ~ denotes a fluid (mutable) type
and * denotes a crystal (immutable) type.
fn process(data: ~Array, config: *Map) -> String { // data must be fluid, config must be crystal return "done" }
Phase Tags
Every value carries a phase tag that controls its mutability. See Phase System for full details.
| Phase Tag | Name | Description |
|---|---|---|
fluid | Fluid | Mutable — can be modified freely |
crystal | Crystal | Immutable — frozen, cannot be modified |
unphased | Unphased | Default — phase not explicitly set |
sublimated | Sublimated | Shallow freeze — structure locked, nested elements remain mutable |
Lattice