New Features
Optional type annotations
You can now annotate function parameters and return types when you want to. Annotations are optional everywhere — existing code keeps working unchanged.
def add(a : Integer, b : Integer) : Integer
return a + b
end
def greet(name : String) : String
return "hello, " + name
end
square = fn(n : Integer) : Integer
return n * n
endThe recognised names mirror what type_of() returns: Integer, Float, String, Bool, Array, Hash, Nil, Any. Whitespace around : is allowed but not required.
What this gets you:
- Faster code. Annotated
Integer/Float/String/Boolparams and returns compile to typed Go signatures (e.g.func(int, int) int), skipping the boxing/unboxing that dynamic code needs. - Earlier errors. Misspelled type names (
integer,Strring) fail at compile time with a clear file:line message; common lowercase shorthands likeintget a "did you mean" hint. - Visibility.
rugo emit --stats script.rugonow reports annotation coverage alongside what type inference figured out on its own, so you can see where a couple of annotations would help most.
Mix annotated and unannotated freely — there's no requirement to type everything, and no runtime cost to typing some of it.
See docs/language.md for the full reference and examples/typed_functions.rugo for a working tour.
Variable type annotations
Local variables can be annotated too, with the same name : type = expr syntax:
name : String = "alice"
count : Integer = 0The annotation sticks for the rest of the scope. Later assignments must match, or the compiler tells you exactly what went wrong:
count = "oops"
# error: cannot assign String value to variable 'count' declared as IntegerUse : Any when you genuinely want a variable to hold different types over time:
value : Any = 42
value = "now a string" # okCompile-time type-mismatch detection
When the types involved are concretely known, Rugo now flags clear conflicts at compile time instead of producing a confusing Go-level message or a runtime panic. Three flavours are covered:
Bad assignments inside annotated function bodies:
def f(a : Integer)
a = "hello"
# error: script.rugo:2: cannot assign String value
# to parameter 'a' declared as Integer
endBad return values:
def lookup(id : Integer) : Integer
if id < 0
return "negative"
# error: cannot return String value from
# function declared returning Integer
end
return id
endBad arguments at call sites — works for literal values and for variables whose type the compiler can track:
def add(a : Integer, b : Integer) : Integer
return a + b
end
puts add("xyz", 2)
# error: cannot pass String literal as argument 1 to 'add'
# (parameter 'a' declared as Integer)
x = 42
def greet(name : String)
puts "hi #{name}"
end
greet(x)
# error: cannot pass Integer value as argument 1 to 'greet'
# (parameter 'name' declared as String)Rugo follows the type of each variable through if, case, while, and for, so the same checks apply across branches and reassignments. When a variable could legitimately hold one of several types depending on which branch ran, the compiler only complains if every possible type would be wrong — keeping real code quiet while still catching genuine mistakes.
Compatibility rules match what the runtime does: numeric types (Integer and Float) are mutually compatible, and Any parameters accept any value. Pass --no-infer to disable the check.
Improvements
Nilas a type annotation.def f() : Nilanddef g(x : Nil)now parse correctly, matching what the documentation already described.- Annotation coverage in
--stats.rugo emit --statsreports how many params/returns carry annotations, alongside the existing inferred-type stats.
Bug Fixes
test.run("rugo …")uses the rugo under test. RATS tests that invokerugothrough thetestmodule now consistently use the binary running the suite, not a system-installed one that happened to be onPATH. Tests can reliably assert onrugo emit,rugo build, and other CLI output.