Kagari
Type to search documentation.

Syntax

Types

Kagari is statically typed. Every value has one type, known at compile time. Types are written out in item signatures and inferred inside function bodies.

Type expressions

SyntaxType
Path, Path<Args>a named type: primitive, struct, enum, type alias or generic parameter
()the unit type, the empty tuple
(A,), (A, B), …a tuple
(A)A; parentheses only group
[T; N]an array of N elements
&T, &mut Ta shared or mutable reference
fn(A) -> R, const fn(A) -> R, pushdown fn(A) -> R, scene fn(A) -> Ra function pointer
Fn(A) -> R, const Fn(A) -> R, pushdown Fn(A) -> Ra closure type
_a placeholder: “infer this type”
Selfthe target type inside an impl

A function pointer or closure type without -> R returns (). The placeholder _ is allowed in let annotations and generic arguments inside bodies, but not in item signatures (E2011). There are no slice types [T], no str, no ! type syntax, no dyn or impl Trait types and no raw pointers.

Overview

TypeValuesNotes
booltrue, false
()()the value of blocks and functions without a result
Int<lo, hi>the integers from lo to hiprimitive; see Numbers
u8 … u128, i8 … i128, usize, isizeas in Rustsee Rust integers
Decimal, Ratexact decimals and fractionssee Numbers
Textconstant textsee Text and String
Stringruntime text
tuples, arrays(bool, Route), [Int<0, 3>; 4]
Vec<T>growable lists
Range<T>0..4, 3..=5see Ranges
structs, enumsuser-declared; Option<T> and Result<T, E> from the prelude
references&T, &mut Tsee Values, References and Closures
function pointers, closuresfn(u32) -> bool, Fn(u32) -> boolsee Closure and pointer types
builtin typesScreen, Narrative, Element, Setting<T>, Meta<T>, the asset types, …opaque handles; see Builtin Types

There are no floating-point types. Which types the story may hold at runtime is restricted to finite types.

Primitive types

bool, Int and the twelve Rust integer types are primitive types. They have no declaration and are found after every other name (see Paths). Int always takes exactly two bounds, the other primitives take no generic arguments (E2005). User code cannot add an impl to a primitive type.

Text and String

TypeWhat it is
TextConstant text of the program: a plain string literal ("Mio") or rich text from t![…] with ruby and text effects. Every string literal has type Text. Text is finite: its values are the texts written in the program, so the story may hold it
StringA string built at runtime by fn code. Not finite

Both are Eq. Text is declared in std::narrative::text; String in std. Both are in the prelude.

Tuples, arrays and Vec

  • A tuple (A, B, …) groups values of different types. Its fields are .0, .1, …. A one-element tuple is written (A,), and () is the unit type.
  • An array [T; N] holds exactly N values of type T. N is a type-level constant. An array is written [a, b, c] or [value; N] and indexed with a[i]; an index out of range is an error at compile time or a VM panic at runtime.
  • Vec<T> is a list whose length changes at runtime: Vec::new(), push, len, is_empty, get and set. It is not finite. See the STD Reference.

Structs and enums

Structs and enums are nominal: two declarations with the same fields are different types. They may be generic. The standard library declares Option<T> (None, Some(T)) and Result<T, E> (Ok(T), Err(E)), with their variants in the prelude. Their declaration forms are in Items.

Ranges

a..b and a..=b are values of the standard struct Range<T>:

kis
pub struct Range<T> {
pub start: T,
pub end: T,
pub inclusive: bool,
}

a..b excludes b, a..=b includes it. As values, only a..b and a..=b exist; any other form, such as a.. or ..b, is E2030. Patterns have their own range forms, a.., ..=b, a..b and a..=b (see Range patterns); there is no ..b pattern, and a bare .. in a pattern is the rest pattern. Ranges are iterated by for (see Control Flow and Loops).

Generics

Functions, structs, enums, type aliases and impl blocks may have generic parameters:

kis
struct Pair<T> { a: T, b: T }
fn first<T, const N: usize>(items: [T; N]) -> T {
items[0]
}
  • A type parameter T may have bounds: T: Eq, T: Eq + Integer.
  • A const parameter const N: usize stands for a constant of an integer type. Int<lo, hi> itself is the primitive with two i64 const parameters.
  • Generic arguments are inferred, or written explicitly: Pair<u8> in a type, first::<u8, 3>(…) (the turbofish) in an expression. A wrong number of arguments is E2005.
  • Generic code is monomorphized: the compiler makes a copy for each combination of arguments that the program uses. In a pushdown fn, every type argument must be a finite type, so the story stays a finite program.
  • There are no lifetimes, no where clauses, no default arguments and no associated types of your own.

Type-level constants

Array lengths, Int bounds and const generic arguments are type-level constants. As a generic argument one is written as an integer literal (Square<8>), a negative literal (Int<-5, 5>), the name of a const parameter or const item (Int<0, MAX>), or an expression in braces (Int<0, { MAX * 2 }>). The expression may use only integer literals, unary -, + - * / %, blocks with only a final expression, const parameters and const items whose values are such expressions (E2002). A type given where a constant is expected is E2007.

kis
const MAX: i64 = 5;
static mut TRUST: Int<0, MAX> = 0;
static mut DOUBLE: Int<0, { MAX * 2 }> = 0;

Trait bounds

A bound names a trait of the standard library. Code cannot declare or implement traits, but it can require them:

TraitImplemented byGives
Eqevery type with structural equality, see Equality==, !=
IntegerInt<lo, hi> and the Rust integersinto, saturate; see Conversions
RandChoosefieldless enums and Int<lo, hi>rng_choose::<T>()
Assetthe asset typesasset::<A>(…)
Stepthe steps Say and Choicepush_step, with the associated type Output
ToTextText, String, Int<lo, hi>, the Rust integers, Rat, Decimalinterpolation {expr} in t![…]

A generic argument that does not satisfy a bound is E2026. A bound makes the trait’s items available on the parameter: S::Output for S: Step, value.into() for T: Integer. Details are in Language Traits.

Equality

There is no derive. == and != need Eq on both sides (E2017), and Eq is implemented automatically:

  • by bool, Int<lo, hi> and the Rust integers;
  • by a struct, enum, tuple or array whose fields or elements are all Eq, comparing structurally: two values are equal when they are built the same way;
  • by function pointers and named functions, which compare by identity;
  • by &T and &mut T when T is Eq, comparing the values they point to;
  • by the builtin types that the standard library declares Eq: String, Vec<T> (when T is Eq), Rat, Decimal, Text, Screen and the asset types.

Closures are never Eq. Other builtin types are not Eq.

Finite types

The story holds only finite types at runtime: types with finitely many values that the compiler can enumerate. In short:

FiniteNot finite
bool, (), Int<lo, hi>Rust integers, Rat, Decimal
structs, enums, tuples and arrays of finite typesString, Vec<T>, recursive types
Text and the asset typesScreen and the other builtin handles
pushdown fn, scene fn and const fn pointersfn pointers, closures
&T and &mut T of a finite T, as parameters only

A constant expression of any type may still appear in the story, because its value is computed at compile time. The full rule is in Function Kinds.

Function pointer and closure types

Function pointer and closure types carry the kind of the code they call, so the call rules hold through them:

TypeHolds
fn(A) -> R, const fn(A) -> Ra named function of that kind
pushdown fn(A) -> Ra named instant procedure
scene fn(A) -> Ra named scene (tentative syntax)
Fn(A) -> R, const Fn(A) -> R, pushdown Fn(A) -> Ra closure or a function of that kind (tentative syntax)

They are described with their coercions in Values, References and Closures.

Coercions

Where a value of one type is expected and another is given, these implicit conversions apply:

FromTo
an integer literalany integer type whose range contains it
Int<l1, h1>Int<l2, h2> with l2 <= l1 and h1 <= h2 (widening)
&mut T&T
a named function or tuple constructora function pointer of the same kind and signature
a named function, function pointer or closurea closure type of the same kind (not for scenes)
a closure that captures nothinga function pointer of the same kind
the type of return, break, continue and endless loopany type

Every other change of type is explicit, for example x.into().

Type inference

Inside a body, types are inferred from use, as in Rust. Item signatures are never inferred: parameter types, return types and the types of constants and statics are always written. When inference cannot decide a type, an annotation is needed (E2029):

kis
fn demo() {
let names: Vec<Text> = Vec::new();
let level: Int<0, 5> = 3;
}

Last updated Oct 29, 2026