try
CUE v0.18.0
Surface: per-file @experiment(...) attribute
Enablement
@experiment(try)Lifecycle
preview since v0.16.0
Description
Try enables the try clause and optional reference markers (?). The try clause allows conditional field inclusion based on whether optional references resolve. The ? marker on references (a?, a.b?, a[i]?) is only valid within a try context.
Specification changes
What this experiment changes in the language specification, section by section. Removed and added text is marked in place.
Document try clause in comprehensions
Lexical elements › Keywords › Comprehension clausesIn the specification
The following keywords are used in comprehensions.
for in if let
try else otherwiseExpressions › ComprehensionsIn the specification
Lists and fields can be constructed using comprehensions.
Comprehensions define a clause sequence that consists of a sequence of
for, if, let, and lettry clauses, nesting from left to right.
The sequence must start with a for, if, or iftry clause.
The for, let, and lettry (assignment form) clauses each define a new scope
in which new values are bound to be available for the next clause.
4 unchanged paragraphs
The for clause binds the defined identifiers, on each iteration, to the next
value of some iterable value in a new scope.
A for clause may bind one or two identifiers.
If there is one identifier, it binds it to the value of
a list element or struct field value.
If there are two identifiers, the first value will be the key or index,
if available, and the second will be the value.
For lists, for iterates over all elements in the list after closing it.
For structs, for iterates over all non-optional regular fields.
An if clause, or guard, specifies an expression that terminates the current
iteration if it evaluates to false.
The let clause binds the result of an expression to the defined identifier
in a new scope.
The try clause enables conditional field inclusion based on whether
optional references resolve successfully.
A try clause has two forms: the binding form (try x = expr { body })
and the struct form (try { struct }).
The struct form must be the last clause; the comprehension value
becomes the try expression.
Within a try expression, references may be marked with a ?
suffix (e.g., a?, a.b?.c, a[foo?]?) to indicate optional evaluation.
The ? marker is only valid within the try expression scope.
If any ?-marked reference fails to resolve because the referenced field does
not exist (undeclared or a field constraint), the entire try clause silently
discards its output. Other errors propagate normally.
In the binding form, the expression after = is evaluated and,
if successful, bound to the identifier for use in subsequent clauses
and the comprehension body.
2 unchanged paragraphs
A current iteration is said to complete if the innermost block of the clause sequence is reached. Syntactically, the comprehension value is a struct. A comprehension can generate non-struct values by embedding such values within this struct.
Within lists, the values yielded by a comprehension are inserted in the list at the position of the comprehension. Within structs, the values yielded by a comprehension are embedded within the struct. Both structs and lists may contain multiple comprehensions.
Comprehension = Clauses StructLit .
Clauses = StartClause { [ "," ] Clause } .
StartClause = ForClause | GuardClause .
Clause = StartClause | LetClause .
ForClause = "for" identifier [ "," identifier ] "in" Expression .
GuardClause = "if" Expression .
LetClause = "let" identifier "=" Expression .
ElseClause = "else" StructLit .A comprehension may have an optional clause to handle the case when no values are yielded. Two keywords are available, each with distinct semantics:
a: [1, 2, 3, 4]
b: [for x in a if x > 1 { x+1 }] // [3, 4, 5]
c: {
for x in a
if x < 4
let y = 1 {
"\(x)": x + y
}
}
d: { "1": 2, "2": 3, "3": 4 }else clause: Used with single-clause conditionals for binary choice.
Provides traditional if-else semantics where exactly one branch is taken.
- With
if: the else clause executes when the condition is false - With
try: the else clause executes when the optional reference fails - Restriction: only allowed with a single
ifor singletryclause (no chaining, no other clauses)
otherwise clause: Used for all other comprehension types as a default value.
Provides a default when the comprehension yields zero items.
- Triggers when the source collection is empty, when iterations are filtered out, or when multiple clauses result in no values being yielded
- Used with:
forclauses, multiple clauses, or any combination other than singleifor singletry
The clause body is evaluated in the enclosing scope, not the comprehension’s
internal scope. Identifiers bound by for or let clauses are not accessible
within the otherwise clause, since these bindings have no meaningful
value when zero items are yielded.
If the comprehension source or condition contains an error, the error propagates rather than triggering the else or otherwise clause
Comprehension = Clauses StructLit [ OtherwiseClause ] .
Clauses = StartClause { [ "," ] Clause } .
StartClause = ForClause | GuardClause | TryClause .
Clause = StartClause | LetClause .
ForClause = "for" identifier [ "," identifier ] "in" Expression .
GuardClause = "if" Expression .
LetClause = "let" identifier "=" Expression .
TryClause = "try" [ identifier "=" Expression ] .
OtherwiseClause = ( "else" | "otherwise" ) StructLit .a: [1, 2, 3, 4]
b: [for x in a if x > 1 { x+1 }] // [3, 4, 5]
c: {
for x in a
if x < 4
let y = 1 {
"\(x)": x + y
}
}
d: { "1": 2, "2": 3, "3": 4 }
// else examples (single clause only)
e: {
if false { a: 1 } else { b: 2 }
}
f: { b: 2 }
g: {
for x in [] { "\(x)": x } otherwise { empty: true }
}
h: { empty: true }
// otherwise accesses outer scope
i: {
let threshold = 10
for x in [] { x } otherwise { otherwiseField: threshold }
}
j: { otherwiseField: 10 }
// try clause examples (requires @experiment(try))
@experiment(try)
// struct form: include fields only if optional references resolve
k: {
x: 1
try { y: x? } // { y: 1 }
}
// try with else: otherwise when reference doesn't resolve
m: {
optField?: int
try { y: optField? } else { z: 0 } // z: 0
}
// binding form: bind result to identifier
o: {
x: 1
try v = x? { y: v + 1 } // y: 2
}
// chained try clauses
q: {
a: 1
b: 2
try x = a? try y = b? { sum: x + y } // sum: 3
}
// optional marker on selector and index expressions
s: {
obj: { field: 1 }
list: [1, 2, 3]
try { a: obj.field?, b: list[0]? } // { a: 1, b: 1 }
}
t: {
a: 1
b?: int
try { aa: a?, bb: b? } else { cc: "missing b" } // cc: "missing b"
}
// for with try: process items conditionally
u: {
items: [{ val: 1 }, { other: 2 }, { val: 3 }]
for i, item in items try { "a\(i)": item.val? } // { "a0": 1, "a2": 3 }
}