Variables, Types & Control Flow
Ube adopts Go's human-first clarity while enforcing strict immutability defaults, zero hidden conversions, and deterministic value scoping.
1. Variables & Immutability
In Ube, variables are bound using the let keyword. By default, every binding is strictly immutable. If you intend to reassign a variable or modify its value in place, you must explicitly mark it with mut:
fn main() {
let name: string = "Ube" // Immutable binding
let speed = 60 // Type inferred as i32
// speed = 80 // COMPILE ERROR: cannot reassign immutable binding
let mut score = 100 // Mutable binding
score += 25
score *= 2
println(f"{name} final score: {score}") // prints: Ube final score: 250
}
let. Add mut only when in-place modification or loop accumulation is truly necessary. This helps LLMs and human reviewers trace data mutations without ambiguity.
2. Primitive Types
Ube does not have implicit type promotions (e.g., converting an i8 to an i32 automatically). All types are explicit and sized:
| Type | Description | Literals / Example |
|---|---|---|
i8, i16, i32, i64 |
Signed integers (8, 16, 32, 64 bits) | let x: i32 = -42, 0x2A, 0b101010 |
u8, u16, u32, u64 |
Unsigned integers (8, 16, 32, 64 bits) | let byte: u8 = 255 |
f32, f64 |
IEEE-754 floating point numbers | let pi: f32 = 3.14159 |
bool |
Boolean truth values | true or false |
string |
UTF-8 byte sequence (arena allocated) | "sweet ube" |
void |
Return type for functions that produce no value | fn cleanup() -> void { } |
3. Strings & Format Interpolation
Plain string literals in Ube (written with double quotes "...") are strictly literal. If you want string interpolation, prefix the literal with f and embed expressions in {expr}:
fn main() {
let hero = "Grounded Player"
let hp = 100
// Plain string (no interpolation happens)
println("Status: {hero} has {hp} HP")
// Output: Status: {hero} has {hp} HP
// Format string (interpolation enabled with 'f')
println(f"Status: {hero} has {hp} HP, boosted: {hp + 50}")
// Output: Status: Grounded Player has 100 HP, boosted: 150
// Byte indexing & slicing
let title = "UbeLanguage"
let first_byte: u8 = title[0] // 85 ('U')
let prefix = title[0:3] // "Ube" (end index exclusive)
println(f"Prefix: {prefix}, length: {title.len}")
}
4. Arrays vs Slices
Ube clearly separates fixed-size stack arrays from dynamic arena-backed slices:
- Fixed Arrays (
[N]T): Fixed capacity, value type allocated on the stack. Safe across task boundaries. - Dynamic Slices (
[]T): Growable collections allocated in the regional bump arena. Supports.push(),.pop(),.clear(), and.contains().
fn main() {
// Fixed array of 3 elements on the stack
let coords: [3]f32 = [3]f32{0.0, 10.5, 20.0}
println(coords[1]) // 10.5
// Dynamic slice allocated in the current arena
let mut items = []i32{}
items.push(10)
items.push(20)
items.push(30)
println(f"Items length: {items.len}") // 3
let last = items.pop()
println(f"Popped: {last}") // 30
println(f"Remaining: {items.len}") // 2
}
5. Loops & Conditional Branches
Ube supports if / else, range-based for loops, collection iteration, and while loops:
fn main() {
let health = 45
if health > 50 {
println("Status: Healthy")
} else if health > 20 {
println("Status: Wounded")
} else {
println("Status: Critical")
}
// Range loop (end index exclusive)
println("Counting from 0 to 4:")
for i in 0..5 {
println(i) // 0, 1, 2, 3, 4
}
// Iterating dynamic slice
let numbers = []i32{100, 200, 300}
for n in numbers {
println(f"Number: {n}")
}
// While loop with break & continue
let mut step = 0
while step < 10 {
step += 1
if step == 2 { continue }
if step == 5 { break }
println(f"Step {step}")
}
}
6. Scope Deferral with defer
Similar to Go, Ube provides the defer keyword to schedule a cleanup call to execute whenever the enclosing function or scope exits:
fn teardown() {
println("Cleaning up resources...")
}
fn main() {
defer teardown()
println("Beginning game session...")
println("Executing gameplay logic...")
}
// Console output:
// Beginning game session...
// Executing gameplay logic...
// Cleaning up resources...