Skip to content

The book · 01

Values and types

book.md · 96 lines · 2 min read

Goal: declare values, get to know the primitive types, and understand the two decisions that change day-to-day life the most: no null and no implicit zero-value.

Declaration: let / var / const, and the := shortcut

Section titled “Declaration: let / var / const, and the := shortcut”

Immutable is the default; the common case carries no keyword:

x := 5 // immutable, runtime (shortcut, the common case)
let y := 5 // immutable, runtime (explicit form; identical to 'x := 5')
var z := 5 // mutable
const N := 64 // compile-time constant (comptime)
nome: string = "ana" // explicit type (Odin style); ':=' is ': =' with the type omitted

The rule: := (or : T =) declares; a bare = only assigns to a var. The : is what marks a declaration.

z = 10 // ok: z is var
x = 10 // ERROR: x is not var

And a rule that gives teeth to immutable-by-default: a var that is never mutated is a compile error, and the compiler tells you to use :=. A capability that is requested and not exercised does not pass.

  • Arbitrary-width integers: iN/uN for any N from 1 to 65535. u8, i32, u7, i128, u777 are all valid. Aliases: int/uint (default width), isize/usize (pointer width), byte = u8, bit = u1.
  • Floats only in IEEE widths: f16/bf16/f32/f64/f128; alias float.
  • bool (true/false), string (always valid UTF-8).
conta: u64 = 0
ratio: float = 0.5
ativo: bool = true
flags: u3 = 0b101 // 3 real bits; takes up 1 addressable byte

The lexer understands units, with no import needed:

@limit(16mb) // 16 megabytes (base-10). '16mib' = base-2. the atom is the byte.
prazo := 5s // Duration: 5 seconds. also 300ms, 10ns, 6m, 7h, 18d

Sizes become usize in bytes; durations become Duration. For runtime values there are helpers (mem.mib(n), time.millis(n)).

There is no null in Makoto. When something can be missing, the type says so with Optional[T], and the empty case is the builtin value none:

fn buscar(id: int) -> Optional[User] {
if id == 0 { return none }
return achar(id) // a present User coerces into Optional[User]
}
u := buscar(42)
nome := u.is_present() ? u.or_panic().name : "anonymous"

The access API is honest in its naming: is_present()/is_none(), or_panic() (returns the value; panics if empty, and the name shows the consequence), or(default) (value or default). Or you branch with match:

match buscar(42) {
none => io.println("not found")
user => io.println("found {{user.name}}")
}

Every declaration is born with a right-hand side. There is no var x: int that turns into 0 on its own: 0 and "" only show up when you write them. This eliminates the whole class of “I forgot to initialize” bugs: there is nothing to forget.

var total := 0 // 0 because I wrote 0
var nome := "" // "" same thing

Next: 02 · Functions and flow