Parallel Loops​

parallel_for, parallel_transform and parallel_reduce run a loop over an array on every core. The engine checks the body when the statement is reached, before any element runs. A body it cannot prove safe is refused with a catchable error that names the construct. It is never run serially in silence.

The model​

  1. An iteration owns its element. The loop variables are the elements: auto& changes one in place, a by-value variable is the body's copy. Every element is a value, or a handle to an object no other element names.
  2. Everything outside the loop is captured and read-only. The body reads enclosing state as it was when the statement was reached. A write to it is refused.
  3. Results leave through writes that cannot collide: the element, the same position of a zipped array, out[i] where i is a loop variable and every element carries a different index, the array parallel_transform returns, or a parallel_reduce accumulator.
  4. The rules follow the calls. They cover the functions the body calls, the methods it runs and the constructors and destructors of the objects it makes. An object the body makes belongs to its iteration.
  5. The worker count never changes the result. thread_count() reports it: the count the host set, else the machine's performance cores, at most one fewer than its hardware threads.

With deterministic inputs, the elements, the index writes and the error (the first failing element in iteration order) are the serial loop's, on any number of workers. Four things are not the serial loop's:

  • A captured random gives the same numbers at every worker count, but not the numbers a serial loop would draw (Random numbers).
  • crypto:: calls, and a random the body makes without a seed, differ on every run, as they do anywhere.
  • parallel_reduce folds by chunk. The result is bit-identical at every worker count, but a float fold is grouped differently from a left-to-right loop, and a non-identity init is counted once per chunk (Reductions).
  • After an error, index writes hold up to the failing element, as in the serial loop, but an element changed in place past it may or may not have been written.

A read that would depend on scheduling, such as time::now(), is refused.

parallel_for and parallel_transform​

parallel_for runs its body once per element. parallel_transform(arr, fn) returns a new array with out[i] = fn(arr[i]).

jaiscript
var nums = [1, 2, 3, 4, 5, 6, 7, 8];
parallel_for (auto& x : nums) { x = x * x; }           // auto& is the element itself
print(to_json(nums));

int inc(int v) { return v + 1; }
print(to_json(parallel_transform(nums, inc)));         // a new array; nums is not touched
output
[1,4,9,16,25,36,49,64]
[2,5,10,17,26,37,50,65]

An element may be an instance of a class whose fields hold values. Math vectors are values (Math): an array of them, a class holding them and a captured one all qualify.

jaiscript
using math::vec2;
class Particle { vec2 pos; vec2 vel = vec2(0.0, 2.0); }
vec2 gravity = vec2(0.0, -0.5);
array<Particle> sparks;
for (int i = 0; i < 4; ++i) {
    Particle p;
    p.vel.x = i;
    sparks.push(p);
}
parallel_for (auto& p : sparks) {
    p.vel += gravity;             // gravity is captured: read, never written
    p.pos += p.vel;               // p is this iteration's element
}
print(sparks[0].pos, " ", sparks[3].pos);
output
(0.0, 1.5) (3.0, 1.5)

Counted loops​

parallel_for (int i = a; i < b; i++) runs its body once for each value the serial for gives i, with no array made and no memory per element. The form is kept to what provably visits each value once:

  • one int (or auto) variable, never written by the body
  • compared with a bound by <, <=, > or >=
  • stepped toward the bound by i++, i--, i += k or i -= k, with k a constant or a name.

a, b and the step are evaluated once, in that order, before any element runs. Any other shape is refused when the script loads. A step that would never end the serial loop, or a last step that would overflow, is refused when the statement is reached.

jaiscript
int n = 8;
array<int> squares = [0, 0, 0, 0, 0, 0, 0, 0];
parallel_for (int i = 0; i < n; i++) { squares[i] = i * i; }
array<int> marks = [0, 0, 0, 0, 0, 0, 0, 0];
parallel_for (int i = n - 2; i >= 0; i -= 2) { marks[i] = 1; }
print(`${to_json(squares)} ${to_json(marks)}`);
int none = 0;
try { parallel_for (int i = 0; i < n; i += none) { } } catch (e) { print(e); }
output
[0,1,4,9,16,25,36,49] [1,0,1,0,1,0,1,0]
parallel_for: a counted loop's step of 0 never reaches its bound (the serial loop would not end)

The zip form​

The zip form walks several arrays of one length in lockstep, one loop variable per array. To produce several values per element, allocate the outputs before the loop and write them in place. Every zipped array is a separate array of the range's length.

jaiscript
class Body { float x = 0.0; float vx = 0.0; Body() {} Body(float px, float v) { x = px; vx = v; } }
class Sample { float pos = 0.0; bool moving = false; }
array<Body> bodies = [];
array<Sample> samples = [];
for (int i = 0; i < 4; i++) { bodies.push(Body(i * 10.0, i % 3 - 1.0)); samples.push(Sample()); }

parallel_for (auto& b, auto& s : bodies, samples) {
    b.x += b.vx * 0.5;            // the input, changed in place
    s.pos = b.x;                  // the output slot at the same position
    s.moving = b.vx != 0.0;
}
for (auto& s : samples) { print(`${s.pos:.1f} ${s.moving}`); }
output
-0.5 true
10.0 false
20.5 true
29.5 true

parallel_transform is that zip​

parallel_transform(xs, fn) is parallel_for (auto& x, auto& o : xs, out) { o = fn(x); } over a fresh out of xs.size() slots.

  • fn is a script function or lambda that takes its element by value. One that only reads its parameter costs no call and no copy per element. One that changes its parameter changes a private copy, so the source array is never touched.
  • Any result type works, objects included. The returned array holds copies that alias nothing.
  • An optional third argument, weight_fn, balances the chunks by effort per element. It takes one parameter by value and returns a finite, non-negative number.
jaiscript
class Body { float x = 0.0; float vx = 0.0; Body() {} Body(float px, float v) { x = px; vx = v; } }
array<Body> bodies = [];
for (int i = 0; i < 4; i++) { bodies.push(Body(i * 10.0, 1.0)); }

float next_x(Body b) { return b.x + b.vx; }
Body stepped(Body b) { b.x += b.vx; return b; }          // changes its own copy

var xs = parallel_transform(bodies, next_x);
var moved = parallel_transform(bodies, stepped);
print(`${to_json(xs)} moved ${moved[3].x:.1f} source ${bodies[3].x:.1f}`);

var texts = ["7", "42", "1999"];
int digits(string s) { return s.size(); }
float cost(string s) { return s.size(); }                // longer strings weigh more
print(to_json(parallel_transform(texts, digits, cost)));
output
[1.0,11.0,21.0,31.0] moved 31.0 source 30.0
[1,2,4]

Control flow in the body​

continue, and a return at the body's top level, move to the next element. A loop-level break is a parse error, since iterations are independent. Inside a nested loop or switch, break and continue keep their usual meaning, and try/catch works. An element that throws fails the whole call with the first error in iteration order.

jaiscript
var words = ["keep", "", "skip", "keep", "", "keep"];
array<int> kept = [0, 0, 0, 0, 0, 0];
parallel_for (auto& w, auto& k : words, kept) {
    if (w == "") { continue; }
    if (w == "skip") { return; }      // the next element, like continue
    k = w.size();
}
print(to_json(kept));
output
[4,0,0,4,0,4]

Reductions: parallel_reduce​

parallel_reduce(xs, init, step, combine) folds an array on every core:

  1. xs splits into chunks whose size depends on xs.size() alone, never on the worker count.
  2. Each chunk folds its elements in order with step(acc, x), starting from its own copy of init.
  3. The chunk results combine left to right with combine(a, b). An empty array returns init.

init must be combine's identity (0 for a sum, 1 for a product, an empty container for a merge). Every chunk starts from it, so any other value is counted once per chunk: the last line of the sample sums 100 ones from 100.0 and prints 800.0, not 200.0.

  • step is a script function or lambda of two parameters and follows the body rules. It takes its element by value. The input may hold class instances, each its own object.
  • A scalar accumulator suits the by-value form T step(T acc, x). A container or object accumulator is taken by reference (void step(T& acc, x), or -> T& returning acc) and folds in place with no copy in or out.
  • combine is ordinary code of two parameters. It runs on the calling thread after the chunks join, so it may print or write enclosing state.
jaiscript
array<float> prices = [4.5, 12.0, 7.25, 3.0, 9.5];
float add(float a, float b) { return a + b; }
print(`total ${parallel_reduce(prices, 0.0, add, add):.2f}`);

class Span { float lo = 1000000.0; float hi = -1000000.0; }
void widen(Span& r, float x) { if (x < r.lo) { r.lo = x; } if (x > r.hi) { r.hi = x; } }   // folds in place
Span join(Span a, Span b) { if (b.lo < a.lo) { a.lo = b.lo; } if (b.hi > a.hi) { a.hi = b.hi; } return a; }
Span r = parallel_reduce(prices, Span(), widen, join);
float big = parallel_reduce(prices, 0.0, [](float a, float x) { return x > 5.0 ? a + x : a; }, add);
print(`span ${r.lo:.2f}..${r.hi:.2f} big ${big:.2f}`);

array<float> ones = [];
for (int i = 0; i < 100; i++) { ones.push(1.0); }
print(parallel_reduce(ones, 0.0, add, add), " ", parallel_reduce(ones, 100.0, add, add));   // 100.0 counted once per chunk
output
total 36.25
span 3.00..12.00 big 28.75
100.0 800.0

Captured reads​

The body reads enclosing scalars, strings, math vectors, arrays and maps, and runs the read-only builtins on them. Every worker sees their content as of the statement.

  • A container of primitives or strings that the body only subscripts, or asks size(), length() or empty(), is read in place with no copy. Anything else captured is copied once per worker, and the copies count toward the memory cap.
  • A function or method the body calls takes enclosing state through a const T& parameter as a captured read. A plain T& parameter could write it and is refused; when the function never writes through it, the error names the const T& to declare.
  • The lambda given to parallel_transform or parallel_reduce reads its captures the same way.

Writes to enclosing state​

Every write to captured state is refused: assignment, compound assignment, ++/--, a mutating or unrecognized method (g.push(...)), a non-const reference that aliases it (var& r = g), an argument to a non-const reference parameter (directly or as an arm of c ? a : b), and for (auto& v : g). A const T& declaration or a const auto& loop reads it.

Index writes​

The one exception is out[i] = v (and op=, ++, --) into an enclosing array:

  • i is a loop variable the body never writes, directly or through a T& parameter.
  • Every element carries a different index, which the statement checks when it is reached. An index past the array raises the serial loop's error.
  • The body touches out only as out[i]. Reading another slot would race the writes.
jaiscript
array<int> heights = [3, 9, 4, 7, 1, 8];
int peak(const array<int>& h, int i) {          // const T&: a captured read, no copy
    int best = h[i];
    if (i > 0 && h[i - 1] > best) { best = h[i - 1]; }
    if (i + 1 < h.size() && h[i + 1] > best) { best = h[i + 1]; }
    return best;
}
int tally(array<int>& h) { return h.size(); }   // never writes h, but does not say so
array<int> peaks = [0, 0, 0, 0, 0, 0];
parallel_for (int i = 0; i < 6; i++) { peaks[i] = peak(heights, i); }
print(to_json(peaks));
try { parallel_for (int i = 0; i < 6; i++) { peaks[i] = tally(heights); } } catch (e) { print(e); }
int total = 0;
try { parallel_for (int h : heights) { total += h; } } catch (e) { print(e); }
array<int> order = [3, 3, 1, 0];
try { parallel_for (int slot : order) { peaks[slot] = 1; } } catch (e) { print(e); }
output
[9,9,9,7,8,8]
parallel_for: argument passes enclosing state 'heights' to reference parameter 'h' of 'tally' (captured reads are read-only; 'tally' only reads it: declare it const array<int>& h) at 12:63
parallel_for: body writes enclosing state 'total' at 14:40
parallel_for: elements 0 and 1 write 'peaks' at the same index 3

Captured objects​

A captured object is read through its fields and methods and written by no iteration.

CapturedWhat the body may do
A value-class instance (fields of primitives, strings, containers of them and nested value-class instances), or a shared_ptr to oneRead its fields; call a script method that writes no part of the object, which the statement checks before any element runs. Each worker reads its own copy.
An array or map of value-class instancesThe same, per entry: ITEMS[i].cost, for (auto it : ITEMS), ITEMS[i].price().
A C++ class's instanceEvery worker reaches the one C++ object, so its binding decides: const methods run unless the host registered them serial (C++ calls).
A handleUsed through its members and methods only.
RefusedAn object holding a handle, a function, a var field or a C++ object; one whose class runs a destructor; a script class over a C++ class; a weak_ptr; a shared_ptr, weak_ptr or C++ object inside a captured container; a range whose elements reach a captured object.
jaiscript
class Vec3 {
    float x = 0.0; float y = 0.0; float z = 0.0;
    Vec3(float a, float b, float c) { x = a; y = b; z = c; }
    float dot(Vec3 o) { return x * o.x + y * o.y + z * o.z; }   // writes nothing of its object
    void scale(float k) { x *= k; y *= k; z *= k; }
}
Vec3 sun = Vec3(0.0, 1.0, 0.0);
array<float> light = [0.0, 0.0, 0.0, 0.0];
parallel_for (int i = 0; i < 4; i++) { light[i] = sun.dot(Vec3(i * 1.0, i * 0.5, 0.0)); }
print(to_json(light));
try { parallel_for (int i = 0; i < 4; i++) { sun.scale(2.0); } } catch (e) { print(e); }
try { parallel_for (int i = 0; i < 4; i++) { sun.x = 1.0; } } catch (e) { print(e); }
output
[0.0,0.5,1.0,1.5]
parallel_for: method 'scale' of 'Vec3' writes its object, and captured 'sun' is read-only (write a local copy, or the element) at 11:50
parallel_for: member store reaches captured state 'sun' (captured reads are read-only) at 12:50

Calling functions​

A body calls named script functions (recursion included), the builtin container and string methods on its own values, and host functions the host did not register serial. The target of a call must be known when the statement is reached:

  • A global name that holds a function or a lambda is called directly.
  • A call through a local of the enclosing function, a local of the body or a parameter is refused.
  • A function read as a value, to store it or to pass it, is refused. A body writes no lambda of its own.
jaiscript
int twice(int v) { return v * 2; }
var bump = [](int v) { return v + 1; };      // a global that holds a lambda
array<int> xs = [1, 2, 3, 4];
parallel_for (auto& x : xs) { x = bump(twice(x)); }
print(to_json(xs));

int apply(var f, int v) { return f(v); }
void local_target() {
    var pick = twice;
    try { parallel_for (auto& x : xs) { x = pick(x); } } catch (e) { print(e); }
}
local_target();
try { parallel_for (auto& x : xs) { x = apply(twice, x); } } catch (e) { print(e); }
try { parallel_for (auto& x : xs) { var keep = twice; } } catch (e) { print(e); }
try { parallel_for (auto& x : xs) { print(x); } } catch (e) { print(e); }
output
[3,5,7,9]
parallel_for: call target 'pick' is not a function known at admission time at 10:45
parallel_for: call target 'f' is not statically resolvable in a parallel body at 7:34
parallel_for: captured name 'twice' is a function value (only direct calls to statically-known functions are allowed in a parallel body)
parallel_for: host function 'print' is marked serial_function() and cannot be called in a parallel body at 15:37

C++ calls​

Bound C++ functions, static methods and methods run on the worker unless the host registered them serial_function, serial_static_method or serial_method (the binder).

  • A serial function or static method refuses the statement before any element runs. A serial method is refused when it is called, since its receiver's class is known only then. The mark covers the method's non-const overloads, so a const getter of the same name stays parallel.
  • A non-const C++ method, or a store to a C++ property, runs only on an object the body owns: its element, a parallel_reduce accumulator, or an object it made. On any other object only const methods run.
  • The library's builtins follow the same rule. The table lists the serial ones; print is among them.

Constructing objects​

A body constructs script classes in every spelling: Hit(r, d), Hit h(r, d);, Hit h;, new Hit(r, d), and the converting constructor a typed slot runs (Box b = x;, an argument, a return, a store). The new object belongs to the worker that made it, so its constructor writes its fields freely. Everything else the construction runs follows the body rules, and the statement checks it before any element runs:

  • every constructor the call's argument count admits
  • the base constructors its initializer list names (an unnamed base is default-constructed)
  • each level's field initializers
  • the destructor of every class whose instance can die in the body: an object the body makes, an element it replaces, and whatever their fields hold.

An error from this code names where it sits: a constructor, a field initializer or a destructor.

jaiscript
string PREFIX = "hit-";
class Vec { float x = 0.0; float y = 0.0; Vec() {} Vec(float a, float b) { x = a; y = b; } }
class Hit {
    int ray = -1;
    Vec at;
    string tag = PREFIX;
    Hit() {}
    Hit(int r, float d) { ray = r; at = Vec(d, d * 2.0); tag = tag + to_string(r); }
}
array<Hit> hits = [Hit(), Hit(), Hit(), Hit()];
parallel_for (int r = 0; r < 4; r++) { hits[r] = Hit(r, r * 0.5); }
print("{} {} {}", hits[3].tag, hits[3].at.y, hits[0].ray);
output
hit-3 3.0 0

C++ classes and hybrids​

A class the host binds, and a script class over one, construct in a body too: the host's constructor and destructor are taken to be safe on any thread. A host class whose constructor or destructor touches shared state (a registry, a cache, a service) is bound with .serial_only_constructor() (the binder), and a body that constructs it, or a script class over it, is refused before any element runs. An object of such a class that dies on a worker anyway is released on the calling thread when the workers are done, in element order.

Random numbers​

The generator isWhat a draw gives
Captured from outside the loopThe statement takes one draw from it to seed a stream per element (per chunk for parallel_reduce). A stream depends only on that draw and the element's index: the same numbers at every worker count, not the numbers a serial loop would draw. After the loop the generator has moved one draw. Reseeding it in the body is refused.
Owned by the element (an array<random> zipped beside the data, or a field of the element's class)Its own sequence, as anywhere. Two elements holding one generator are refused.
Made in the bodyIts own sequence: reproducible when the body seeds it, different on every run when it does not.

crypto:: runs in a body too, apart from crypto::sha256_file, and is never reproducible. Random Numbers & Crypto has the generator's side of these rules.

jaiscript
random loot(5);
array<int> drops = [0, 0, 0, 0, 0, 0, 0, 0];
parallel_for (auto& d : drops) { d = loot.next_int(1, 100); }   // captured: one stream per element
print(drops.join(" "));
print(loot.next_int(1, 100));                                     // loot moved one draw for the loop

array<random> brains = [random(1), random(2), random(3)];        // owned: one generator per element
array<int> hp = [10, 10, 10];
parallel_for (auto& h, auto& brain : hp, brains) { h -= brain.next_int(1, 6); }
print(hp.join(" "));

try { parallel_for (auto& d : drops) { loot.seed(d); } } catch (e) { print(e); }
output
51 17 31 91 93 79 47 67
61
5 9 5
parallel_for: random::seed cannot be called on captured 'loot' in a parallel body (seed before the loop: each element draws its own stream from it) at 12:45

Handles as elements​

An array of shared_ptr<T> or weak_ptr<T> elements runs when no object appears at two elements of the range and its zipped arrays, which the statement checks when it is reached. Its class must still be a flat value class. Another holder of the object elsewhere in the program is fine, since nothing else runs during the loop.

Choosing a shape​

Use captured reads for shared inputs, a zipped output array or out[i] for per-element results, and parallel_reduce for a total. Where the output shape is not known up front, elements shaped [input..., tag] come back from parallel_transform as [tag, outputs...], collected from the result in iteration order.

What a parallel body may do​

One row per construct. The reference lists every refusal text with its cause (Parallel bodies); the last column links each row to its texts there.

  • A text that starts with parallel_for: is raised when the statement is reached, before any element runs. parallel_transform and parallel_reduce raise the same texts under their own names.
  • A text without the prefix is raised when the body runs the construct, because only then is the receiver known.
  • Most texts end with the position of the construct, at LINE:COL.
In a bodyRuleRefused with
ElementsA primitive, a string, a math vector, a value container, an instance of a flat value class (typed fields holding values, vectors included, no handles), or a handle to an object no other element names. A bound C++ value such as a random counts when no other element holds the same object. A reference to a member of the element or of a local (auto& p = b.pos;), and a by-reference loop over a map they hold (for (auto& [k, v] : b.m)), write it as the serial loop does.not value-semantic
Handle elementsNo object at two elements of the range and its zipped arrays (Handles as elements).reference
The range and zipped arraysThe range is an array. Every zipped array is a separate array of the range's length.range not an array, zipped not an array, zipped length, zipped range
Counted loopsOne int variable the body never writes, compared with a bound, stepped toward it by a fixed amount (Counted loops).reference
Index writesout[i] into an enclosing array, i an unwritten loop variable, a different index per element, out touched no other way (Index writes).reference
Reductionsstep and combine are script functions or lambdas of two parameters; step follows the body rules and takes its element by value; each input element is its own object (Reductions).reference
parallel_transform's functionsfn takes its element by value; weight_fn takes one parameter by value and returns a finite, non-negative number (above).fn not a script function, fn by reference, weight parameter, weight not finite
Captured readsAllowed (Captured reads). A captured vector's components read by name (gravity.y, table[1].x). The read-only builtins run: size, empty, front, back, index_of, has, contains, first, last, length, slice, join, keys, values, get, to_array, at, substr and the find family. A subscript read of the range's own array would race the element writes, and so would a const T& of it or of an index-written array.mutating method, read of the range
Writes to enclosing stateRefused in every shape but the index write (Writes to enclosing state). A member or element of enclosing state is that state, and so is a default a call leaves to bind to a reference parameter.enclosing write, by-reference loop
Captured objectsRead only; no element may reach one (Captured objects). Every other object an iteration writes is its element or one it made. this exists only inside a method the body runs.mutating method, captured weak_ptr
Constructing script classesAllowed in every spelling; all the code a construction can run follows the body rules (Constructing objects). A conversion the statement could not see is refused when it runs, never run unchecked.reference
Constructing C++ classesAllowed, a script class over one included, unless the host bound it with serial_only_constructor() (C++ classes and hybrids).reference
Script class methodsA method runs when it is the only overload of its name on the element's class or the base it inherits it from, the class has no private members and the method body follows the body rules. Inside it a sibling method is called as this.name() or by its bare name; a C++ base method keeps the this. spelling. A method of an object reached through a field is not admitted.private members, bare base call
OperatorsA class's operators run on the worker: arithmetic, comparison and <=> (reversed forms included), unary - + ~, compound assignment, [], operator=, and the conversions into a typed slot (to_string, to_bool, to_int, to_float, to_char). A script operator's overloads and what they construct follow the body rules; one that breaks a rule is refused when it runs. A C++ operator follows the host-call rules, so += and the other non-const ones run only on an object the body owns.reference
Enums and class constantsE::v reads an enum value and C::K a static const of a number, bool or char, in the body or a method it runs.reference
Accessors, class changesA user getter or setter, and an assignment that changes an object's class, are refused when they run.reference
Script function callsA direct call to a named function (recursion included) or to a global that holds a function or lambda, and the builtin container and string methods on the body's own values. Refused: a call through a local or a parameter, and a function read as a value (Calling functions).enclosing local, parameter or body local, function value
Host callsParallel unless registered serial; non-const methods and property stores only on an object the body owns (C++ calls). The library's serial builtins: print, sleep, extend, a nested parallel_transform or parallel_reduce, the archives (to_json, from_json, to_binary, from_binary, to_base64, from_base64), the file and folder lookups (resolve_path, file_data, app_data_dir, app_cache_dir, import_recheck_interval), the namespace forms that call back into script (array::map, array::filter, array::sort, map::filter), crypto::sha256_file, HTML template rendering, and every reflect::, time:: and spider:: entry. A namespaced host function follows the same rule (string::repeat runs). A static method of a script class is refused.serial function, script static method
Objects from host callsAn object a host call returns that the program already holds (a C++ object's wrapper or script layer) is shared. The body reads its fields, each read a copy; an object a field holds is shared too, and its C++ class's const methods run. A store into it and a script method on it are refused when they run. ==, a conversion into a typed slot, a condition and its text read the worker's own copy, on which its class's operators and conversions run and which never runs a destructor. Kept in the element, in out[i] or in the result, it is the object itself after the loop.reference
Signalsconnect and disconnect on a Signal are serial methods. A C++ call that fires a signal runs its receivers on the worker that made the call, so a host marks such a method serial_method when its receivers reach shared state (Signals).reference
LambdasA body writes no lambda. fn, step and combine may be lambdas.lambda
yieldA body neither yields nor calls a coroutine method.yield, coroutine method
time::The clock is engine-owned, and a read would depend on scheduling (Time and parallel loops).reference
Random numbersDraws are allowed; reseeding a captured random is not (Random numbers).reference
Nested parallelismA body, and every function it calls, runs no parallel loop of its own.reference
Declarations and modulesA body declares no function, class, namespace or enum, and includes or imports nothing.declaration, include/import
Control flowcontinue and a top-level return move to the next element; a loop-level break is a parse error (Control flow).loop-level break
Reference castscast<T&> would bind a reference the proof cannot follow.reference cast
Errors and limitsThe first error in iteration order fails the call. The execution budget and the memory cap hold inside a body (Safety Rails): a budget overrun is terminal as anywhere, and copies made for the workers count toward the memory cap.the element's own error