Types
Every value in Rux has a type, fixed at compile time. The type decides what the value can hold, how much storage it takes, and which operations apply to it. There are no implicit conversions that can change a value: the few that happen on their own only widen, and every other conversion is written with as.
Kinds of type
| Kind | Examples | Described in |
|---|---|---|
| Integer | int, uint8, int128 | Integers |
| Floating-point | float64, float32 | Floating-Point |
| Boolean | bool, bool32 | Booleans |
| Character | char, char8, char16 | Characters |
| Text | char8[..], char16[..] | Text |
| Array | int[4], float64[3][3] | Arrays |
| Slice | int[..], var int[..] | Slices |
| Tuple | (int, char8[..]) | Tuples |
| Range | int..int, int..=int, .. | Ranges |
| Reference | &T, &var T | References |
| Pointer | *T, *var T, *opaque | Pointers |
| Optional | T? | Optionals |
| Fallible | T ! E, ! E | Errors |
| Sum | int | bool | Sums |
| Function | func(int) -> int | Function types |
| Struct | struct Point { … } | Structs |
| Enum | enum Color { … } | Enums |
| Variant | variant Shape { … } | Variants |
| Union | union Word { … } | Unions |
| Interface | interface Shape { … } | Interfaces |
| Generic parameter | T in func First<T>(items: T[..]) -> T | Generics |
The integer, floating-point, boolean and character types are the primitive types. Their names are predefined, not keywords, and the Primitive Types appendix lists all of them, with sizes. Text is not a separate type: a string is a slice of characters.
Two more types have no family of their own: opaque, the unknown pointee of an untyped pointer *opaque, and (), the unit type of a function that returns nothing.
Type expressions
Type ::= SumType ( '!' SumType )?
| '!' SumType
SumType ::= RangeType ( '|' RangeType )*
RangeType ::= PostfixType ( ( '..' | '..=' ) PostfixType? )?
| ( '..' | '..=' ) PostfixType?
| 'var' PostfixType
PostfixType ::= PrimaryType ( '?' | '[' ']' | '[' Expression ']' | '[' '..' ']' )*
PrimaryType ::= Path TypeArguments?
| '*' 'var'? PostfixType
| '&' 'var'? PostfixType
| 'func' '(' … ')' ( '->' Type )?
| '(' Type ( ',' Type )* ')'
| '(' ')'
The forms bind in this order, tightest first:
- Postfix suffixes, applied left to right:
?optional,[N]array,[..]slice, and[], the flexible array tail a struct's last field may have (see Memory Layout). - Range bounds
T..T,T..=T, and thevarof a writable slicevar T[..]. - Sum
A | B. - Fallible
T ! E— one!per type; a leading! Eis a fallible with no success value.
The pointer and reference forms take a postfix type, so *int? is a pointer to an optional, *(int?).
| Written | Means |
|---|---|
int32?[..] | A slice of optional int32 |
int32[..]? | An optional slice of int32 |
int[3][2] | An array of 2 elements, each an int[3] |
int?? | An optional of an optional int |
A | B ! E | F | (A | B) ! (E | F) |
T ! (E?) | A fallible whose error is an optional — grouped |
A | (B?) | A sum with an optional member — grouped |
An optional member of a sum and an optional error of a fallible must be written in parentheses, since A | B? could mean either A | (B?) or (A | B)?. Ungrouped, they are error: an optional sum member must be grouped and error: an optional error type must be grouped.
variant ParseError {
Empty,
Bad(char8)
}
func Double(x: int) -> int {
return x * 2;
}
func Check(text: char8[..]) -> ! ParseError {
if text.length == 0 {
fail ParseError::Empty;
}
}
func Main() -> int {
let grid: int[3][2] = [[1, 2, 3], [4, 5, 6]];
let pair: (int, char8[..]) = (1, "one");
let span: int..int = 0..10;
let maybe: int? = none;
let either: int | bool = 7;
let op: func(int) -> int = Double;
var storage: int[4] = [1, 2, 3, 4];
let view: var int[..] = storage[..];
let address: *int = @grid[0][0];
let pointerToOptional: *int? = null;
view[0] = 10;
return grid[1][2] + op(pair.0); // 6 + 2
}
Type identity
Two type expressions denote the same type when they are spelled the same after aliases are resolved. A type alias is another name for its target, never a new type. Each struct, enum, variant, union and interface declaration, on the other hand, introduces a type distinct from every other, even one with identical fields. A generic type is a different type for each set of type arguments: Box<int> and Box<bool> are unrelated.
int and int64 are distinct types — a function can be overloaded on them — that convert to each other implicitly, because int is 64 bits wide on every supported target. The same holds for uint and uint64.
Implicit conversions
A value converts to another type without as only when no value can change:
| From | To | Example |
|---|---|---|
| An integer | A wider integer of the same signedness | int8 → int32, uint32 → uint |
int / uint | int64 / uint64, and back | |
float32 | float64 | |
char32 | char64 | |
| Any boolean width | Any other boolean width | bool → bool32 |
T[N] | T[..] | An array viewed as a slice |
var T[..], *var T, &var T | T[..], *T, &T | Dropping write access |
| Any pointer | *opaque | |
T | T?, or a sum that has T as a member | See Optionals and Sums |
Everything else needs as: narrowing, changing signedness, crossing between integers, floats, characters and booleans, and float64 to float32. Each family page lists its conversions. Unsuffixed integer literals are the one flexible case: they take the type their context needs, as Literals describes.
Two integer operands of one signedness meet at the wider type, as an assignment would widen the narrower one. Operands of different signedness have no common type:
let u: uint64 = 3;
let s: int64 = -5;
let mixed = u + s;
error: operator '+' cannot combine left operand 'uint64' with right operand 'int64'
A result that is wider than its destination is not narrowed back: with small: int32 and big: int64, let t: int32 = small + big; is error: cannot assign 'int64' to 'int32'.
Size and alignment
sizeof(T) and alignof(T) give a type's storage size and alignment in bytes, as uint values computed at compile time. Every primitive's size is fixed by its name — int and uint are 8 bytes on every supported target — and Memory Layout describes how compound types are laid out.
See also
- Primitive Types — every primitive in one table
- Casts —
asandis - Types — the Learn part on building your own types
- Convert — the lesson on conversions