Values and expressions
Rivet evaluates expressions while compiling. The host only receives the results.
Parentheses group expressions. Declared types include scalars, resource handles, and nested list/set/map/object/tuple types. Collections can be inputs, locals, module arguments, or outputs. See types.
Interpolation
Strings, finite numbers, and booleans can be interpolated. Null, collections, and handles require explicit conversion or encoding. Numeric facts support interpolation and numeric outputs; ordinary resource parameters retain strict types. Templates support heredocs, directives, and whitespace stripping. Expression nesting is limited to 128.
Operators
Precedence follows HCL: unary, multiplicative, additive, comparison, equality, &&, ||, then the conditional. A result carries the dependencies and sensitivity of every operand that was evaluated.
For expressions
[for v in collection : value] builds a list; {for k, v in collection : key => value} builds a map. Lists bind index and element, maps bind key (in sorted order) and value, and sets bind each element as both. An optional if filter drops elements. Duplicate map keys fail unless the value ends in ..., which groups values into lists. Keys convert strings, numbers, and bools to strings.
Loop variables are visible only within the expression and may shadow other names. Each element keeps the annotations it actually read. A sensitive filter or collection shape makes the result sensitive, and a sensitive key makes the map’s keys sensitive. Template loops and for expressions share a compilation-wide 100,000-iteration budget.
Splats
list[*].attr applies every following traversal step to each element and returns a list. list.*.attr applies only the attribute lookups immediately after it, so list.*.attr[0] indexes the resulting list. Splatting null yields [], and any other non-list value becomes a one-element list.
References
Use var.name, local.name, file.label, or an exported static property such as service.app.name. Local values can refer forward to other locals or declarations. Cycles are errors.
A handle is declared work, not an apply result. You can read only supported static attributes; omitted optional attributes cannot be discovered from the host. See resource conventions for the export list.
Resource provenance survives locals and interpolation. Referring to file.config.path in a consuming resource makes the config a prerequisite. An independently written equal path string does not create that relationship.
Fact paths
Use dot traversal, map keys, array indices, splats, and for expressions:
These examples need a collected snapshot with the relevant fields. Baseline-only assumptions contain just the four legacy fact aliases. Indices may be evaluated expressions. Missing keys and invalid indices fail; integer facts can supply numeric indices/count. Optional inventory fields may be absent; use try or can for guarded access. Decoded collections such as jsondecode(file("assets/config.json")) support computed traversal too.
Conditionals and collection functions
A conditional requires a boolean condition and evaluates only the selected branch. Condition dependencies and sensitivity propagate into selected values. toset accepts a list or set, unifies compatible primitive elements, and deduplicates deterministically. length accepts a collection or string; string length counts Unicode grapheme clusters.
Use each.key/each.value and count.index only inside the appropriate expanded instance. Module outputs use module.name.output; path.module is the canonical declaring directory. All built-in functions and template directives are available. Final argument expansion is supported for ordinary functions, for example max([1, 2, 3]...); try and can take explicit expressions.