Scopes and Visibility



Lexical Scoping

CUE uses lexical scoping, not dynamic

Evaluation (or resolution) of references is lazy, consider

https://cuelang.org/play/?id=5AmcbLo3DPC#cue@export@cue

That the “example” field unifies the #example definition and a struct does not place the definition’s fields in the scope of the struct.

Reference Lookup

Cue will reference a value from the nearest enclosing scope. Some quirks are:

  • fields without quotes you can reference as identifiers and with dot notation
  • fields with quotes require index notation
  • fields defined across scopes require sufficient paths to resolve

lookup.cue

val: 42
A: {
	val: 23
	num: val // will be 23, matches locally

	// irregular names need to be quoted and require indexing to access
	"user-id": "abc"
	UserID:    A["user-id"]
}
A: {
	b: val   // 42, matches top-level
	c: A.num // 23, reference A.num

	// num itself is not in scope even though it is part of A
}

Aliases

An alias defines a local value which is not part of the struct. They can be used for accessing shadowed fields, naming intermediate calculations or expressions, and more. They will not be output.

alias.cue

import "strings"

label: "app"
// alias for label
let L = label
S: {
	name: "Postgres"
	// intermediate value
	let lower = strings.ToLower(name)
	version: "13"
	label:   L
	image:   "docker.io/\(lower):\(version)"
}

Paths

As we’ve seen, there are a few ways to access nested values.

paths.cue

A: {
	a:    "A"
	"2f": 3
	l: ["cow", "moo"]
}

a: {
	// either is valid
	f1: A.a
	f2: A["a"]
	// must index
	f3: A["2f"]
	// list index
	f4: A.l[1]
}

Hidden Fields and Values

Hidden fields and values are prefixed with underscores. You can reference them in the current package and optionally show them when evaluating. Use quotes if you want a real label that begins with an underscore.

hidden.cue

A: {
	_hidden: "a hidden field"
	isshown: "I can be seen"
	hidrefd: _hidden + " sort of?"
}

_#NoshowDefn: {
	hello: string
	num:   int | *42
}

B: _#NoshowDefn & {hello: "world"}

cue eval hidden.cue

A: {
	isshown: "I can be seen"
	hidrefd: "a hidden field sort of?"
}
B: {
	hello: "world"
	num:   42
}

cue eval -H hidden.cue

A: {
	_hidden: "a hidden field"
	isshown: "I can be seen"
	hidrefd: "a hidden field sort of?"
}
B: {
	hello: "world"
	num:   42
}

Note, hidden fields are not visible across package boundaries. So you cannot refer to hidden fields in imported packages.

Reference Cycles

Cue can resolve many cycles, as long as they resolve to a final, concrete value.

ref-cycle.cue

// a mathematically valid releationship
// but invalid in Cue until one or the other has a value
a: b - 10
b: a + 10

// we need to set a or b and Cue will do the rest
// if not, a cycle error will be reported
a: 100

Structural Cycles

Structural cycles and recursion are not allowed in Cue. We can define infinite structures as long as the data is finite.

structural.cue

#List: {
	val:  _
	next: #List | *null
}

// Cannot do this, it is structural recursion
#Contains: {
	list:  #List
	val:   _
	found: bool | *false

	if list.val == val {
		found: true
	}
	if list.val != val && list.next != null {
		// No recursion!
		found: #Contains & {"list": list.next, "val": val}
	}
}

// We can define an infinite structure
list: #List & {val: "a", next: {val: "b"}}

// results in
list: {
	val: "a"
	next: {
		val:  "b"
		next: null
	}
}
We'll never share your email with anyone else.
2024 Hofstadter, Inc