Skip to Content
i documentation
The language standardChapters

11. Run-time behavior

11.1 Values

No value may be changed. The values are integers, floats, strings, codepoints, bit arrays, data, lists, values of labelled types, functions, boundaries, continuations, keys, keyed values, requests, tickets, and host references.

11.2 Int

An Int may be of any size. It never overflows.

  • a / b rounds toward zero. a % b has the sign of a, and a == (a / b) * b + a % b when b is not 0.
  • When b is 0, a / b and a % b are 0.

11.3 Float

A Float is a finite IEEE 754 binary64 number. Results are rounded to the nearest value, with ties to even.

  • If the result of an operation is not finite, the program stops.
  • When b is 0.0, a /. b is 0.0.

11.4 Equality

a == b is True when a and b are the same value, and a != b is the opposite. This is the only equality in i. Values are the same in these cases:

  • Ints are the same when their numbers are equal. Floats are the same when they are equal by IEEE 754, so 0.0 == -0.0.

  • Strings are the same when their scalar values are equal. Codepoints are the same when their numbers are equal. Bit arrays are the same when their bits are equal.

  • Data is the same when it has the same shape and its parts under each key are the same. Lists are the same when their parts are the same, in order. Values of labelled types are the same when they have the same constructor and their data is the same. Values with different type constructors, and data of different shapes, are never the same; data and a labelled value are never the same.

  • A function, a boundary, a continuation, and a host reference have no parts that may be looked at, so each is the same only as itself: the value made by one evaluation of a function expression or a chain, by one call of control.boundary or control.suspend, or one reference from the host (§B.4). A top-level function is worked out once (§2.5), so all its uses are the same value. In the same way, a constructor, a built-in function, a library function, and each @external declaration are each one value; two declarations of one import are two values. So two function expressions that are written the same way give values that are not the same.

  • Each call of keep (§9.4) makes a new function, the same only as itself. A key (§9.8) is the same only as itself. Two keyed values are equal when their keys are the same and their values are equal.

  • A request or ticket compares by its underlying request identity, which does not change on answer, take or cancel. Ticket identity is not a fresh value on each call of ticket. Request and Ticket are different type constructors and are not equal to one another.

  • A value stays the same value when the program copies it from one worker to another (§11.9).

11.5 Comparing

< <= > >= compare Ints by number. <. <=. >. >=. compare Floats by IEEE 754.

11.6 Tail calls

A call in tail position uses no more stack. Tail positions are: the last expression of the body of a function expression (§6.8); the body of a branch of a chain that is called; the body of a branch of a match in tail position; the last expression of a block in tail position; the right side of && or || in tail position; and the left operand of a provide in tail position. A provide does not change which calls are in tail position.

So in loop = (0) { Done } | (n) { loop(n - 1) }, the call loop(n - 1) uses no more stack. A match adds no call frame (§6.12).

11.7 Stops

The program stops in these cases:

CaseReason
panic(m) is calledpanic: and m
The Assert of an entry point gets a failure (§9.6)assertion failed: and the message
An error comes to the program entry or a test (§9.3)error: and the text form of the error
control.suspend or control.escape with a boundary that is not active, also through a Fail used after its catch (§9.3, §9.4)control: boundary is not active
A continuation is used a second time (§9.4)control: continuation was already used
A float operation has a result that is not finite (§11.3)float: result is not finite
Making a bit array fails (§8.3)bit array: value is too short, bit array: length is negative, bit array: bytes are not whole, or bit array: float is not finite
A value from the host is not correct (Appendix B)host: value is not correct
There is no more memory or stacknone

A JS bridge call that does not expose exceptions uses panic("js: call threw") for a thrown exception. Malformed raw outcomes, argument builders or typed host replies use host: value is not correct. These stops are not Mismatch errors; explicit checked conversions may return Mismatch. Memory and stack exhaustion remain stops.

Before the program stops, the host gets a stop message, in one call of panic (Appendix B): the reason, then one line for each source position, with a line feed between lines. The first position is that of the innermost expression in the program source that was being worked out: a call, an operator, a <<, or a binding. Then come the positions of the calls in the program source that were running, the innermost first. Calls that were replaced by tail calls are gone, but the first position is always given, also when that expression is a call in tail position: in f = (n) { assert(n > 0) }, the stop shows the position of assert(n > 0). A call of a library function, such as fail.catch or resource.ensure, counts as a tail call when it is in tail position. For an error at an entry point, there is only the position of the entry point’s binding.

A source position is written module path:line:column, for example app/users : 12 : 5, with the module path as its own package names it. For a module of a package with a location, the location, @, the major version, and a space come first: github.com/alice/json@1 json/decode : 12 : 5. Lines and columns count from 1; a column counts Unicode scalar values. The position of an expression is the position of its first token.

A function with no need may still stop for these reasons, and it may never end.

11.8 Text form of values

The text form of a value is given by core.inspect (§13):

ValueText form
IntDecimal digits, with - before a negative number: -42
FloatSee below
String", then the characters, then ". " and \ are written as \" and \\. Line feed, carriage return, tab, and form feed are \n, \r, \t, \f. Other characters from U+0000 to U+001F, U+007F, and U+0080 to U+009F are \u{XXXX}, with four upper-case hex digits. All other characters are written as they are.
List[, the text forms of the parts with , between them, ]
Data(, the text forms of the positional parts in order, then label = and the text form of each labelled part, in the UTF-8 byte order of the labels, with , between them, ). () when empty.
Value of a labelled typeThe constructor name; when its data is not (), then the text form of its data: True, Ok(1), User(age = 36, name = "ada").
BitArray<<, the whole bytes as decimal numbers with , between them, >>. If the number of bits is not a multiple of 8, the last k bits come last, written as value:uk: <<82, 2:u2>>
UtfCodepoint//utfcodepoint(, the character, )
Function//fn(, one name for each key of its shape with , between them, ) { ... }. The names are a, b, … in order: position i, and then the i-th label (counting on after the positions), gets the letter a + (i mod 26), with the decimal number i div 26 after it when i is 26 or more; a label is written label = before its name: //fn(a, port = b) { ... }. A function with several shapes writes the part of each shape group, in the written order of their first branches, joined by |: //fn(a) { ... } | (a, b) { ... }.
Boundary, continuation//boundary, //continuation
Key, keyed value//key, //keyed
Request, ticket//request, //ticket
Host reference//wasm(opaque)

A Float is written in this way. Let d1 … dn be the shortest list of decimal digits, with dn not 0, such that 0.d1…dn × 10^k gives back the same float when read. When more than one list of that length does this, take the one nearest to the exact value of the float; when two are equally near, take the one whose last digit is even. -. comes first for a negative float, including negative zero, whose text is -.0.0. Positive zero is 0.0. The sign is the Float unary operator of §6.9, so the text can be read as a Float expression; it is not part of the decimal digits. Let p = k − n.

  • The long form is used when p is at least L and at most U, and not when p ≥ 0 and the size of the float is 2^53 or more. L is −4 when n = 1, and −2 − n when not. U is 2 when n = 1 or k ≥ 11, and 1 when not.
  • In the long form, the digits are written as a decimal number, with . and at least one digit after it: 123.0, 12.67, 0.0123.
  • In the short form, d1 is written, then ., then d2 … dn, or 0 when n = 1, then e, then k − 1 as a decimal number with - when it is negative: 3.0e26, 1.0e-7, 1.0e3.

The library module i/float_text writes this text in i: float_text.to_string(x) is the text form of the Float x, the same as core.inspect(x).

The text form of a function, a key or a keyed value never depends on which worker made it or holds it. Task handles keep bookkeeping ids under keyed fields, whose text is //keyed; only task.path exposes the semantic task path. Requests and tickets show no ids, state or answers.

11.9 Workers

A program may run on one or more workers at the same time. Which worker does which part of the work cannot be seen. Tasks that run at the same time take their steps (sends and receives, joins, host calls, failures and stops) one after another, in an order that the program does not fix; this order may differ from run to run and with the number of workers. Every run gives a result that some order of the steps gives, and each step does what it would do if the steps were taken one at a time in that order. The number of workers only changes which order happens. So anything that depends on the order of steps may change, for example which sender a receive pairs with, which event of a choose commits, whether a step comes before or after the end of a scope, which tasks fail before a cancel reaches them, which of two stops is first, and the order of output. A program whose results do not depend on the order of its steps gives one result. Running out of memory or stack (§11.7) is not covered by this rule. The host chooses the number of workers, subject to the top-level rule below.

Top-level initialization happens once semantically. If any initialized top-level value is not portable, the program runs on one worker. Otherwise an implementation may repeat initialization on workers only when every worker obtains equal values and the repetition has no observable effect. A portable function can refer to a nonportable top-level value without capturing it; that value still forces the program to use one worker.

The captures of a function made by a function expression are the local names that its body uses and that are bound outside it, after macro expansion; a name counts when it is written in the body, also in a part that never runs. Top-level names are not captures. A function made by keep has the captures of its argument and the services selected by §9.4; task captures use the substitutions of §9.9. Other functions have no captures.

The values that a value holds are: the parts of data, of a list, or of the data of a labelled value (§11.4); the values of the captures of a function; and the value in a Keyed (§9.8). == does not look at what a function holds (§11.4). A value is portable when it is not a boundary, a continuation, a host reference, a request, a ticket or a Syntax value (§14.4), and every value it holds is portable. A value may hold itself, through a local function that calls itself; only the other values it holds then count.

A value that is not portable never goes to another worker. Values may be copied between workers; a copy is the same value (§11.4).