Menu
Try in Playground

Zero Primitive Types: Integers, Floats, Bool, String, Void

The built-in types Zero gives you out of the box: signed and unsigned integers of every width, floats, booleans, characters, strings, and the empty type Void.

The Built-In Types

Zero gives you a small, regular set of primitive types. Nothing exotic, nothing surprising — just the ones every systems language needs, named consistently.

FamilyTypesNotes
Signed integersi8, i16, i32, i64Two's complement.
Unsigned integersu8, u16, u32, u640 and positive only.
Pointer-sizedusize, isizeWidth matches the platform pointer.
Floatsf32, f64IEEE-754.
Booleanbooltrue or false.
CharactercharA single Unicode scalar.
StringStringUTF-8 string.
EmptyVoid"No useful value."

That's the full list of primitives you'll touch day to day. Compound types — shapes, enums, choices — are built from these.

Integers

The integer types follow a uniform naming pattern: i for signed, u for unsigned, followed by the bit width. So i32 is a 32-bit signed integer; u8 is an unsigned byte; i64 is a 64-bit signed integer.

let small_signed: i8   = -120
let byte:         u8   = 250
let id:           i32  = 1
let big:          i64  = 9_000_000_000
let index:        usize = 0

The default for an unsuffixed literal is i32 unless the surrounding context forces something else:

let answer = 42       // i32

When you need a specific width, attach a suffix to the literal or annotate the binding:

let byte = 250_u8     // typed literal
let byte: u8 = 250    // typed binding

Both forms produce the same value. The literal-suffix form is handy when you're passing a literal directly to a function or building a struct:

let pair: BytePair = Pair { left: 1_u8, right: 2_u8 }

When to pick which width

A short rule of thumb:

  • i32 for most signed math. Wide enough for almost anything you'd count, fast on every platform.
  • u8 for byte-level work. Bytes from a file, bytes in a buffer, bytes over the network.
  • u32 / u64 for non-negative counts when range matters. File offsets above 2 GB, large counts.
  • usize for sizes and indices. Pointer-sized — matches what the platform uses for memory addressing.
  • i64 for time-since-epoch and similar. Big enough for nanoseconds for hundreds of years.

Picking the smallest type that fits is good practice; picking too small a type and overflowing is a much bigger problem than picking one bit too wide.

Booleans

let ok = true
let done: bool = false

bool has exactly two values: true and false. They're literals, not constants you import from somewhere. The condition in an if or while is a bool — there's no implicit truthiness for integers or strings.

if ok {
    check world.out.write("yes\n")
} else {
    check world.out.write("no\n")
}

If/else covers conditionals in detail.

Floats

f32 and f64 are 32-bit and 64-bit IEEE-754 floating-point numbers respectively. Use them when you need fractional values — measurements, ratios, geometry. For exact arithmetic on currency, prefer integers in the smallest unit (cents, satoshis) over floats.

let ratio: f32 = 0.5
let pi:    f64 = 3.141592653589793

f64 is the default for unsuffixed float literals.

Characters and Strings

A char holds a single Unicode scalar value:

let initial: char = 'Z'

A String is a sequence of characters, typically encoded as UTF-8 by the standard library. String literals use double quotes:

let message: String = "hello from zero\n"

Escape sequences you'd expect work — \n for newline, \t for tab, \\ for a literal backslash, \" for a literal double quote.

let multi_line = "line one\nline two\n"

The standard library exposes byte-level views over a string for low-level work. The std.mem.span("zero") form returns a Span<u8> over the bytes — useful when you're parsing, hashing, or comparing bytewise.

Void

Void is Zero's "no useful return value" type. Functions that exist for their side effects use it:

pub fun main(world: World) -> Void raises {
    check world.out.write("hello\n")
}

main writes something and returns. There's no value to hand back, so the type is Void. You'll see Void on most functions that touch World — they're chosen for their effect, not their result.

Underscores in Number Literals

Long number literals can use underscores as visual separators. The compiler ignores them, so they're a pure readability feature:

let big   = 9_000_000_000_i64
let bytes = 1_048_576_u32     // 1 MiB

Drop them anywhere the digits are getting hard to count.

Typed Literal Suffixes Cheat Sheet

SuffixTypeExample
_i8 / _i16 / _i32 / _i64Signed integer127_i8
_u8 / _u16 / _u32 / _u64Unsigned integer255_u8
_usize / _isizePointer-sized0_usize
_f32 / _f64Float0.5_f32

Reach for these when you're constructing a value where the surrounding context doesn't pin the type down.

Next: Functions

Primitives are useless without something to do with them. The next doc covers functions in Zero — how you declare them, return values from them, and bring them together to build real programs.

Frequently Asked Questions

What primitive types does Zero have?

Zero ships with sized signed integers i8, i16, i32, i64; unsigned integers u8, u16, u32, u64; pointer-sized integers usize and isize; floats f32 and f64; bool; char; String; and Void for functions that return nothing useful.

What is the default integer type in Zero?

An unsuffixed integer literal like 42 defaults to i32 unless context forces a different type. To use a specific width, write the literal with a suffix like 42_u8 or 42_i64, or annotate the binding's type explicitly with let count: u8 = 42.

Does Zero have a separate string type?

Yes. String literals like "hello" have a built-in string type that the standard library treats as a sequence of bytes (often UTF-8). For lower-level byte work, the standard library exposes spans and byte-level utilities; for character-level operations there's char for individual scalar values.

What does Void mean in Zero?

Void is the return type of a function that doesn't produce a useful value — it exists only for its side effects. The conventional pub fun main(world: World) -> Void raises signature uses Void because main exists to do I/O and exit, not to produce a value.

What is the difference between i32 and u32 in Zero?

i32 is a signed 32-bit integer with the range −2,147,483,648 to 2,147,483,647. u32 is unsigned and has the range 0 to 4,294,967,295. Use signed types when negative values are meaningful, unsigned when negative values would be a bug — for counts, indices, sizes, and so on.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED