October 19, 2022·4 min read·engineeringterraformtypesdefaults

Terraform 1.3.3 fixes empty collections with optional attributes

A type-conversion repair makes empty lists and sets work with nested optional-object constraints, without changing the meaning of attribute defaults.

A collection containing no objects still has to satisfy an object element type. Terraform 1.3.3 fixes an error that occurred when empty collections met variables whose nested object attributes had defaults. The October 19 release is a small patch to a recently stabilized language feature, but it concerns a common module interface requirement. Zero configured instances should be expressible without abandoning the element schema.

The implementation arrives through pull request 32033, which upgrades Terraform's cty dependency to 1.11.1. The cty changelog for that version identifies the correction as removing optional-attribute information appropriately when converting empty sets and lists. That is a type-conversion repair, not a request for module authors to supply a dummy element.

I value strongly described module inputs because infrastructure interfaces often outlive their first caller. An empty collection should preserve that description. Requiring one placeholder object just to get through conversion would confuse the module's input shape with the number of objects a caller actually wants.

Defaults describe members that exist

The Terraform 1.3 type-constraint reference defines optional attributes as part of an object constraint. A missing optional attribute receives its declared default. If the declaration supplies no default, Terraform uses a typed null value.

The rule applies to an object that reaches the receiving module. A list constraint can use that object type for each element, allowing callers to omit selected attributes from individual objects. The following input represents named workers with an optional enabled setting.

variable "workers" {
  type = list(object({
    name    = string
    enabled = optional(bool, true)
  }))
  default = []
}

With an empty list, there is no worker object whose enabled flag needs filling. With an object containing a name alone, the optional member receives true. Those cases are compatible uses of the same interface. The default list controls what happens when the whole variable is omitted, while the attribute default controls what happens inside an element.

That distinction becomes useful when a module intentionally accepts no workers for a particular configuration. An empty collection does not mean there is one worker with unknown settings. It means that no elements need the object-level defaults. Conversion still needs a concrete collection type so the rest of the configuration can use the input consistently.

Optional constraints and converted values differ

The cty release note points to optional-attribute metadata in the conversion process. Optionality describes what the receiving constraint will accept. After conversion, a value needs the corresponding concrete type. Empty collections are an edge case because there are no element values to traverse while determining the result.

The correction explicitly removes optional-attribute information from collections where appropriate. Terraform incorporates it by updating the dependency, so the small Terraform patch does not contain a new language-level defaulting algorithm. Its change is to use the repaired conversion implementation.

This explains why the version number of the library matters to the event. The Terraform pull request changes the dependency from cty 1.11.0 to 1.11.1. The latter's changelog dates the fix to October 17. Terraform's patch release makes that dependency correction available in the CLI two days later.

The language contract remains the one documented in September. Non-null optional defaults also replace explicitly supplied null attributes, and nested object defaults apply from the outside inward. The collection fix makes a boundary case conform to that interface instead of changing the fallback policy for populated objects.

I prefer this repair at the conversion layer because it preserves one declaration for empty and populated input. A module author can keep the element type explicit, and callers can express absence with the collection value the interface already allows. The type system does not need to lose information merely because the current value has no members.

Removal also has an absence case

The release includes another correction involving absence. It fixes an error when removing a resource from configuration after the provider reports that the remote object is already deleted. That concern is separate from object defaults, but both cases require Terraform to handle something that is no longer present.

The linked resource-removal report describes a failed creation followed by removing the declaration and encountering a graph-node configuration error. The release notes identify the shipped correction in terms of a provider-reported deleted object. That scope should not be expanded into a claim that all interrupted creates or all graph errors share one cause.

For the optional-attribute change, the practical result is narrower and clear. A variable can retain a collection of nested object types with defaults while accepting an empty collection. Modules that deliberately permit zero members no longer encounter this particular conversion failure simply because their element type contains optional attributes.