CUE allows fields to be given a value which is used only if unification results in no other value being provided.

This guide demonstrates CUE’s default value syntax.

Specifing defaults

Specify a default by marking one element of a disjunction with an asterisk prefix:

// field 'a' has a default value of "A".
// it can also be set to any other value,
// through unification
a: *"A" | _

If a field’s value is not provided through unification then the default is used.

If a regular field has a value provided elsewhere, through unification, then that value is used instead:

policy.cue
package example

a: *"A" | _
b: *"B" | _
data.cue
package example

a: "some value"
TERMINAL
$ cue export .:example
{
    "a": "some value",
    "b": "B"
}

The purpose of the disjunction

If a default is provided, it must take the form of a single element of a valid disjunction - that is, a disjunction containing at least 2 elements.

If the field has a value provided elsewhere, through unification, then at least one of the disjunction’s elements must unify with the field’s concrete value. The concrete value provided through unification may be the same as the default value:

policy.cue
package example

a: *"A" | string
b: *5 | int
data.cue
package example

a: "A"
TERMINAL
$ cue export .:example
{
    "a": "A",
    "b": 5
}

If no element unifies with the value that has been provided, a disjunction resolution error results:

policy.cue
package example

a: *"A" | string
b: *5 | int
data.cue
package example

b: "a string"
stderr.txt
b: 2 errors in empty disjunction:
b: conflicting values "a string" and 5 (mismatched types string and int):
    ./data.cue:3:4
    ./policy.cue:4:5
b: conflicting values "a string" and int (mismatched types string and int):
    ./data.cue:3:4
    ./policy.cue:4:9

Defaults are usually specified as concrete values

Defaults are used by the CUE evaluator when a field’s value needs to be used in a concrete context, but where no specific value has been provided elsewhere, through unification.

Therefore, in order to be useful, defaults need to evaluate to a concrete value. If CUE needs to use a default but the value provided is not concrete, an error results:

CUE
package example

a: *"A" | _
b: *string | _
ERR
b: incomplete value string:
    ./in.cue:4:5

Defaults can include references

Defaults are commonly specified as explicit concrete values, but if CUE can resolve a default to a concrete value via references then the result can be successfully used as a default:

CUE
package example

a: 5
b: *( a + 10) | int

c: "hello"
d: *( c + ", world!") | string
JSON
{
    "a": 5,
    "b": 15,
    "c": "hello",
    "d": "hello, world!"
}

Defaults can be complex values

Defaults are often specified as primitive types (bool, string, number, int or float) but they can also take complex types.

These values can either be provided inline, or by reference.

In this example, both defaults for the field a are equivalent:

CUE
package example

a: string | *_s
a: string | *{
	x: "value"
	y: [
		"hello",
		"world",
	]
}
_s: {
	x: "value"
	y: [
		"hello",
		"world",
	]
}
JSON
{
    "a": {
        "x": "value",
        "y": [
            "hello",
            "world"
        ]
    }
}

Specifying multiple defaults is usually not useful

A single field may have multiple defaults specified in parallel, provided that all the defaults unify successfully:

CUE
package example

a: *"A" | _
a: *string | _
JSON
{
    "a": "A"
}

If multiple defaults are provided but they do not unify successfully, then CUE treats the field as if no defaults were provided. If unification then finishes without a concrete value being specified elsewhere, or without sufficient information being provided to resolve the disjunction, then the result is an error:

CUE
package example

a: *"A" | _
a: *int | _
ERR
a: incomplete value "A" | int | _

However, even if multiple defaults are provided and they unify successfully, doing so is usually not a useful technique.

This is because in the case where CUE needs to use the unified default (because no concrete, regular value was discovered through unification) the result of unifying all the defaults must be concrete. Such concreteness can be derived from the unification of multiple non-concrete defaults - but as the following example demonstrates, this has a higher change of leading to unclear CUE that could confuse the reader:

package example

port_x: *<=8080 | string
port_y: *<=8080 | string
package example

port_x: *>=8080 | string
port_y: *>=8080 | string
data.cue
package example

port_x: "a string, for some reason"
TERMINAL
$ cue export .:example
{
    "port_x": "a string, for some reason",
    "port_y": 8080
}

The preceding example is not an example of clear and straightforward CUE, and the reader would be better served by having an explicit and concrete default specified (e.g. port_x: *8080 | string). Having specified such a concrete default, there is usually little point in also specifying non-concrete defaults in parallel.

There are specific, nuanced, situations where this advice might not hold true. However, in general, using multiple defaults is unnecessary and risks confusing the reader.

Defaults provide only a single preference layer

As demonstrated above, if a field has multiple defaults provided but they do not all unify successfully, then CUE behaves as if no default had been provided.

For this reason, defaults cannot be used as a system of multi-layered, overriding values.

See also