Variables and Types
Gruel is statically typed—every variable has a type known at compile time.
Integer Types
Gruel has the integer types you'd expect, plus pointer-sized variants:
fn main() -> i32 {
// Signed integers: i8, i16, i32, i64, isize
let x: i32 = 42;
let big: i64 = 1000000000000;
// Unsigned integers: u8, u16, u32, u64, usize
let index: usize = 0; // pointer-sized — used for array/slice indexing
let byte: u8 = 255;
@dbg(x);
x
}
The number after i or u is the bit width. Signed integers (i) can be negative; unsigned integers (u) cannot. isize and usize are pointer-sized — usize is the type used for array and slice indices.
Floating-Point Types
Gruel has three floating-point types: f16, f32, and f64. Float literals contain a decimal point or exponent; without one, a literal is an integer.
fn main() -> i32 {
let pi: f64 = 3.14159;
let small: f32 = 1.5;
@dbg(pi);
0
}
Type Inference
You don't always need to write types explicitly. The compiler can often infer them:
fn main() -> i32 {
let x = 42; // inferred as i32 (the default)
let y = true; // inferred as bool
@dbg(x);
x
}
When there's no context, integer literals default to i32.
Booleans
Boolean values are either true or false:
fn main() -> i32 {
let flag: bool = true;
let done = false;
@dbg(flag); // prints: true
@dbg(done); // prints: false
0
}
Characters
The char type holds a single Unicode scalar value. Char literals use single quotes:
fn main() -> i32 {
let letter: char = 'A';
let snowman: char = '☃';
@dbg(letter.is_ascii()); // prints: true
@dbg(letter.to_u32()); // prints: 65
let bytes: u32 = @cast(snowman.len_utf8());
@dbg(bytes); // prints: 3
0
}
A char is always 4 bytes and can represent any Unicode codepoint. Common escapes work too: '\n', '\t', '\\', '\'', and '\u{1F600}' for an arbitrary codepoint. It's distinct from u32 — converting between them is explicit, via c.to_u32() and char::from_u32(n). Arithmetic operators are not defined on char; convert through u32 if you need to do codepoint math.
Numeric Casts
To convert between integer or float types, use @cast. The target type is inferred from context:
fn main() -> i32 {
let big: i64 = 1000;
let small: i32 = @cast(big); // i64 -> i32
let index: i32 = 5;
let as_u64: u64 = @cast(index); // i32 -> u64
@dbg(small); // prints: 1000
let n: i32 = @cast(as_u64);
@dbg(n); // prints: 5
0
}
If the value doesn't fit in the target type, the program panics at runtime:
fn main() -> i32 {
let x: i32 = 300;
let y: u8 = @cast(x); // panics: 300 doesn't fit in u8 (max 255)
@cast(y)
}
Mutability
Variables are immutable by default. Use let mut to make them mutable:
fn main() -> i32 {
let mut count = 0;
count = count + 1;
count = count + 1;
@dbg(count); // prints: 2
count
}
Trying to assign to an immutable variable is a compile error:
fn main() -> i32 {
let x = 42;
x = 43; // Error: cannot assign to immutable variable
x
}
This helps catch bugs—if a value shouldn't change, the compiler enforces it.