Functions, Methods & Structs

Ube models data and behavior cleanly: structs represent plain value types, and functions attach to them using explicit value or mutable receivers.

1. Function Declarations

Functions are declared with fn at top-level. Return types are indicated with -> Type. If a function returns nothing, the return type is void or may be omitted:

fn add(a: i32, b: i32) -> i32 {
    return a + b
}

fn log_event(name: string) {
    println(f"[EVENT] {name}")
}

fn main() {
    let result = add(20, 22)
    println(f"Computed: {result}")
    log_event("Simulation initialized")
}

2. Struct Definitions & Instantiation

Structs are value types in Ube. They contain named fields and are constructed with TypeName { field: value, ... } syntax:

struct Vector2D {
    x: f32,
    y: f32
}

struct Player {
    name: string,
    position: Vector2D,
    health: i32
}

fn main() {
    let p = Player {
        name: "Aria",
        position: Vector2D { x: 12.0, y: 34.5 },
        health: 100
    }

    println(f"Player {p.name} at coordinates ({p.position.x}, {p.position.y})")
}

3. Method Receivers (Value vs Mutable)

Similar to Go, methods in Ube are declared with a receiver preceding the function name. Ube distinguishes between:

struct Rect {
    width: i32,
    height: i32
}

// 1. Read-only Value Receiver
fn (r Rect) area() -> i32 {
    return r.width * r.height
}

// 2. Mutable Receiver (in-place modification)
fn (mut r Rect) scale(factor: i32) {
    r.width *= factor
    r.height *= factor
}

fn main() {
    let mut box = Rect { width: 10, height: 5 }

    println(f"Initial area: {box.area()}") // 50

    box.scale(2)
    println(f"Scaled dimensions: {box.width}x{box.height}") // 20x10
    println(f"New area: {box.area()}")                     // 200
}
⚠️ Receiver Rule
To call a mutable receiver method like box.scale(2), the variable holding the struct must have been declared with let mut. Calling a mutable receiver on an immutable let binding is caught and rejected at compile time!

4. Visibility & Multi-File Organization

By default, items in Ube are private to their file. Use the pub keyword to export structs, enums, functions, and methods across files:

// physics.ube
pub struct RigidBody {
    pub mass: f32,
    velocity: f32 // Private field
}

pub fn create_body(mass: f32) -> RigidBody {
    return RigidBody { mass: mass, velocity: 0.0 }
}

You can import other files directly with import "relative/path.ube":

import "physics.ube"

fn main() {
    let body = create_body(50.0)
    println(f"Body mass: {body.mass}")
}

5. Why Ube Eliminates Closures & Anonymous Functions

In many languages, closures introduce hidden heap allocations, implicit captures, and tricky lifetime bugs. In game engines and low-latency servers, closures often cause unexpected GC pressure or hidden pointer indirections.

Ube has no anonymous functions or closures. Every function is top-level and explicitly parameterized. This guarantees: