Skip to main content
Kairo’s type system is designed around one central idea: types should reflect the business domain, not just the data structure. The distinction between a value (compared by its content) and an object (compared by its identity) is not just technical — it encodes how a concept behaves in the real world. A Money amount with the same number is the same amount regardless of which instance you hold; a Customer with the same name is still a different person. Kairo makes this distinction explicit, and all other type-system choices — non-null by default, no implicit conversions, no type aliases — serve the same goal of keeping domain intent clear and unambiguous.

Scalar Types

Kairo provides six built-in scalar types. There are no automatic widening conversions between them.
Decimal is the default type inferred for fractional literals like 19.99. If you need Float64, you must annotate the binding explicitly. This keeps financial and domain-numeric logic free from floating-point surprises.
Kairo has no broad implicit conversions. You cannot assign an Int32 where an Int64 is expected, or a Decimal where a Float64 is expected, without an explicit conversion.

Temporal Types

Kairo includes first-class temporal types for dates, times, and durations. These are part of the Core prelude and require no import.

Collection Types

Kairo’s built-in collections come in immutable and mutable variants. Immutable collections are the default — they cannot be modified after construction.

Immutable Collections

Mutable Collections

Prefer immutable collections in value types and function return positions. Use MutableList, MutableSet, and MutableMap only inside object types where state change is intentional.

Nullability

All types in Kairo are non-null by default. A value can only be null if its type is explicitly marked nullable with a trailing ?.
You must handle the nullable case before using a nullable value. Kairo will not let you call methods on a T? as if it were a T.
Avoid nullable types in domain value declarations unless the absence of a value is genuinely meaningful in your domain. In most cases, a dedicated enum or an optional wrapper value communicates intent more clearly.

value Declarations

A value is an immutable, content-based domain type. Two value instances with identical field contents are considered equal — there is no notion of reference identity. Use value for domain concepts that are defined by what they contain: money amounts, identifiers, addresses, measurements.
Members of a value are always immutable. You cannot mark a member var inside a value. All stored members must themselves be value-like — you cannot embed an object-typed member inside a value.

object Declarations

An object is an identity-bearing, stateful type. Two object instances are distinct even if all their fields are identical — identity is intrinsic. Use object for domain concepts that change over time and have a lifecycle: customers, orders, sessions, services.
Mark mutable members with var. Immutable members (no var) can only be set in init. Each object has exactly one init in v0.1.

contract Declarations

A contract declares a capability promise — a named set of requirements that any conforming type must satisfy. Contracts play the role that interfaces play in other languages.
Declare that a type conforms to one or more contracts by listing them after a colon in the type declaration:
In v0.1, only value and object types can declare contract conformance. Conformance must be declared at the type definition site — there are no retroactive or extension conformances in this version.

enum Declarations

An enum defines a closed set of named cases. Use enum when a concept has a fixed, known set of states or categories.
Enum cases are referenced using the type name as a qualifier:

Generics and Constraints

Kairo supports generic type parameters using angle-bracket syntax. Constrain a type parameter by naming a contract it must satisfy:
The constraint T: Named means the type argument must conform to the Named contract.

Result<T, E> — Outcome Type

Result<T, E> is the standard way to represent an operation that can either succeed with a value of type T or fail with an error of type E. It is part of the Core prelude and available without import.
Inspect a Result using .isSuccess, .isFailure, .value, and .error:
Accessing .value on a failed Result, or .error on a successful one, is a fault — an unexpected runtime error that terminates the current execution boundary. Always check .isSuccess before accessing .value or .error.
See Error Handling for full coverage of Result, faults, and error-type design.