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.

zolo▶ Playground
@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 #

bash
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 #

zolo▶ Playground
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.

zolo▶ Playground
@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).

zolo▶ Playground
@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 #

zolo▶ Playground
@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.

zolo▶ Playground
@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 #

zolo▶ Playground
@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:

zolo▶ Playground
@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:

zolo▶ Playground
@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).

enespt-br