Rugo's hashes and arrays are more than data containers — they're the building blocks for objects, configurations, and APIs. Understanding their idioms is essential to writing good Rugo.
Hashes as Lightweight Objects
The colon syntax creates clean, readable hashes with string keys. Combined with dot access, hashes become lightweight objects without ceremony.
person = {name: "Alice", age: 30, city: "NYC"}
puts person.name
puts person.city
person.age = 31
puts "#{person.name} is #{person.age}"
Alice
NYC
Alice is 31
Dot access works on any hash — you don't need structs for simple data objects. This is idiomatic Rugo: start with a hash, graduate to a struct only when you need constructors and methods.
Colon vs Arrow: Know the Difference
Colon syntax (key: value) converts keys to strings. Arrow syntax (key => value)
uses the expression as-is — variables, integers, booleans, anything.
status_codes = {200 => "OK", 404 => "Not Found", 500 => "Server Error"}
puts status_codes[200]
puts status_codes[404]
key = "greeting"
config = {key => "hello"}
puts config["greeting"]
OK
Not Found
hello
Rule of thumb: Use colon syntax for most hashes (it's cleaner). Switch to arrow syntax when your keys aren't simple string literals.
Nested Dot Access
Dot access chains through nested hashes — no brackets needed.
app = {
"config" => {
"db" => {host: "localhost", port: 5432},
"cache" => {ttl: 300}
}
}
puts app.config.db.host
puts app.config.cache.ttl
localhost
300
This makes configuration objects feel natural. Build nested hashes for your app's config and access values with clean dot notation.
The Factory Pattern
The most powerful Rugo idiom: functions that return hashes with closures attached. This gives you objects with methods, encapsulation, and state — all without a class system.
def make_counter(start)
c = {value: start}
c["increment"] = fn()
c.value += 1
end
c["decrement"] = fn()
c.value -= 1
end
c["get"] = fn()
return c.value
end
return c
end
counter = make_counter(0)
counter.increment()
counter.increment()
counter.increment()
counter.decrement()
puts "Count: #{counter.get()}"
Count: 2
The closures capture the c hash by reference, so mutations through .increment()
persist. This is how real Rugo libraries like Gummy (an ORM) and Rugh (a GitHub
client) build their APIs — every "object" is a hash with lambda methods.
Array Idioms
Rugo arrays support negative indexing and slicing — use them.
colors = ["red", "green", "blue"]
puts colors[-1]
puts colors[0, 2]
numbers = [10, 20, 30, 40, 50]
first_two = numbers[0, 2]
puts first_two
# Building arrays with append
names = []
names = append(names, "Alice")
names = append(names, "Bob")
puts len(names)
blue
[red green]
[10 20]
2
Note that append returns a new array — you must reassign:
names = append(names, item). This is borrowed from Go's semantics.
Idiomatic Iteration
The for..in loop is Rugo's Swiss army knife for iteration. The two-variable
form destructures into index/value for arrays, or key/value for hashes.
# Iterate arrays with index
fruits = ["apple", "banana", "cherry"]
for i, fruit in fruits
puts "#{i}: #{fruit}"
end
# Iterate hashes with key-value
config = {host: "localhost", port: "8080"}
for k, v in config
puts "#{k} = #{v}"
end
0: apple
1: banana
2: cherry
host = localhost
port = 8080
Prefer for..in over while with manual indexing. It's clearer and less
error-prone.