Closedness describes the state of a value in terms of extensibility.
In the simplest terms, a closed value cannot have fields added and an open value can.
There are a lot of rules for determining a Value’s closed state
and some constructs define sets of fields.
So don’t worry if it seems confusing at first, the implementation is even trickier!
Structs are open by default
Structs are open and extensible by default.
You can add fields through or after conjunction.
This applies recursively to all fields.
struct.cue
S: { name: string point: { x: int y: int }}// we can extend the fieldss: S & { data: bytes point: z: int}
Definitions are closed by default
Definitions have a fixed structure.
You can be sure no extra fields will be added.
This applies recursively to all fields.
definition.cue
#D: { name: string data: { num: int }}// you can split the spec, this is not extension#D: tags: [...string]// after conjuncting, extension is not allowedd: #D & { meta: string data: { val: string }}
Opening and closing values
You can open definitions with ... and close structs with close.
These are not recursively applied, only changing the field they are used on.
open-n-close.cue
S: close({ name: string point: { x: int y: int }})s: S & {// this is no longer allowed data: bytes// this is still allowed point: z: int}#D: { name: string data: { num: int ... } ...}// this is now allowedd: #D & { meta: string data: { val: string }}
Closedness with pattern constraints
Pattern constraints define a set of values. So while d & #D is closed,
it can still have an infinite number of labels defined.
Embedding allows the extension of a definition while still
receiving updates when #D is changed.
embed.cue
#D: { name: string data: { num: int }}// embed D into a new definition#L: { #D tags: [...string]}// after conjuncting, extension is not allowedd: #L & { meta: "meta" data: { num: 3 } tags: ["foo", "bar"]}
Hidden fields
You can add hidden fields to a closed value. This works for both definitions
and structs which have been close()’d.
hidden.cue
#D: { size: string data: { x: int y: int }}// after conjuncting, extension is not allowedd: #D & { data: { x: 3 y: 4 } _calc: data.x * data.y size: string|*"med"if _calc <10 { size: "small" }if _calc >100 { size: "large" }}
List closedness
List open and closedness is much simpler than structs and definitions.
When you use ellipses, the list is open.
Any fixed elements are required in the exact position.
list.cue
// an open listL1: [...]// one element list with any typeL2: [_]// an int list with at least one elementL3: [int, ...]// a mixed list of four elementsL4: [int, string, {...}, _]// you can concatenate lists tooL5: L2 + L3 // openL6: L2 + L4 // closedL7: L3 + L2 // closed, L3 ellipses removed// You cannot append or reopen like thisL2: L2 + [...]