Skip to content

Integer Width Types

Shape’s primary integer type is int, a signed 64-bit integer. For C interop and binary protocol work, the parser also recognizes width-specific integer type names (i8, u8, i16, u16, i32, u32, u64).

int is the default integer type. Unsuffixed integer literals are int. It is a signed 64-bit integer (range: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807) stored as a typed slot — the compiler proves the type at compile time and emits typed opcodes that operate on raw native values without runtime tag checks.

let x = 42 // int
let y = -1000 // int
let big = 9000000000000000000 // int (within i64 range)
print(x)
print(y)
print(big)

For arbitrary-precision integers, use bigint. For floating-point values, use number (f64).

The following width-specific types are recognized in syntax:

TypeBitsSignedRange
i88Yes-128 to 127
u88No0 to 255
i1616Yes-32,768 to 32,767
u1616No0 to 65,535
i3232Yes-2,147,483,648 to 2,147,483,647
u3232No0 to 4,294,967,295
u6464No0 to 18,446,744,073,709,551,615

u64 is recognized for cases requiring unsigned 64-bit values (e.g., content hashes, file sizes). Note that runtime integer storage is i64-backed: u64 values within the i64 range (0 to 9,223,372,036,854,775,807) round-trip correctly, but values above i64::MAX are not yet safely representable at runtime. Use bigint if you need the full unsigned 64-bit range. Other width types are primarily useful as documentation in type annotations and for future C interop.

Append a type suffix to any integer literal to give it a specific width:

let a = 42i8 // i8
let b = 255u8 // u8
let c = 1000i16 // i16
let d = 50000u16 // u16
let e = 100i32 // i32
let f = 3000000u32 // u32
let size = 1048576u64 // u64
print(f)
print(size)

Unsuffixed integer literals are int (i64). The suffix set is i8, u8, i16, u16, i32, u32, u64 — there is no i64 suffix because unsuffixed literals are already int (i64). Hex, binary, and octal prefixes combine with suffixes: 0xFFu8, 0b1010i16, 0o77u32.

Use as to convert between integer types:

let wide: int = 300
let narrow = wide as i8 // truncates to low 8 bits → 44
let signed: int = -1
let unsigned = signed as u8 // reinterpret bits → 255
print(narrow)
print(unsigned)

as performs a bit-level conversion: narrowing keeps the low bits (300 as i8 is 44), and signed/unsigned changes reinterpret the same bits (-1 as u8 is 255). It does not range-check or saturate.

Width types can be used as field types in struct definitions, primarily as documentation of the intended storage semantics:

type Pixel {
r: u8,
g: u8,
b: u8,
a: u8
}
type Sensor {
id: u16,
reading: i32,
flags: u8
}
let px = Pixel { r: 255u8, g: 128u8, b: 0u8, a: 255u8 }
print(px.r)
  • Use int for general-purpose integer work — it has full runtime optimization (typed slots, typed opcodes, JIT support).
  • Use number (f64) when you need floating-point values.
  • Use bigint for arbitrary-precision integer arithmetic.
  • Use width types in type annotations when documenting binary protocol layouts or C interop signatures.
  • Use u64 for unsigned values that stay within the i64 range; reach for bigint when you need the full unsigned 64-bit range with guaranteed precision.