hashicorp/terraform · error

argument must be a string, a collection type, or a structura

Error message

argument must be a string, a collection type, or a structural type

What it means

Thrown by the Type function of LengthFunc (the HCL length() builtin). length() only accepts a string, tuple, object, list, map, set, or dynamic value. Any other concrete type (number, bool) fails type validation before the function body runs.

Source

Thrown at internal/lang/funcs/collection.go:35

)

var LengthFunc = function.New(&function.Spec{
	Params: []function.Parameter{
		{
			Name:             "value",
			Type:             cty.DynamicPseudoType,
			AllowDynamicType: true,
			AllowUnknown:     true,
			AllowMarked:      true,
		},
	},
	Type: func(args []cty.Value) (cty.Type, error) {
		collTy := args[0].Type()
		switch {
		case collTy == cty.String || collTy.IsTupleType() || collTy.IsObjectType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType:
			return cty.Number, nil
		default:
			return cty.Number, errors.New("argument must be a string, a collection type, or a structural type")
		}
	},
	RefineResult: refineNotNull,
	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		coll := args[0]
		collTy := args[0].Type()
		marks := coll.Marks()

		switch {
		case collTy == cty.DynamicPseudoType:
			return cty.UnknownVal(cty.Number).WithMarks(marks), nil
		case collTy.IsTupleType():
			l := len(collTy.TupleElementTypes())
			return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
		case collTy.IsObjectType():
			l := len(collTy.AttributeTypes())
			return cty.NumberIntVal(int64(l)).WithMarks(marks), nil
		case collTy == cty.String:

View on GitHub (pinned to c9def3e214)

Solutions

  1. Convert first or pick the right function: length(tostring(var.n)) for digit/grapheme count, or use a different expression for booleans.
  2. Re-type the variable so it is a collection or string.
  3. Guard with can(length(var.x)) when the input type is uncertain.

Example fix

# before
output "n" { value = length(var.replica_count) }   # replica_count is a number -> error
# after
output "n" { value = length(split("", tostring(var.replica_count))) }   # count digits
# or, if you meant a count of resources, pass the actual collection:
output "n" { value = length(aws_instance.replicas) }
Defensive patterns

Strategy: type-guard

Validate before calling

# ensure the argument is a collection/string before calling length()
locals {
  len_or_null = can(length(var.x)) ? length(var.x) : null
}

Type guard

# type guard: only call length on values that can be measured
locals {
  ok  = can(length(var.x))
  res = local.ok ? length(var.x) : null
}

Try / catch

locals { len = try(length(var.x), null) }

Prevention

When it happens

Trigger: Calling length() on a scalar: length(5), length(true), or a variable typed as number/bool.

Common situations: Assuming length() works on numbers (e.g., to count digits); passing a boolean flag; a variable whose type was narrowed to number by an upstream expression.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/bff2a95a9a0f7079. Report an issue: GitHub.