04 — Variables and Types
Chapter goal: know which types exist, how to write them, and what the checker actually enforces. Reference:
../language-reference/types.mdand../language-reference/type-system.md.
Aura is gradually typed
Annotations are optional. A binding without an annotation takes the type inferred from its initialiser; a binding the checker cannot prove is treated as any and accepted. There is no “must annotate” rule.
let inferred = 42 // int, from the literal
let explicit: int = 42 // written out
Crucially, annotations are erased before the Python emit. They document and (partly) check; they do not change the runtime. aura check runs the type checker; aura run does not — so a program can run with a type error that check would report.
Primitive types
| Aura type | Meaning |
|---|---|
int | integer (arbitrary precision, Python int) |
float | 64-bit floating point |
str | Unicode string |
bool | true / false |
bytes | byte string (annotation accepted, not checked) |
none | absence value and its type |
let age: int = 30
let price: float = 3.14
let title: str = "Aura"
let active: bool = true
let nothing: none = none
null is not Aura — let n = null is a pointed parse error telling you to use none. There is no char, uint, void or unit type.
bool does not widen to int
Unlike Kof/JVM, true + 1 is rejected (E108); there is no bool → int coercion. Convert explicitly with int(b) if you need a number.
Collection types
let items: [int] = [1, 2, 3] // list
let scores: {str: int} = {math: 95} // dict
let structured: {x: float, y: float} = {x: 1.0, y: 2.0} // structural
let boxed: Box[int] = Box(42) // generic (brackets only)
| Spelling | Meaning |
|---|---|
[T] | list of T (also List[T]) |
{K: V} | dict from K to V (also Dict[K, V]) |
{name: T, ...} | structural shape; checked as a dict, field names not retained |
Set[T] | set of T |
Box[T] | generic type application — brackets, never <...> |
Box<T> is rejected: “type arguments use brackets, not ‘<…>’“. Set and tuple type spellings are limited: {T} and (A, B) do not parse as types (see types.md §2.3), though set and tuple values {1, 2} and (1, "x") exist and are inferred.
Optional and union types
let maybe: str? = none // optional — same as `str | none`
let value: int | str = 42 // union
T? lowers to T | none. A union is compatible with each of its members.
Function types
let handler: (int, int) -> int = add
let predicate: (str) -> bool = is_valid
A leading ( with -> is a function type. Parameter and return types are not retained by the checker — any such annotation becomes a plain function type.
Type aliases
type UserId = int
type Point = {x: float, y: float}
Aliases are documentation: the checker does not expand them, so an alias name in an annotation resolves to any unless it is also a declared class.
Casts: as
let q = 3.0 as int // int(3.0)
let s = 42 as str // str(42)
print(7 / 2) // 3.5
print(int(7 / 2)) // 3 — integer quotient
x as T emits T(x). Aura has no floor-division operator; // is a line comment. Use int(a / b).
none and null checks
let missing = none
print(missing is none) // true
print(missing ?? "fallback") // fallback
print((0) ?: "zero-ish") // zero-ish
x is none/x is not none— identity null check.x ?? y— yieldsyonly whenxisnone.x ?: y— yieldsywhenxis falsy (none,false,0,"",[],{}).
Comparing any other literal with is is a parse error: write x == 5, not x is 5.
What the checker guarantees
- Same type, or either side
any— accepted. int → floatwidening is not implicit; annotate or cast.- Unannotated functions have no inferred return type; the annotation is documentation.
The guarantee boundary is spelled out in type-system.md §4.
What you learned
- Primitives:
int,float,str,bool,bytes,none. - Collections:
[T],{K: V},{shape},Set[T],Box[T]. - Optional
T?, unionT | U, function types(T) -> R. none,is none,??,?:, and theascast.- Annotations are optional, checked partially, and erased at runtime.