vision
Install

Language reference

Effects & failure

Vision makes recoverable failure part of the line that can fail. Optional values use a separate, path-sensitive presence check, and world contact stays visible through the >> marker.

Every rule below describes the current Vision compiler. For the shorter safety model, read Safe by design. The values reference defines which values may be absent.

Failure clauses

A fallible statement is followed by one or more indented | clauses. A tagged clause has the form | tag: resolution. A bare clause has the form | resolution and catches the statement's entire failure signature. Clauses run only when their statement reports the matching failure. Success skips the block and continues with the next statement.

A clause may print one value before resolving: | missing: say "no file" and stop. It is not a general statement block. The only clause body is the optional say expression and prefix followed by exactly one resolution.

Every intrinsic failure kind must be covered. Missing coverage is a compile error that names the uncaught kind, and an unknown tag is also rejected. Two tagged clauses for the same single kind are rejected as duplicates.

Current limitation: the checker does not consistently reject extra catches. A known tag that the operation cannot raise can be accepted, a clause on an infallible user call can be silently discarded, and a catch-all may overlap a tagged clause without a duplicate error. Write only the operation's real tags, and do not mix a bare catch-all with tagged clauses.

Failure tags

The complete tag set is missing, unreadable, unreachable, unwritable, failed, malformed, unrunnable, closed, unstartable, ended, unwaitable, noproc, unsignallable, timeout, channelclosed, and badtext. unopenable is an accepted alias for failed; it is not a separate failure kind.

File and collection operations use missing, unreadable, or unwritable. Network fetches use unreachable. C faces, number conversion, base64 decoding, database work, and JSON shape conversion use failed; invalid JSON text is separately malformed. Process, connection, signal, channel, and byte-to-text operations use the remaining specific tags. Their reference pages name the exact signature of each operation.

timeout is opt-in rather than part of an operation's intrinsic signature. Only operations with a bounded form accept | timeout N milliseconds:, | timeout N seconds:, or | timeout N minutes:. The positive integer is a compile-time duration, and only run, ask, process wait for, and connection read exactly accept it. A timeout must be handled locally; it cannot use pass. | any: and but if the channel is closed, resolution are specialized concurrency catches, not general clauses that can be attached to any statement.

Resolve, propagate, or exit

stop handles the failure and normally ends the containing verb. In to begin:, that is a successful program exit. A non-fallible verb that answers a value rejects stop because the stopped path has no answer. Inside a parallel-each worker, a general strand, or a server connection handler, stop ends only that worker, strand, or connection.

pass propagates the matched kind. That kind becomes part of the containing verb's inferred failure signature, so every caller must cover it in turn. pass is illegal in to begin:, top-level test blocks, and the shutdown handler because none has a caller. A strand is the exception: its caller is the enclosing all at once: block.

give up exits the whole process immediately with status 70. It does not run Vision's live-scope cleanup before calling the host exit function. Use it for a deliberately fatal path, not ordinary recovery or secret-bearing cleanup.

Current limitation: a fallible value-returning verb can pass one failure kind and use stop for another without a checker error. That stopped path lowers to the generic failed code even though failed is absent from the verb's inferred signature. A caller can then fall through with a zero result. Avoid stop inside every value-returning verb; use pass or give up, and let the caller stop if needed.

The live propagation spelling is pass. pass it on is parsed only to produce a retirement diagnostic, and hand it on is not Vision syntax. Some extended diagnostic examples and editor completion data still show the retired phrase; follow the parser and use pass.

reference-effects-failure.viscompiler checked
to parse measure(source: text), returns decimal:
    source as a number returning parsed
        | failed: pass
    answer parsed

to parse strictly(source: text), returns decimal:
    source as a number returning parsed
        | failed: give up
    answer parsed

to begin:
    parse measure("12.5") as measure
        | failed: say "could not parse the measure" and stop
    say "measure: {measure}"

    the checked is parse strictly("3.5")
    say "checked: {checked}"

    parse measure("not a number") as unused
        | failed: say "invalid input" and stop
    say "unreachable: {unused}"
to parse measure(source: text), returns decimal:
    source as a number returning parsed
        | failed: pass
    answer parsed

to parse strictly(source: text), returns decimal:
    source as a number returning parsed
        | failed: give up
    answer parsed

to begin:
    parse measure("12.5") as measure
        | failed: say "could not parse the measure" and stop
    say "measure: {measure}"

    the checked is parse strictly("3.5")
    say "checked: {checked}"

    parse measure("not a number") as unused
        | failed: say "invalid input" and stop
    say "unreachable: {unused}"
The first verb passes a failed conversion to its caller; the later invalid call prints its message and stops begin. The second verb contains a dormant give up resolution and succeeds here.

Fallible results

A verb may both pass failures and answer a value. A statement-position call receives that value with verb(arguments) returning name or verb(arguments) as name, followed by a complete failure block. The as form always creates a fresh name. Its result exists only on the success path: a failure-clause message cannot read it, and code after the statement sees it only after every failure path has terminated or propagated.

A fallible call cannot appear as an ordinary expression, interpolation hole, or branch operand because those positions have nowhere to attach the required clauses. Put the call in statement position, bind its result, then use the bound name.

Absence checks

when there is place: opens a true or present-only block for a supported stable local or field. It has no else arm. The condition forms place is something and place is nothing work in ordinary when/else when/else chains. is not nothing is retired; use is something.

For nullable text, enums, and supported optional aggregate fields, these checks inspect presence rather than emptiness or numeric value. A present empty string or zero-valued enum is still present. Yes/no is the exception: is something means yes and is nothing means no; it is a truth-value test, not an optional-state test.

Current limitation: the checker rejects presence tests on whole numbers, decimals, bytes, sets, callables, and unsupported aggregates even when a field was declared or nothing. In particular, an optional numeric field cannot currently be narrowed with when there is, is something, or is nothing.

E1 presence facts

The compiler records present or absent for a stable local or member path. The true branch of is something proves presence; its false branch proves absence. is nothing does the reverse. not swaps those facts. Proving an optional parent also permits reads of its required descendants.

Facts stay inside the control-flow path that established them. An and true path carries the proofs from both operands. An or true path keeps only facts common to either way the condition could succeed. At a branch join, a fact survives only when every incoming path that continues has the same fact. This is why a read after one present branch and one absent branch is rejected, while a value assigned on both branches is readable afterward.

when there is joins its present body with the path that skipped the body, so a newly learned proof does not escape the block. Loop bodies are checked to a fixed point: writes feed the next iteration, and only the false-condition facts are available after a while exits. Facts learned inside a parallel worker never join the parent strand.

Assignment invalidates the old fact for the written place. Replacing a described thing invalidates facts about its fields; writing an optional field installs the new value's present or absent fact. Passing a value to a changing parameter invalidates facts reachable through that alias. Test again after any such mutation before reading an optional value.

reference-effects-presence.viscompiler checked
kind profile:
    name: text
    note: text or nothing
    alias: text or nothing

to begin:
    the reader is a profile:
        name is "June"
        note is "ready"
        alias is "Sol"

    when there is reader.note:
        say "there is: {reader.note}"

    when reader.note is something and reader.alias is something:
        say "both: {reader.note} / {reader.alias}"

    when reader.note is nothing:
        say "nothing"
    else:
        say "else proves: {reader.note}"

    when reader.note is something:
        reader.note is nothing
        when reader.note is nothing:
            say "reassignment: cleared"

    the choose blue is yes
    when the choose blue:
        reader.note is "blue"
    else:
        reader.note is "rose"
    say "joined: {reader.note}"
kind profile:
    name: text
    note: text or nothing
    alias: text or nothing

to begin:
    the reader is a profile:
        name is "June"
        note is "ready"
        alias is "Sol"

    when there is reader.note:
        say "there is: {reader.note}"

    when reader.note is something and reader.alias is something:
        say "both: {reader.note} / {reader.alias}"

    when reader.note is nothing:
        say "nothing"
    else:
        say "else proves: {reader.note}"

    when reader.note is something:
        reader.note is nothing
        when reader.note is nothing:
            say "reassignment: cleared"

    the choose blue is yes
    when the choose blue:
        reader.note is "blue"
    else:
        reader.note is "rose"
    say "joined: {reader.note}"
The fixture checks all three live presence forms, an and-condition that proves two fields, reassignment to nothing, and a join where both continuing branches install a present value.

Effects and irreversible work

Failure and world contact are separate properties. The text-to-number conversion in the fixture is fallible but pure, so it needs clauses and no >>. A world-touching operation normally needs >> on its statement, and a verb that reaches one needs >> on its declaration. Direct say is the deliberate line-marker exception. It needs no marker of its own, though a clock, environment read, or other world-touch used to produce what it says still needs >>. The compiler checks both missing and unnecessary markers.

!! marks the small set of irreversible world-touching statements. It does not catch failure and does not replace a | block. A recoverable world operation may need >> and failure clauses but reject !!; an irreversible fallible operation needs all three parts.

Ownership and cleanup

stop and pass clean resources already live in the current Vision scope before returning. A failure-clause interpolation also frees its temporary text after printing. give up is different: it exits immediately without that language-level cleanup.

A heap-owning result bound by a successful fallible call is caller-owned and follows the ordinary return-boundary rules. The result slot is not available on a failure path. Presence checks borrow the value they inspect; they neither clone nor transfer it, and reassignment follows the destination's normal ownership rules.