Terraform 0.12 previews a consistent expression language
The HashiConf preview addresses the divide between HCL structure and interpolation. Expressions, conditional omission, and richer module values change how configuration can be composed.
Terraform's configuration language has accumulated workarounds for a division users do not necessarily expect to encounter. HCL describes configuration structure, while interpolation expressions provide dynamic values inside strings. The distinction becomes awkward when a dynamic expression needs to construct a collection. At HashiConf on October 23, HashiCorp announces a preview release of Terraform 0.12, bringing a redesigned language into an executable preview.
The language work predates today's announcement. The team's June introduction describes a broader HCL revision after years of feedback and unintuitive workarounds. It also acknowledges breaking changes and a developing upgrade guide. Today's preview should therefore be understood as an opportunity to examine the new configuration model, with the compatibility work still part of the project.
I regard Terraform's general language over infrastructure APIs as one of its strongest ideas. Its usefulness depends on whether reusable configuration remains understandable when real inputs vary. A language change that removes special cases can improve that property, even when the corresponding migration needs deliberate work.
Expressions stop depending on string wrappers
Martin Atkins's first-class expressions preview, published July 5, explains the structural cause. Earlier HCL separates structure parsing from interpolation. Collection literals belong to one phase, while expressions belong to the other, so constructs that look naturally composable do not always work together.
An ordinary attribute reference in 0.11 uses an interpolation string. In the 0.12 preview model, the expression can appear directly.
# Terraform 0.11 expression
ami = "${var.ami}"
# Terraform 0.12 preview expression
ami = var.ami
These are alternative attribute assignments, not two assignments to place in one resource. Removing the wrapper is the visible syntax improvement, but collection composition is a more consequential result. A conditional can use list literal syntax within its expression without switching to a special collection-building function.
The same article distinguishes attributes from blocks. An attribute uses assignment and has one value. Repeated blocks remain valid when the configuration defines several instances of a block type. Configurations that relied on repeated assignments being flattened into a collection lose that ambiguous interpretation.
For configuration authors, that gives punctuation a more stable meaning. The equals sign identifies an attribute assignment rather than a construction whose interpretation can depend on how repeated maps were flattened. This is one of the migration issues the preview makes visible, even while older string interpolation remains supported.
Iteration constructs values
The July 12 iteration preview describes for expressions that transform and filter a collection into another collection. Square brackets select a list result, while braces support a map result. The expression can supply an argument or module input that expects such a value.
# Terraform 0.12 preview
variable "subnet_numbers" {
default = [1, 2, 3]
}
output "subnet_cidrs" {
value = [
for num in var.subnet_numbers:
cidrsubnet("10.1.0.0/16", 8, num)
]
}
The example adapts the preview's subnet calculation. It derives subnet CIDRs from a list of numbers and an explicit base network. The resulting collection is a value, so this expression does not by itself declare subnet resources or give Terraform permission to create them.
This distinction helps keep the design understandable. Transforming input data and declaring remote objects are separate operations even when a language supports iteration around both. A module can accept a collection, derive another collection, and pass it onward without requiring a resource for each intermediate calculation.
A filter clause can omit elements, and the map form can group several results under a common key. These capabilities reduce dependence on narrowly tailored interpolation functions. Their practical value is a direct representation of the transformation being requested, which is easier to review than a sequence of unrelated function workarounds.
Absence becomes an expressible value
The July 26 conditional preview identifies two old limitations. Conditional results were restricted to simple values, and both result expressions were evaluated. The proposed model lifts those restrictions and introduces null to represent an unset argument.
The latter matters for modules that expose an optional override. Without a value representing omission, passing an input through to an argument can prevent the caller from selecting the argument's normal default behavior. A module may need different source text merely to distinguish setting a value from leaving it unset.
# Terraform 0.12 preview
variable "override_private_ip" {
type = string
default = null
}
When this input supplies an optional resource argument, its default represents omission rather than an empty string chosen by convention. The preview article demonstrates it with an instance private-IP override. This permits one module interface to support both an explicit address and the resource's default address selection.
I find this particularly relevant to reusable infrastructure modules. Defaults are often properties of the resource API, and a module should be able to preserve them without inventing a second meaning for an arbitrary string. An explicit absence value makes that relationship easier to state.
Module interfaces can describe structured data
The August rich-value-types preview extends module inputs and outputs to complex values. A module can receive a map of objects and constrain the expected structure, including nested maps. It can also return an entire resource as an object whose attributes derive from the resource schema.
That changes where an infrastructure abstraction can put its data model. Instead of several loosely associated inputs, a module can accept a collection of structured network descriptions. Type constraints describe the relationship between each network's number and availability zone. Validation then has information about the expected structure before the provider attempts a remote operation.
Richer values also make transformations more useful. An iteration expression can operate on the same structured data a module accepts, and outputs can preserve relationships that would otherwise need separate lists. These improvements reinforce each other because expression composition and module interfaces use a more consistent value model.
The October preview gives configuration authors a concrete way to examine that model. Its main architectural change is the ability to compose expressions and structured values without the old split between configuration structure and interpolation. Existing modules that depend on ambiguous syntax still need the migration guidance, while new interfaces can be assessed against the preview's explicit value and type rules.