Skip to content

Releases: rubiojr/rugo

v0.28.0

05 Mar 18:12
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

File embedding (embed)

Bake file contents into your compiled binary at build time — no external files needed at runtime:

embed "config.yaml" as config
embed "assets/template.html" as template

puts config
puts len(template)

Paths are relative to the source file and restricted to the same directory or subdirectories, matching Go's embed rules. This applies equally to main scripts and required libraries, so a module can bundle its own data files.

Embedded variables are regular strings — use them in expressions, interpolation, function arguments, or across function boundaries. Clear compile-time errors for invalid paths, missing files, duplicates, and misplaced embed statements.

Binary build cache

Compiled Rugo programs are now cached. On repeat runs of the same source, the cached binary is reused instead of invoking go build (~300ms → ~5ms per cache hit). Cached binaries are gzip-compressed and evicted LRU-style with a 10GB cap.

This benefits any workflow that repeatedly compiles the same code: RATS tests with shared fixtures, linter tools, eval.file() calls, etc.

RATS suite timing with warm cache: 52s → 28s (46% faster).

Improvements

Go bridge reliability

  • Reader/Writer interop — Go bridge now handles io.Reader/io.Writer interface parameters, enabling APIs like xml.new_decoder and xml.escape_text.
  • Struct pointer-slice fields — bridged Go structs now expose pointer-slice fields as wrapped values.
  • WalkDir-style callbacks — named interface types inside callback signatures are now supported, making APIs like filepath.walk_dir available.
  • Variadic function-option params — remote module docs now include functions with variadic named option params (e.g., go-gpx.read(...ReadOption)).
  • Plain text rugo doc — doc output is now plain text automatically when stdout is not a TTY, so grep/pipe workflows work out of the box.

Bug Fixes

  • next/break in try handlers — using next or break inside a try...or err...end block within a loop now works correctly. Previously it failed with "next is not in a loop".
  • Shell interpolation with shadowed params — string interpolation in shell commands (e.g., echo "#{dest}") no longer breaks when a function elsewhere declares a parameter with the same name.
  • Bad for-loop errors — malformed for loops now produce clearer error messages.
  • Go module import validation — invalid imports from Go modules are now caught at compile time.

v0.27.0

25 Feb 15:48
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

Case/of multi-branch matching

Rugo now has a case/of/elsif/else/end construct for matching a value against multiple branches — the idiomatic alternative to long if/elsif chains:

status = "ok"

case status
of "ok"
  puts "all good"
of "error", "fail"
  puts "something went wrong"
else
  puts "unknown"
end

Each of branch can match multiple comma-separated values. For concise one-liners, the arrow form (->) puts the branch body on the same line:

code = 404

case code
of 200, 201 -> puts("success")
of 301, 302 -> puts("redirect")
of 400, 404 -> puts("client error")
of 500, 502 -> puts("server error")
else -> puts("other")
end

elsif branches add boolean conditions that don't compare against the subject — of branches are checked first by equality, then elsif conditions, then else:

score = 75

case score
of 100 -> puts("perfect")
of 0 -> puts("zero")
elsif score >= 90
  puts "A"
elsif score >= 80
  puts "B"
else
  puts "C"
end

case also works as an expression — assign the result directly to a variable, pass it to a function, or use it as a return value. Variables inside expression branches are scoped and don't leak into the surrounding context:

label = case status
of "ok" -> "success"
of "error" -> "failure"
else -> "unknown"
end

puts label  # "success"

Postfix if modifier

Simple conditional assignments can now be written on a single line using a trailing if:

x = 10 if true
puts x  # 10

This follows the Ruby convention of placing the condition after the expression for concise one-liners.

@latest for remote imports

Remote require statements now accept @latest to always fetch the newest version of a dependency:

require "github.com/user/repo@latest"

Sandbox $VAR expansion

Sandbox path configurations now expand $VAR references at runtime, allowing environment-variable-driven sandbox policies.

Improvements

Ruby-compatible || and && semantics

The || and && operators now return the actual value that determined the result instead of a boolean. This matches Ruby semantics and enables idiomatic patterns like default values:

name = nil
greeting = name || "stranger"
puts greeting  # "stranger"

x = "hello"
y = x && x.upcase
puts y  # "HELLO"

Character-based len() and string slicing

len() now returns the character count instead of the byte count for strings. String slicing also operates on characters instead of bytes. This means Unicode strings behave correctly:

s = "héllo"
puts len(s)    # 5 (was 6 with byte counting)
puts s[1..3]   # "éll" (correct character slice)

Improved value display

  • nil is now displayed as "nil" instead of Go's "<nil>"
  • Float values always show a decimal point (e.g., 1.0 instead of 1)
  • Arrays and hashes print in Rugo format instead of Go format
  • Runtime error messages use Rugo type names instead of Go type names

Paren-free namespace calls

Paren-free function calls like str.trim value now work consistently across all namespaces, not just built-in modules.

Better error messages

  • Namespace conflicts — importing a Go package that collides with an already-loaded use module now produces a clear error at compile time instead of silent shadowing.
  • Builtin redefinition — defining a function with the same name as a builtin (puts, len, etc.) now raises an error instead of silently breaking.
  • Reserved keywords — using a reserved keyword as an identifier produces a specific error message instead of a generic parse failure.
  • Float as array index — using a float to index an array now raises a clear error instead of silently truncating to an integer.
  • Non-iterable iteration — iterating over a non-iterable type (e.g., for x in 42) now produces a user-friendly error message.
  • Destructuring underflow — destructuring an array with more variables than elements now raises a clear error instead of a cryptic Go panic.

Bug Fixes

  • Postfix-if nilx = 10 if false now correctly leaves x as nil instead of assigning a zero value.
  • Negative literal parsing — a negative number on its own line is no longer misinterpreted as a subtraction from the previous expression.
  • Float literal folding — arithmetic on float literals (e.g., 1.5 + 2.5) now produces the same result as the equivalent computation using variables.
  • .each callback mutation — modifying outer variables inside an .each callback no longer fails to compile.
  • try/or variable fallbacktry/or now accepts a variable as the inline fallback value, not just literals.
  • Mixed return types — functions that return different types from different branches (e.g., a string in one path and an integer in another) no longer fail to compile.
  • -- separator stripping — the -- separator is now correctly stripped when passing arguments to scripts via rugo run.
  • Shell fallback for assignments — assignments like bar = ls -la are no longer incorrectly handled when the right-hand side looks like a shell command.
  • Path-based commands — commands specified with an explicit path (e.g., ./myscript) are now correctly recognized as shell commands.
  • eval.run with importseval.run() no longer fails when the evaluated code uses import "os" or other Go bridge imports.

v0.26.0

19 Feb 16:03
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

Go bridge callbacks for widget events

The Go bridge now handles struct-method callbacks with super patterns — function parameters whose first argument is itself a function pointer (e.g., OnPaintEvent(func(super func(*QPaintEvent), event *QPaintEvent))). This unlocks event overrides on external types like Qt widgets without writing any Go wrapper code:

require "github.com/mappu/miqt/qt6@v0.13.0"

win = qt6.new_qwidget2()

win.on_paint_event(fn(super_paint, event)
  super_paint(event)   # call the default handler
  puts "paint event!"
end)

win.on_mouse_press_event(fn(super_mouse, event)
  super_mouse(event)
  puts "mouse click!"
end)

The bridge auto-discovers pointer-to-function parameters, named function type casts, and nested callback signatures. Previously, these patterns were silently skipped as unbridgeable.

eval module now supports use and import

Code executed via eval.run() and eval.file() can now use stdlib modules (use "str", use "json", etc.) and Go bridge imports (import "strings"). Previously, eval'd code compiled into a standalone binary with an empty module registry, so any use statement failed at runtime.

All 20 stdlib modules (excluding ast, eval, and sqlite) are now registered in the eval binary via blank imports.

eval error handling improvements

eval.run() and eval.file() no longer panic on compile errors. They return the standard result hash with "status" => 1 and the error message in "output", matching what rugo run produces on failure. This makes eval safe to use with try/or:

use "eval"

result = eval.run("invalid code here ===")
puts result["status"]   # 1
puts result["output"]   # error: ...

rugo eval error path scrubbing

The rugo eval CLI command now scrubs temporary file paths from error messages in all cases. Previously, some error paths leaked the internal temp directory (e.g., /tmp/rugo-eval-abc123/eval.rugo:1) instead of showing the clean eval:1 prefix.

Bug Fixes

  • Parallel remote dependency pulling — when multiple require statements fetched the same remote module concurrently, the cache directory could be corrupted by overlapping writes. Git clones now use atomic installation: the repo is cloned into a unique temp directory and renamed to the final cache path. Lock file writes are also atomic (write to temp, then rename). If another process wins the race, the duplicate clone is silently cleaned up.

  • Undefined variable errors in required files — the check_idents pass now tracks source file changes across require boundaries. Previously, an undefined variable in a required file reported the wrong file name in the error message. The walker now saves and restores sourceFile when entering functions, test/bench blocks, and individual statements from required files.

  • Go bridge: functions returning structs — Go bridge functions that return struct values (not pointers) from imported packages are now classified and bridged correctly. The classifier now handles additional patterns including interface-typed callback parameters with assertion casts, named function types, and nested function signatures in callback parameters.

  • Go imports reliability — improved consistency of import-ed Go packages, particularly for time-related calls and packages with complex type hierarchies.

Refactoring

RATS tests migrated from fixture shellouts to eval

186 fixture files deleted. ~30 RATS test files rewritten to use eval.run() inline instead of shelling out to rugo run on separate fixture scripts. Tests now compile and run snippets in-process, which is faster and eliminates the need to maintain separate .rugo fixture files for each test case. The test assertions remain identical — only the execution mechanism changed.

Go bridge test coverage

Expanded test coverage across the bridge subsystem:

  • New classifier tests for pointer-to-function params, named function type casts, and nested callback signatures.
  • New struct bridge tests for functions returning struct values from external packages.
  • 530+ new lines of Go bridge time package tests covering duration parsing, formatting, timestamps, and sleep.

v0.25.0

18 Feb 13:42
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

Undefined variable and function detection

Rugo now catches undefined variables and functions before compilation, reporting them instantly with file and line info instead of waiting for the Go compiler to fail with cryptic errors:

script.rugo:3: undefined variable 'xyz'
script.rugo:5: undefined function helper.nonexistent

This also validates namespaced calls (ns.func()) against the actual functions exported by required modules, stdlib modules, and Go bridge packages. Typos in module function names are caught at compile time:

use "str"
str.uper("hello")    # error: unknown function str.uper — did you mean str.upper?

173 new RATS tests cover top-level variables, function bodies, forward references, if/for/try scoping, lambda parameters, required module namespaces, and stdlib module validation.

rugo bench --count flag

The bench command now accepts a --count (-n) flag to run each benchmark file multiple times:

rugo bench --count 5 benchmarks/

Each file runs N times with the best result kept, useful for reducing noise in benchmark comparisons.

Bug Fixes

  • Implicit return from if/elseif/else blocks as the last expression in a function or lambda now correctly return the branch value instead of nil:

    def classify(x)
      if x > 10
        "big"
      else
        "small"
      end
    end
    classify(20)  # => "big" (was nil)
  • return inside try/or handlersreturn statements inside try/or error handlers no longer cause a compile error. They now correctly set the handler result value:

    matches = try find_stuff(input) or err
      return nil  # sets matches = nil (previously failed to compile)
    end
  • CLI dispatch with required modules — using require with a module that defines a function matching a CLI command name caused either a duplicate key compile error or silent wrong dispatch. The CLI dispatch map now only includes functions defined in the main file.

Refactoring

Structured Go output AST

The entire code generation pipeline has been rewritten to produce Go source through a structured AST instead of raw string assembly. This was a multi-step migration across 20+ commits:

  1. Go output AST types and printer — a complete set of Go AST node types (GoFile, GoFuncDecl, GoIfStmt, GoForStmt, etc.) and a printer that serializes them to formatted Go source with correct indentation and braces. 17 printer tests cover all node types.

  2. Statement codegen — all AST statement types (assignments, returns, breaks, if/else, while, for, range optimization) now build GoStmt nodes instead of writing strings directly.

  3. Function codegenbuildFunc returns GoFuncDecl nodes handling param unpacking, arity checks, recursion guards, and default return values.

  4. Expression codegen — the entire expression pipeline flows through buildExprGoExpr nodes → printer. Leaf expressions, operators, collections, string interpolation, and complex expressions (try, spawn, parallel, lambdas) all produce structured nodes.

  5. Old infrastructure deletedgoWriter, goir.go, captureOutput, and 20+ dead write* methods removed. All Go source output flows through PrintGoFile.

AST transform pipeline

A composable Transform interface and Chain compositor replace inline AST rewriting in codegen:

  • ConcurrencyLowering — rewrites spawn/parallel/try constructs and their nested return statements into lowered node types before codegen.
  • ImplicitReturnLowering — converts last-expression-as-return-value into explicit AST nodes for functions, lambdas, and try handlers.

Codegen no longer inspects AST structure to decide how to emit returns — it switches on node type and emits mechanical Go code.

Package reorganization

  • preprocess/ — preprocessing pipeline extracted from ast/ into its own package, since preprocessing runs before AST construction.
  • preprocess/string_tracker.go — reusable CodeScanner type extracted from 17 repeated string/escape/backtick tracking implementations.
  • util/ — shared utilities (levenshtein.go) extracted into a dedicated package.
  • compiler/codegen.go split — the monolithic codegen file split into six focused files: codegen.go (core), codegen_expr.go, codegen_stmt.go, codegen_func.go, codegen_runtime.go, codegen_scope.go.

Fuzzer improvements

  • 22 of 23 stdlib modules now registered in the fuzzer (all except eval which causes an import cycle).
  • 55+ new seed corpus entries for recent language features.
  • New checkGoLeaks() helper detects Go internals leaking into error messages (interface{}, goroutine traces, panic:, *main.).

v0.24.0

16 Feb 19:49
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

Default parameter values for functions and lambdas

Functions and lambdas now support default parameter values using = expr syntax. Parameters with defaults can be omitted by callers, and the default expression is evaluated at call time:

def greet(name, greeting = "Hello")
  puts "#{greeting}, #{name}!"
end

greet("Alice")            # Hello, Alice!
greet("Alice", "Hey")     # Hey, Alice!

Multiple defaults and any expression (including nil, booleans, arithmetic) work as defaults:

def connect(host, port = 8080, tls = true)
  puts "#{host}:#{port} tls=#{tls}"
end

connect("example.com")              # port=8080, tls=true
connect("example.com", 443)         # port=443, tls=true
connect("example.com", 443, false)  # port=443, tls=false

A function with all-optional parameters can be called with zero arguments:

def label(text = "default", color = nil)
  puts text
end

label()          # both default
label("hello")   # color defaults to nil

Lambdas use the same syntax:

transform = fn(x, factor = 2) x * factor end
puts transform(5)      # 10
puts transform(5, 3)   # 15

Arity is now a range (min required .. max total). Required parameters must come before parameters with defaults — mixing them the other way is a compile error. 25 new RATS tests cover def defaults, lambda defaults, all-optional params, arity errors, and validation errors.

Bytes type

Go bridge functions returning []byte (like sha256.sum256, json.marshal, os.read_file) now return a proper Bytes type instead of silently converting to String. This eliminates unnecessary copies when []byte values flow between bridge functions.

import "crypto/sha256"
import "encoding/hex"

hash = sha256.sum256("hello")
type_of(hash)                  # "Bytes"
len(hash)                      # 32

# Zero-copy pipeline — bytes flow directly between bridge calls
encoded = hex.encode_to_string(hash)
puts encoded   # 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
  • type_of() returns "Bytes" for []byte values
  • puts and string interpolation work naturally
  • len() returns byte count; slicing and indexing work
  • Indexing returns an integer; slicing returns Bytes
  • Explicit conv.to_s() needed to compare with strings
  • String concatenation (+) with Bytes is a deliberate error — use interpolation or conv.to_s()

Bug Fixes

  • Heredocs in return statements — heredocs used with return (e.g., return <<~'CSS' ... CSS) were rejected with a spurious "semicolons are not supported" error. Only heredocs in assignment context worked. Now heredocs work in any expression position including return.
  • Variadic bridge functions with mixed params — Go bridge functions with a fixed parameter followed by variadic parameters (e.g., url.join_path(base, elem...)) failed when called with 2+ arguments. Arguments are now correctly converted using the element type instead of the slice type.
  • --recap shows all failures at the end — previously, rugo rats --recap only showed failure details inline with each file's output. When running many test files, failures got buried in hundreds of lines. Now, --recap prints a consolidated "Failed tests" section at the very end, grouped by file.
  • macOS RATS test compatibility — several tests relied on Linux-specific behavior (non-portable shell commands, hardcoded binary paths, Linux-only files). Updated to work on both Linux and macOS.

v0.23.0

16 Feb 17:33
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

Auto-introspected Go stdlib bridge

Go stdlib packages are now lazily introspected at compile time instead of relying on hand-written bridge files. When a script does import "strings" or import "math", the compiler inspects the compiled Go package directly, classifies its exported functions, and makes them available automatically — no generated code required.

This replaces ~4000 lines of generated and custom bridge code with a single introspection pass. The 17 previously hand-maintained stdlib packages (strings, math, os, strconv, filepath, base64, hex, time, rand, unicode, path, html, crypto/md5, crypto/sha256, sort, encoding/json, net/url) now bridge through the same mechanism as require'd Go modules.

The rugo dev bridgegen command has been removed — it is no longer needed.

New array dot methods

Arrays gain four new built-in methods:

arr = [1, 2, 3, 2, 1]

arr.contains(3)      # true
arr.index(2)         # 1
arr.reverse()        # [1, 2, 3, 2, 1] (returns new array)
arr.compact()        # [1, 2, 3, 2, 1] (removes consecutive duplicates)
  • .contains(value) — checks if the array contains a value (deep equality)
  • .index(value) — returns the index of the first occurrence, or -1
  • .reverse() — returns a new reversed array
  • .compact() — removes consecutive duplicate elements

New hash dot methods

  • .clone() — returns a shallow copy of the hash

Sorted hash keys

.keys() now returns keys in sorted order instead of Go's random map iteration order:

h = {"c" => 3, "a" => 1, "b" => 2}
puts h.keys()  # ["a", "b", "c"]

Byte slice handling improvements

Functions that accept []byte parameters now accept both strings and arrays of integers. Functions like hex.encode and hex.decode that use output-buffer patterns (func(dst, src []byte)) are auto-wrapped into simple single-argument calls:

import "encoding/hex"
import "crypto/sha256"

puts hex.encode_to_string("hello")           # 68656c6c6f
puts hex.encode_to_string(sha256.sum256("hello"))  # 2cf24dba...

Breaking Changes

  • maps and slices bridge packages removed — these were custom pseudo-packages wrapping Rugo arrays/hashes. Use native dot methods instead: .contains(), .index(), .reverse(), .compact(), .keys(), .values(), .clone().
  • rugo dev bridgegen removed — the bridge generation tool is no longer needed with auto-introspection.
  • Some curated bridge aliases removed — auto-introspection uses 1:1 Go function names. For example, base64.encode/base64.decode become base64.std_encoding_encode_to_string/base64.std_encoding_decode_string. For friendlier names, prefer the use "base64" stdlib module.
  • json.unmarshal via import removed — use use "json" with json.parse() instead.
  • url.parse struct decomposition removedurl.parse no longer returns a hash with scheme/host/path fields.
  • sort.strings / sort.ints removed — custom in-place sorting helpers are gone. Use sort.strings_are_sorted, sort.search_strings, or the native .sort_by() dot method.
  • time.now_unix / time.sleep_ms removed — use use "time" stdlib module for these functions.

Other

  • Removed ~4000 lines of generated and custom bridge code (net -3200 lines).
  • Updated docs/modules.md to remove stale maps/slices bridge references.
  • New RATS tests for .contains(), .index(), .reverse(), .compact(), .clone(), and sorted .keys().

v0.22.0

16 Feb 10:29
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

Comprehensive standard library

Rugo's use modules are now the recommended, idiomatic way to work with strings, files, math, time, encoding, and more. Seven new modules and major expansions to four existing ones bring the stdlib to 125+ curated functions with Ruby-inspired naming.

The Go bridge (import) remains available as a power-user escape hatch for the full Go stdlib, but use modules now cover the common cases — one obvious way to do things.

New modules

  • math — 23 functions for common math operations
  • filepath — 11 functions for path manipulation
  • time — 6 functions for timestamps, sleeping, and formatting
  • base64 — standard and URL-safe encoding
  • hex — hexadecimal encoding
  • crypto — hash digests
  • rand — random number and string generation

Expanded modules

  • str
  • os
  • json
  • conv

do...end trailing block syntax

Pass a block to any function using do...end instead of fn()...end:

vbox do
  label("Hello")
  button("Quit") do
    quit()
  end
end

This is equivalent to vbox(fn() ... end). When the call already has arguments, the block is appended: button("Quit") do...end becomes button("Quit", fn()...end). Nesting, assignment, and paren-free forms are all supported.

GoOpaque: struct-typed callback parameters

Callbacks with struct-typed parameters like OnItemDoubleClicked(func(item *QListWidgetItem)) are now bridged automatically. Previously, signal connectors for many Qt widgets were silently missing from the generated bridge.

Bug Fixes

  • GoOpaque bridge fixes — various fixes to the auto-bridge for external Go module types.
  • Cross-platform RATS tests — fixed test portability issues across platforms.

v0.21.0

15 Feb 17:15
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

GoOpaque bridge — use any Go library from Rugo

Go modules loaded via require can now use types from external dependencies. The bridge auto-discovers types, methods, signal callbacks, and class hierarchies — no Go wrapper code needed.

require "github.com/mappu/miqt/qt6@v0.13.0"
import "os"

qt = {
  app:    fn() qt6.new_qapplication(os.args()) end,
  exec:   fn() qt6.qapplication_exec() end,
  quit:   fn() qt6.qcore_application_quit() end,
  window: fn() qt6.new_qwidget2() end,
  button: fn(text) qt6.new_qpush_button3(text) end,
  label:  fn(text) qt6.new_qlabel3(text) end,
  vbox:   fn(parent) qt6.new_qvbox_layout(parent) end
}

app = qt.app()
win = qt.window()
win.set_window_title("Hello from Rugo!")
win.resize(320, 200)

layout = qt.vbox(win)
lbl = qt.label("Click count: 0")
layout.add_widget(lbl)

count = 0
btn = qt.button("Click Me")
btn.on_clicked(fn()
  count = count + 1
  lbl.set_text("Click count: #{count}")
end)
layout.add_widget(btn)

win.show()
qt.exec()

This example bridges 3600+ functions, 600+ struct types, and hundreds of methods per type from the miqt Qt6 bindings — all auto-discovered.

What the bridge handles automatically

  • Methods on external typeswin.set_window_title("Hello"), btn.on_clicked(fn() ... end)
  • Rugo lambdas as Go callbacks — signal connections, iterators, any func() parameter
  • Embedded struct navigationlbl.qframe.qwidget walks Qt's class hierarchy
  • Auto-upcasting — pass a QPushButton where a QWidget is expected, the bridge walks the embedded field chain automatically
  • Enum and named type castsqt6.GestureType, int8, int16 etc. handled transparently
  • Module-aware type resolution — external dependencies resolved via go list -export

os.args()

New function returning command-line arguments as a string array:

import "os"
puts(os.args())

--show-warnings flag

Bridge warnings about unbridgeable Go functions (e.g., unsafe.Pointer params) are now hidden by default. Use --show-warnings to see them:

rugo build --show-warnings main.rugo -o app
rugo run --show-warnings main.rugo

Bug Fixes

  • Remote Go sub-package requirerequire "github.com/user/repo/subpkg@v1.0.0" now correctly resolves Go sub-packages from remote repositories.
  • with clause version pathrequire "github.com/user/repo@v1.0.0" with subpkg no longer puts the version in the wrong position.
  • Stale module cache — empty cache directories from failed fetches are now re-fetched instead of treated as cached.
  • Void GoFunc adapter — Go callbacks with no return value (func()) no longer generate invalid return statements.
  • Named return types in callbacks — signal callbacks with named return types (e.g., func() qt6.Orientation) now compile correctly.
  • Improved interpolation error messages — clearer errors for malformed string interpolation.
  • Handler variable types — handler vars are now correctly typed as interface{} at the Go level.
  • Variable shadowing in def — reading top-level vars before shadowing them inside a def now works correctly.

Other

  • New var-shadow linter rule.
  • Implicit return documentation.
  • Updated docs/gobridge.md with full GoOpaque reference.
  • New examples/require_go_qt/ — Qt6 GUI in pure Rugo.

v0.20.0

14 Feb 16:31
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

New Features

require Go modules directly

Rugo scripts can now require Go packages — the compiler introspects the source, discovers bridgeable functions, and makes them available automatically:

require "mymod"
puts mymod.greet("world")

require "github.com/user/rugo-slug@v1.0.0" as slug
slug.make("Hello World!")

Works with local directories, remote git repos, sub-packages, with, as aliases, rugo build, and rugo doc.

Go structs in require'd modules

Go modules can now expose structs. Create instances, read and write fields with dot syntax, call methods, and pass structs back to Go functions:

require "mymod"

c = mymod.new_config("app", 8080)
c.port = 9090
puts c.summary()

Methods are discovered at compile time and dispatched through generated switch statements — no reflection. type_of() and rugo doc work with structs too.

Integer range iteration and range() builtin

For-loops now support iterating over integers:

for i in 100
  puts i
end
# prints 0, 1, ..., 99

for i in range(5, 20)
  puts i
end
# prints 5, 6, ..., 19

range() also works as a standalone function to create arrays:

arr = range(10)
# [0, 1, 2, ..., 9]

Module visibility with underscore prefix

Functions prefixed with _ are now private to their module:

# In mymod.rugo
def _helper()    # private — not callable from outside
  return "internal"
end

Calling a private function from outside produces a compile error. Private functions are also hidden from rugo doc output.

Performance

Range for-loops now emit pure native Go arithmetic — same performance as while loops and equivalent Go code:

Loop style v0.19.0 v0.20.0
for i in 10000 N/A 2.0 µs/op
for i in range(0, 10000) N/A 2.0 µs/op
while loop (10k iterations) 2.0 µs/op 2.0 µs/op

The type inference engine now recognizes integer ranges, so loop variables and accumulators stay as native int instead of boxing through interface{}.

Bug Fixes

  • Struct error messages — Error messages no longer leak internal wrapper type names. Shows undefined method .Add() on Counter instead of *main.rugo_struct_sm_Counter.
  • /vN module pathsgo.mod placeholder versions now match major version suffixes correctly (e.g. v3.0.0-... for /v3 modules).
  • Value-type struct returns — Methods returning struct values (not pointers) now work correctly.
  • rugo eval error paths — Shows eval:1 instead of /tmp/rugo-eval-XXXX/eval.rugo:1 in error messages.
  • rugo doc index — Module index now shows only the summary line instead of the full multi-line description.

Documentation

  • New progressbar example using require'd Go modules.
  • Added ast module documentation.

v0.19.0

13 Feb 16:02
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

Rugo v0.19.0

New Features

Multi-return values and array destructuring

Go bridge functions that return multiple values now return arrays, and a new destructuring syntax unpacks them into variables:

before, after, found = strings.cut("key=value", "=")
dir, file = filepath.split("/home/user/test.txt")
a, b, c = [10, 20, 30]

Works with any expression returning an array — function calls, bridge multi-return functions, or plain array literals.

Automated bridge generation (rugo dev bridgegen)

New developer tool that scaffolds Go bridge packages automatically from Go's standard library:

rugo dev bridgegen math          # generate gobridge/math_gen.go
rugo dev bridgegen --dry-run os  # preview without writing
make bridgegen                   # regenerate everything

Uses go/types to enumerate exported functions, classify them by bridgeability, and generate complete bridge files with snake_case names and smoke tests. Bridge coverage is now 19 packages with 400 functions.

Expanded math bridge (67 functions)

The math bridge now covers 67 functions including hypot, trunc, round_to_even, copysign, fma, sinh, cosh, tanh, sincos, ldexp, and more.

New bridge packages: path and html

  • pathbase, clean, dir, ext, is_abs, join, match, split
  • htmlescape_string, unescape_string

Sandbox env: permission

Sandboxed scripts can now restrict which environment variables are visible:

sandbox env: ["PATH", "HOME"]
sandbox ro: ["/etc"], env: ["PATH", "HOME"], connect: [443]

When env: is specified, all env vars are cleared and only the listed ones are preserved. Also available via CLI: rugo run --sandbox --env PATH.

rugo doc disambiguation

Names like os and json exist as both a stdlib module and a bridge package. rugo doc now detects the conflict and asks you to be explicit:

rugo doc use:os      # stdlib module
rugo doc import:os   # bridge package

Unambiguous names continue to work without a prefix.

Documentation

  • New Rugo by Example guide with practical examples and verified outputs.
  • Rewritten gobridge.md reflecting the current auto-generation architecture.