torb run

torb run runs a file, the only program below a directory or the program named in the bytecode VM, or builds and runs it natively with --native, passing the rest of the command line, the three streams and the exit code through.

Diese Seite gibt es noch nicht auf Deutsch, deshalb steht sie hier auf Englisch. Zur englischen Seite

run runs a program at once, in the bytecode VM inside torb, which needs no C compiler. --native is build plus starting what came out. The two are one language: the conformance suite holds every program to the same output, exit code and panics in both, so a program cannot behave one way under run and another way when it is shipped.

Synopsis#

torb run [--vm | --native] [--profile dev|release] [path | name] [arguments]   Run a file or a program
    --native                In front of the path: build it natively and run the binary
    --profile dev|release   In front of the path: build it natively, this hard does the C compiler optimize;
                            with --vm, run it in the VM in that profile
    --release               The same as --profile release
    --vm                    The default, accepted: run it in the bytecode VM, whatever profile is named
    --timings               In front of the path: what every step up to the first instruction took, on standard error

What it does#

What it runs#

An argument that contains / or \, ends in .trb, or is . or .. is a path; anything else is the name of a program (project.trb). So:

  • A file is run as it is: a script, or the entry of a program. A file that nothing imports may hold top-level code and needs no fn main, which is what makes a single script runnable at all.

  • A directory, and the working directory where nothing is named, runs the only program below it: a package's src/main.trb, or the entry of one of its program lines. torb run ./my-project and torb run my-project/src/main.trb reach the same file. Where there are several, run names them instead of guessing:

    error: acme/shop has 2 programs. Name one: `torb run shop`
      = shop, migrate
  • A name runs the program of that name below the working directory - torb run migrate. A name nothing declares answers error: There is no program `migrate` here with the names that exist and the reminder that a directory is written as a path, ./migrate.

A package without a program has nothing to run: error: acme/lib is a library: there is nothing to run, or error: acme/tour has no program: there is nothing to run for a package of scripts with neither src/main.trb nor src/lib.trb - its files are run by naming them. All of this is the same in the VM and with --native.

The cache of --native#

The binary goes into build/run/<profile>/<key>/ under the workspace root, and <key> is a hash of every file the front end read, with its path and its text, plus the path and the program name that were given and the toolchain that builds it: the hash of the compiler's own C that torb build wrote beside the running torb (program.hash, or program.c where only that is there) and the text of every C file and header of the runtime it links. The profile is dev unless the command line says --profile release or --release in front of the path. An unchanged program is therefore not rebuilt: the first run costs a build, every run after it costs a process start.

Keying on every file that was read rather than on the ones the program imports is the same trade check makes when it reads the whole workspace: an edit to a file the program does not import costs one rebuild, and in exchange "is this binary current" is a question about bytes that were read anyway instead of about a dependency graph that is only known after the check. A torb that was built again, or a runtime that was edited, is another key, so a binary an older compiler built is never the one that starts. The cache is a directory and nothing else - rm -r build/run is how it is cleared. A torb copied somewhere without the program.c it was built from is known by its path alone, which is the one case that asks for it.

Arguments#

Everything after the path or the name is what the program's own Process.arguments() sees - run itself reads none of it - so torb run tool.trb --verbose input.txt hands ["--verbose", "input.txt"] to tool.trb, and a word that looks like a flag of run is passed on rather than read.

What run puts on the output#

Nothing of its own. A build that succeeds says nothing, the program is started with the three streams torb itself holds rather than with a pipe, and the exit code is the program's. So a program's output arrives while it is produced, its standard output and standard error stay apart and in order, a program that reads standard input reads the one the user is typing into, and a gate that compares what a program wrote compares the program and not the driver.

A build that fails says so, on standard error, in the form check uses, and run leaves with 1 without starting anything.

The VM, the default#

torb run <path> builds nothing: the program is checked and lowered to the same typed IR build compiles, encoded as bytecode, and run by the VM inside torb itself, on the same runtime a native binary links. Its output, its exit code and its panics are the native binary's, which the conformance suite of the toolchain checks for every program. A program that runs long wants --native: the VM interprets, and a loop of arithmetic is a few dozen times slower than the binary. Its tasks run on the workers of torb's own pool - as many as TORB_WORKERS says, the core count by default - under the rules of a native binary's. A program that uses what the VM does not run yet is refused before anything runs, with a message that names what is missing. Two things answer differently because the program runs inside torb: CurrentProcess.executable() is the path of the entry file, which is what executes, and a recursion reaches the stack overflow panic at a depth of its own.

It runs in the dev profile, as --native builds, and the profile decides one thing in the VM: the return trace of ?, which the report of a top-level ? prints under the error, one at line per ? the failure went through (Errors at the top level). torb run --vm --release runs it without the trace, as a release binary runs - which is how the conformance suite compares a program with its release build.

It starts at once, because it does only what the program needs: the files of the workspace are listed and only the ones the program reaches are read and parsed - the packages its entry imports, depends on and names as its prelude - and the bodies of the entry are checked, while the bodies of a module of std are checked when the lowering first reaches that module. A program that prints one line is running about a quarter of a second after the command was typed; --timings prints where that time went, step by step, on standard error. A program the lowering refuses is checked in full before the refusal is printed, so the message is the one torb build gives.

The memory limit#

A program run runs - in the VM, or built with the dev profile - stops at a memory limit: the smaller of 8 GiB and half the physical memory. A native binary hands it to the operating system before the program starts (a job object on Windows, RLIMIT_DATA on Linux, its own count of what it allocates where the system has nothing that fits); in the VM the kernel counts what the program allocates. A program that allocates without end ends with panic: out of memory: the limit of 8 GiB was reached (...) and exit code 102 instead of paging the machine to a standstill. A child process the program starts is not held to its limit.

TORB_MEMORY_LIMIT sets another one - a number of bytes, or one with K, M, G or T (512M, 16G) - and 0 or none sets none; a value that is neither makes the program refuse to start, with exit code 2. With --release there is no limit unless the variable asks for one. In the VM the program runs inside torb, and the limit - the same default, or the variable - is the program's alone: the VM's kernel counts what the program allocates and ends it with the same message and exit code, and torb checks and lowers it unlimited by the variable. TORB_MEMORY_LIMIT=64M torb run big.trb gives the program 64 MiB.

Exit codes#

The program's own, unchanged. A panic leaves with 101, the number CONCEPT.md specifies, and running out of memory with 102; an uncaught Fail at the top level leaves with 1; a program that could not be built leaves with 1 and one that found no C compiler with 3.

Examples#

A script, with the program's own arguments behind it:

$ torb run tools/report.trb --since 2026-09-01
14 commits, 3 authors

In a package with a second program, run wants a name, and everything behind the name is the program's:

$ torb run
error: acme/shop has 2 programs. Name one: `torb run shop`
  = shop, migrate
$ torb run migrate --dry-run
3 tables to migrate

Built natively, the second run of an unchanged program starts immediately, because the key of its sources did not change:

$ torb run --native examples/tour/src/01-bindings-and-values.trb
1
Hello, World! 1 + 1 is 2
list: [1, 2, 3], first: 1, a: 1
  • torb build - the build run is made of, and where the binary goes when you ask for one.
  • torb check - the same front end without the back end, for when only the diagnostics are wanted.
  • project.trb - the program lines a name is looked up in.
  • The torb command - every subcommand in one table.
  • Top-level code - what makes a file runnable on its own.
Esc