Structs: Data with Identity
Structs give hashes a name and a constructor. They're lightweight — no class hierarchies, no inheritance. Just named fields and optional methods.
struct Dog
name
breed
end
rex = Dog("Rex", "Labrador")
puts rex.name
puts rex.breed
rex.name = "Rexy"
puts "Renamed to: #{rex.name}"
puts type_of(rex)
Rex
Labrador
Renamed to: Rexy
Dog
Under the hood, structs are hashes with a __type__ field. The constructor
creates the hash and sets each field in order. type_of() returns the struct
name.
Struct Methods with Modules
Struct methods shine when combined with the module system. Define the struct
and its methods in one file, then require it from another.
dog.rugo — define the struct and its method:
struct Dog
name
breed
end
def Dog.speak()
return self.name + " says woof!"
end
main.rugo — require and use it:
require "dog"
rex = dog.new("Rex", "Labrador")
puts rex.name
puts dog.speak(rex)
Expected output:
Rex
Rex says woof!
Methods use self to access the instance. Callers pass the instance as the first
argument through the namespace: dog.speak(rex). The new() function is
automatically created as an alias for the constructor.
The Class-Like Pattern: Struct + Module
When you need class-like organization, use a module per type: a struct holds
the state, and the module groups its constructor and behavior. Methods get
self automatically; callers pass the instance explicitly.
counter.rugo:
struct Counter
value
end
def Counter.increment(amount=1)
self.value = self.value + amount
return self.value
end
def Counter.reset()
self.value = 0
end
main.rugo:
require "counter"
hits = counter.new(0)
counter.increment(hits)
counter.increment(hits, 5)
puts hits.value
counter.reset(hits)
puts hits.value
other = counter.new(10)
puts counter.increment(other)
puts hits.value
Expected output:
6
0
11
0
Each constructor call creates independent state. The fields are accessible to callers, so this pattern provides organization rather than private fields. For reuse, prefer composition: store another object in a field and delegate operations to its module.
The runnable files are in snippets/ch08_counters/:
rugo run snippets/ch08_counters/main.rugo
Private State with Closure Factories
When state should only be accessed through operations, return a hash of closures. The lambdas share a captured variable that is never exposed as a field on the returned hash.
def make_counter(initial=0)
value = initial
return {
increment: fn(amount=1)
value = value + amount
return value
end,
get: fn()
return value
end
}
end
hits = make_counter()
hits.increment()
hits.increment(5)
puts hits.get()
other = make_counter(10)
puts other.increment()
puts hits.get()
6
11
6
Each factory call creates its own captured state. This gives you direct
hits.increment() calls, but the result is a hash of functions rather than a
named struct, and each instance creates its own closures.
Run this example with rugo run snippets/ch08_closure_counter.rugo.
Idiom: Use structs and modules for named data with shared behavior. Reach for closure factories when you need encapsulated state and object-local operations. Both patterns use existing Rugo features.
Type Introspection
type_of() works on every value. Use it for runtime type checking and
debugging.
puts type_of("hello")
puts type_of(42)
puts type_of(3.14)
puts type_of(true)
puts type_of(nil)
puts type_of([1, 2])
puts type_of({a: 1})
double = fn(x) x * 2 end
puts type_of(double)
String
Integer
Float
Bool
Nil
Array
Hash
Lambda
For structs, type_of() returns the struct name (Dog, User, etc.) instead of
Hash. This lets you build type-aware functions when needed.
The Go Bridge
import gives you direct access to Go's standard library. Function names are
automatically converted from Go's PascalCase to Rugo's snake_case.
import "strings"
import "math"
import "strconv"
puts strings.to_upper("hello rugo")
puts strings.contains("hello world", "world")
puts math.sqrt(144.0)
n = try strconv.atoi("42") or 0
puts n
HELLO RUGO
true
12
42
The bridge covers strings, strconv, math, path/filepath, sort, os,
time, and math/rand/v2. Go functions that return (T, error) auto-panic on
error — pair with try/or for safe handling.
Three Import Mechanisms
| Keyword | Purpose | Example |
|---|---|---|
use |
Rugo stdlib modules | use "http" |
import |
Go stdlib bridge | import "strings" |
require |
User .rugo files |
require "helpers" |
Use as to alias any import when namespaces collide:
use "str"
import "strings" as go_strings
puts str.upper("hello")
puts go_strings.to_lower("WORLD")
HELLO
world
Idiom: Prefer use modules (like str) for common operations — they're
designed for Rugo's conventions. Reach for import when you need something
the Rugo stdlib doesn't cover.