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
| Syntax | Type |
|---|---|
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 T | a shared or mutable reference |
fn(A) -> R, const fn(A) -> R, pushdown fn(A) -> R, scene fn(A) -> R | a function pointer |
Fn(A) -> R, const Fn(A) -> R, pushdown Fn(A) -> R | a closure type |
_ | a placeholder: “infer this type” |
Self | the 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
| Type | Values | Notes |
|---|---|---|
bool | true, false | |
() | () | the value of blocks and functions without a result |
Int<lo, hi> | the integers from lo to hi | primitive; see Numbers |
u8 … u128, i8 … i128, usize, isize | as in Rust | see Rust integers |
Decimal, Rat | exact decimals and fractions | see Numbers |
Text | constant text | see Text and String |
String | runtime text | |
| tuples, arrays | (bool, Route), [Int<0, 3>; 4] | |
Vec<T> | growable lists | |
Range<T> | 0..4, 3..=5 | see Ranges |
| structs, enums | user-declared; Option<T> and Result<T, E> from the prelude | |
| references | &T, &mut T | see Values, References and Closures |
| function pointers, closures | fn(u32) -> bool, Fn(u32) -> bool | see Closure and pointer types |
| builtin types | Screen, 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
| Type | What it is |
|---|---|
Text | Constant 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 |
String | A 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 exactlyNvalues of typeT.Nis a type-level constant. An array is written[a, b, c]or[value; N]and indexed witha[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,getandset. 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>:
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:
struct Pair<T> { a: T, b: T }
fn first<T, const N: usize>(items: [T; N]) -> T { items[0]}- A type parameter
Tmay have bounds:T: Eq,T: Eq + Integer. - A const parameter
const N: usizestands for a constant of an integer type.Int<lo, hi>itself is the primitive with twoi64const 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 isE2005. - 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
whereclauses, 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.
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:
| Trait | Implemented by | Gives |
|---|---|---|
Eq | every type with structural equality, see Equality | ==, != |
Integer | Int<lo, hi> and the Rust integers | into, saturate; see Conversions |
RandChoose | fieldless enums and Int<lo, hi> | rng_choose::<T>() |
Asset | the asset types | asset::<A>(…) |
Step | the steps Say and Choice | push_step, with the associated type Output |
ToText | Text, String, Int<lo, hi>, the Rust integers, Rat, Decimal | interpolation {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
&Tand&mut TwhenTisEq, comparing the values they point to; - by the builtin types that the standard library declares
Eq:String,Vec<T>(whenTisEq),Rat,Decimal,Text,Screenand 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:
| Finite | Not finite |
|---|---|
bool, (), Int<lo, hi> | Rust integers, Rat, Decimal |
| structs, enums, tuples and arrays of finite types | String, Vec<T>, recursive types |
Text and the asset types | Screen and the other builtin handles |
pushdown fn, scene fn and const fn pointers | fn 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:
| Type | Holds |
|---|---|
fn(A) -> R, const fn(A) -> R | a named function of that kind |
pushdown fn(A) -> R | a named instant procedure |
scene fn(A) -> R | a named scene (tentative syntax) |
Fn(A) -> R, const Fn(A) -> R, pushdown Fn(A) -> R | a 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:
| From | To |
|---|---|
| an integer literal | any 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 constructor | a function pointer of the same kind and signature |
| a named function, function pointer or closure | a closure type of the same kind (not for scenes) |
| a closure that captures nothing | a function pointer of the same kind |
the type of return, break, continue and endless loop | any 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):
fn demo() { let names: Vec<Text> = Vec::new(); let level: Int<0, 5> = 3;}