explicitopen

CUE v0.18.0

Surface: per-file @experiment(...) attribute

Enablement

@experiment(explicitopen)

Lifecycle

preview since v0.15.0 · stable since v0.18.0

Description

ExplicitOpen enables the postfix ... operator to explicitly open closed structs, allowing additional fields to be added. It is stable as of v0.18.0, so a file at that language version or later embeds strictly and may use the postfix ... operator; cue fix migrates older files.

Specification changes

What this experiment changes in the language specification, section by section. Removed and added text is marked in place.

Document new ... operator

merged 2026-09-04 · v0.18.0

Values › Structs › EmbeddingIn the specification

A struct may contain an embedded value, an operand used as a declaration. An embedded value of type struct is unified with the struct in which it is embedded, but disregarding the restrictions imposed by closed structs. So if an embedding resolves to a closed struct, the corresponding enclosing struct will also be closed, but may have fields that are not allowed if normal rules for closed structs were observed.embedded.

Embeddings can be useful for composing larger schemas from smaller ones. The order of specification may imply a documented field order.

If an embedded value is not of type struct, the struct may only have definitions or hidden fields. Regular fields are not allowed in such case.

The result of { A } is A for any A (including definitions).

Syntactically, embeddings may be any expression.

S1: {
    a: 1
    b: 2
    {
        c: 3
    }
}
// S1 is { a: 1, b: 2, c: 3 }
 
S2: close({
    a: 1
    b: 2
    {
        c: 3
    }
})
// same as close(S1)
@experiment(explicitopen)

S3: {
    a: 1
    b: 2
    close({
        c: 3
    })
Meta: {
    kind: string
    name: string
}
// same as S2
Guzzler: {
    Meta...
    volume: number
}
// Guzzler is { kind: string, name: string, volume: number }

Values › Structs › Definitions and hidden fieldsIn the specification

2 unchanged paragraphs

A field is a definition if its identifier starts with # or _#. A field is hidden if its identifier starts with a _. All other fields are regular.

Definitions and hidden fields are not emitted when converting a CUE program to data and are never required to be concrete.

Referencing a definition will recursively close it. That is, a referenced definition will not unify with a struct that would add a field anywhere within the definition that it does not already define or explicitly allow with a pattern constraint orconstraint, .... Embedding allows bypassing this check., or the spread operator (postfix ...).

3 unchanged blocks

If referencing a definition would always result in an error, implementations may report this inconsistency at the point of its declaration.

cue
#MyStruct: {
    sub: field:    string
}

#MyStruct: {
    sub: enabled?: bool
}

myValue: #MyStruct & {
    sub: feild:   2     // error, feild not defined in #MyStruct
    sub: enabled: true  // okay
}

#D: {
    #OneOf

    c: int // adds this field.
}

#OneOf: { a: int } | { b: int }


D1: #D & { a: 12, c: 22 }  // { a: 12, c: 22 }
D2: #D & { a: 12, b: 33 }  // _|_ // cannot define both `a` and `b`
cue
#A: {a: int}

B: {
    #A
    b: c: int
}

x: B
x: d: 3  // not allowed, as closed by embedded #A

y: B.b
y: d: 3  // allowed as nothing closes b

#B: {
    #A
    b: c: int
}

z: #B.b
z: d: 3  // not allowed, as referencing #B closes b

Expressions › Primary expressionsIn the specification

Primary expressions are the operands for unary and binary expressions.

PrimaryExpr =
	Operand |
	PrimaryExpr Selector |
	PrimaryExpr Index |
	PrimaryExpr Arguments .
	PrimaryExpr Arguments |
	PrimaryExpr "..." .

Selector       = "." (identifier | simple_string_lit) .
Index          = "[" Expression [ "," ] "]" .
Argument       = [ identifier ":" ] Expression .
Arguments      = "(" [ ( Argument { "," Argument } ) [ "," ] ] ")" .
cue
x
2
(s + ".txt")
f(3.1415, true)
m["foo"]
obj.color
f.p[i].x

Expressions › Spread Operator (...)new section

For a primary expression x that evaluates to a struct or list, the spread operator

x...

unifies all elements of x into the current context disregarding closedness rules implied by x, recursively. The spread operator is a no-op for all other types.

The spread operator can be used to extend a struct or list and it is an error for ... to be applied to anything else.

If x... is used within a definition, normal closedness rules apply after the x is unified with any other fields.

#Base: foo: int
#Def: {
    #Base... // Unified with other fields within #Def.
    bar: int
}
d: #Def & {baz: 2} // Error: baz is not allowed
 
// Define a type that matches a collection of derived types.
#Animal: genus!: string
 
#Dog: { #Animal..., genus: "canis", says: "bark" }
#Cat: { #Animal..., genus: "felis", says: "meow" }
 
// Use #AnyAnimal in schema to allow any value that derives from #Animal.
#AnyAnimal: { #Animal..., ... }
 
#Schema: animal: #AnyAnimal // Dog, Cat or any other #Animal.

Builtin Functions › closeIn the specification

The builtin function close converts a partially defined, or open, structdefined—or open—struct to a fully defined, or closed, struct.defined—or closed—struct.

Builtin Functions › __closeAllnew section

The builtin function __closeAll recursively converts a partially defined, or open, struct to a fully defined, or closed, struct. It is a no-op for values that are not a struct or list.

This builtin is only defined for the explicitopen experiment and can be used by cue fix instead of __reclose if it is known for sure a struct needs to be reclosed. It should not be used by users directly.

Builtin Functions › __reclosenew section

The builtin function __reclose closes a literal struct if any of its embedding that used a spread operator was closed. It is a no-op for all other values.

This builtin is only defined for the explicitopen experiment and is used used by cue fix to convert legacy CUE code to the new semantics. It should not be used by users directly.

Install CUE