Thanks to visit codestin.com
Credit goes to rugo-lang.dev

Blog
Chapter 7

Structuring Your Code

As scripts grow, Rugo gives you three tools for organization: structs for data types, modules for code reuse, and the Go bridge for accessing Go's standard library.

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.