Types and literals
The primitive types, what each one holds, and the coercions the compiler will and will not perform.
Ignis is strongly typed. Annotations are explicit at declaration boundaries, and the compiler infers within an expression rather than across one.
Primitives
| Group | Types |
|---|---|
| Signed integers | i8, i16, i32, i64 |
| Unsigned integers | u8, u16, u32, u64 |
| Floating point | f32, f64 |
| Boolean | boolean |
| Text | char, str |
| Labels | atom |
| Unit and bottom | void, never |
Text
str is a UTF-8 byte string slice. char is one Unicode scalar value — not a byte, and not a
grapheme cluster. A character literal that resolves to zero scalars, more than one scalar, or a
surrogate escape is rejected at compile time.
String is the owned, heap-backed counterpart. A template literal — `hello ${name}` —
produces one; see Template literals.
Atoms
An atom is an interned label. Atom literals carry a : prefix, and two atoms with the same name
are the same value.
function status(ok: boolean): atom {
if (ok) {
return :ok;
}
return :error;
}
Null
null is a literal of type NullPtr. It coerces to any pointer type when the surrounding context
supplies one.
function probe(): i32 {
let p: *i32 = null;
let q: *mut u8 = null;
if (p == null) {
return -1;
}
return 0;
}
Using null where a non-pointer type is expected is a compile-time error, and so are dereferencing
it and doing arithmetic on it. It may appear as a match pattern when the scrutinee is
pointer-typed.
Never
never is the bottom type: the type of an expression that never produces a value. @panic(...),
@trap() and @unreachable() all have it. Because never is compatible with every other type, a
function that always panics still satisfies its declared return type.
function fail(): i32 {
@panic("fatal");
}
Beyond the primitives
The table above is the whole set of types the language defines. The types you will actually spend
most of your time with — String, Vector<T>, HashMap<K, V>, Option<T>, Result<T, E> — are
declared in the standard library as ordinary records and enums. Nothing about them is special to
the compiler.
That distinction matters in exactly two places. Library types have to be imported, and they follow
the same ownership rules as anything you write yourself: String implements Drop, so it moves
rather than copies. Everywhere else you can treat them as types like any other.
| Type | Comes from | Use it for |
|---|---|---|
str |
The language | A borrowed, immutable string — every string literal is one |
String |
std::string |
An owned, growable string you can mutate |
T[N] |
The language | A fixed-size sequence, sized at compile time |
Vector<T> |
std::vector |
A growable sequence sized at runtime |
HashMap<K, V> |
std::collections |
Lookup by key |
HashSet<T> |
std::collections |
Membership without a payload |
BitSet |
std::collections |
Membership for dense u32 indexes |
Option<T> |
The standard library | A value that may be absent |
Result<T, E> |
The standard library | An operation that may fail |
Option and Result are enums marked @lang(try), which is what lets the ! operator work on
them — see enums. Choosing between the rest is covered in
data structures.
References and pointers
References and raw pointers are separate types, and mutability is part of the type rather than a property of the binding on the other end.
| Form | Meaning |
|---|---|
&T |
Shared reference |
&mut T |
Exclusive reference |
*T |
Raw pointer |
*mut T |
Mutable raw pointer |
References are checked by the borrow checker. Raw pointers are not — they exist for FFI and for the low-level parts of the standard library.