Decorators
Decorators are annotations that modify the behavior of functions and structs. They use the @name syntax placed before a declaration.
@test #
Marks a function as a test. Test functions are collected and run with zolo test.
@test
fn test_addition() {
assert_eq(2 + 2, 4, "basic math")
}
@test
fn test_string_concat() {
let result = "hello" + " " + "world"
assert_eq(result, "hello world", "string concat")
}
Running Tests #
zolo test my_tests.zolo # run all tests
zolo test my_tests.zolo --filter fib # only matching tests
zolo test my_tests.zolo --list # list test names
Test Assertions #
assert_eq(actual, expected, "message") // assert equality
assert_ne(actual, expected, "message") // assert inequality
@memoize #
Automatically caches function results. Repeated calls with the same arguments return the cached value instead of recomputing.
@memoize
fn fibonacci(n: int) -> int {
if n <= 1 { n } else { fibonacci(n - 1) + fibonacci(n - 2) }
}
// First call computes normally
print(fibonacci(40)) // fast! cached intermediate results
// Subsequent calls with same args are instant
print(fibonacci(40)) // returns from cache
How It Works #
The compiler wraps the function with a cache table. Arguments are serialized as a key, and the result is stored. On repeated calls with the same arguments, the cached result is returned immediately.
Best For #
- Recursive functions (like fibonacci, tree traversals)
- Pure functions with expensive computation
- Functions called repeatedly with the same inputs
Limitations #
- Only works with serializable arguments
- Cache grows unbounded (no eviction)
- Not suitable for functions with side effects
@deprecated #
Marks a function as deprecated. When called, it prints a warning to stderr (once per function).
@deprecated("use new_calculate() instead")
fn old_calculate(x: int) -> int {
x * 2
}
old_calculate(5)
// stderr: WARNING: 'old_calculate' is deprecated: use new_calculate() instead
Without Message #
@deprecated
fn legacy_api() {
// ...
}
legacy_api()
// stderr: WARNING: 'legacy_api' is deprecated
Behavior #
- The warning is printed only once per deprecated function (not on every call)
- The function still executes normally after the warning
- Output goes to stderr, not stdout
@builder #
Generates a builder pattern for structs. The builder allows constructing structs field-by-field with method chaining.
@builder
struct Config {
host: str,
port: int,
debug: bool,
}
let cfg = Config.builder()
.host("localhost")
.port(8080)
.debug(true)
.build()
print(cfg.host) // "localhost"
print(cfg.port) // 8080
print(cfg.debug) // true
Generated Methods #
For each field name: Type in the struct, @builder generates:
StructName.builder()— creates a new builder instance.field_name(value)— sets the field value, returns the builder.build()— creates the final struct instance
Example: Complex Builder #
@builder
struct Request {
url: str,
method: str,
timeout: int,
headers: {str: str},
}
let req = Request.builder()
.url("https://api.example.com")
.method("POST")
.timeout(30)
.build()
Custom Decorators #
Decorators follow the syntax:
@name
@name(arg1, arg2)
The decorator name and arguments are stored in the AST and processed during compilation. Currently, the built-in decorators (@test, @memoize, @deprecated, @builder) are handled by the compiler. Custom user-defined decorator processing is planned for a future version.
Combining Decorators #
Multiple decorators can be applied to a single declaration:
@test
@memoize
fn test_cached_fibonacci() {
assert_eq(fibonacci(10), 55, "fib(10)")
}
Decorators are applied in bottom-up order (closest to the function first).