Type System

Primitive Types

pub struct Primitives {
  text: String,           // Text data
  count: I32,             // 32-bit signed integer
  amount: F64,            // 64-bit IEEE 754 float
  active: Boolean,        // true or false
  logo: Path,             // File/resource paths
  pattern: Regex          // Regular expressions
}

Numeric Types

FormaLang has four width-tagged numeric primitives instead of a single generic Number type. Backends emit native instructions directly without guessing precision.

TypeRange / shape
I3232-bit signed integer (default for unsuffixed integer literals)
I6464-bit signed integer
F3232-bit IEEE 754 float
F6464-bit IEEE 754 float (default for unsuffixed float literals)

Numeric literals can carry an uppercase suffix to pin the type at the literal site:

let a = 42        // I32 (integer-syntax default)
let b = 42I64     // I64
let c = 3.14      // F64 (float-syntax default)
let d = 3.14F32   // F32

let big: I64 = 9_223_372_036_854_775_807
let tiny: F32 = 0.5F32

Suffix range checks happen at compile time; literals that don't fit their declared / suffixed type are a compile error.

Never Type

Never is an uninhabited type: it has no values and cannot be instantiated. It is used as a return type for functions that diverge (infinite loops, panics):

extern fn abort() -> Never

Array Types

Arrays hold multiple values of the same type:

pub struct Collections {
  names: [String],             // Variable-length array of strings
  scores: [I32],               // Variable-length array of integers
  flags: [Boolean],            // Variable-length array of booleans
  matrix: [[I32]],             // Nested arrays
  users: [User],               // Array of custom types
}

// Array literals
pub let tags = ["urgent", "bug", "frontend"]
pub let numbers = [1, 2, 3, 4, 5]
pub let empty = []

// Array destructuring (see Expressions for full rules)
pub let [first, second] = ["a", "b", "c"]
pub let [user, ...] = ["John", "pass", "etc"]

Optional Types

Optional types can be a value or nil:

pub struct User {
  name: String,
  email: String,
  nickname: String?,            // Optional field
  avatar: String?               // Optional field
}

pub let user1 = User(
  name: "Alice",
  email: "alice@example.com",
  nickname: "Ally",             // Provide a value
  avatar: nil                   // Explicitly nil
)

Dictionary Types

Key-value mappings using bracket syntax with colon:

pub struct AppConfig {
  settings: [String: I32],         // String keys to I32 values
  scores: [I32: String],           // I32 keys to String values
  cache: [String: User],           // String keys to custom types
  assets: [Path: String]           // Path keys to String values
}

// Dictionary literals (string keys must be quoted)
pub let settings: [String: I32] = ["timeout": 30, "maxRetries": 3]
pub let scores: [I32: String] = [100: "perfect", 95: "excellent"]
pub let assets: [Path: String] = [/logo.svg: "icon", /bg.png: "background"]
pub let empty: [String: Boolean] = [:]

Rules:

  • Keys can be any compiler-supported type (String, I32, Path, enum, etc.)
  • String keys must be quoted in literals: ["key": value]
  • Numeric keys are unquoted: [42: value]
  • Path keys use path literal syntax: [/path: value]
  • Empty dict: [:]
  • No destructuring support for dictionaries

Tuples

Named tuples group related values with field names:

pub struct Config {
  person: (name: String, age: I32),
  point: (x: I32, y: I32),
  nested: (user: (first: String, last: String), active: Boolean)
}

// Tuple literals
for item in items {
  let person = (name: "John", age: 30)
  let point = (x: 10, y: 20)
  let nested = (user: (first: "John", last: "Doe"), active: true)
}

// Accessing tuple fields
for item in items {
  let person = (name: "John", age: 30)
  let name = person.name      // Access by field name
}

Rules:

  • Tuples use parentheses: (name: value, ...)
  • All fields must be named (no positional tuples)
  • Access fields with dot notation: tuple.fieldName
  • Trailing comma allowed: (x: 1, y: 2,)
  • Tuples can be nested

Closure Types

Closure types define function signatures for callbacks and transformations. For closure expressions, see Closures.

pub struct Controls<E> {
  // No parameters - returns E
  onPress: () -> E,

  // Single parameter (default / let convention)
  onChange: String -> E,

  // Multiple parameters (comma-separated, no parens needed)
  onResize: I32, I32 -> E,

  // mut parameter: caller must pass a mutable binding
  onScale: mut I32 -> E,

  // sink parameter: caller's binding is consumed (moved)
  onSubmit: sink String -> E,

  // Optional closure (can be nil)
  onFocus: (String -> E)?,

  // Closure returning optional
  validate: String -> Boolean?
}

Type syntax:

ParametersSyntaxExample
None() -> T() -> Event
One (default)T -> UString -> Event
One (mut)mut T -> Umut I32 -> Event
One (sink)sink T -> Usink String -> Event
MultipleT, U -> VI32, I32 -> Point
Mixed conventionsmut T, sink U -> Vmut I32, sink String -> V

Rules:

  • Arrow -> separates parameters from return type
  • Multiple parameters are comma-separated (no parentheses required)
  • Empty parameters require parentheses: () -> T
  • Convention keywords (mut, sink) precede the type in the type position
  • Parser uses -> to determine grouping in ambiguous contexts

Generic Types

Types parameterized with type variables (full details in Generics):

Box<T>                      // Single type parameter
Pair<A, B>                  // Multiple type parameters
Container<T: Layout>        // With trait constraint
Widget<T: Render + Click>   // Multiple trait constraints
Result<String, I32>         // Instantiated generic