Plum

Running programs

Building, running, cross-compiling, and the worked examples.

Running a program

Plum programs live in a project directory. A directory is a module (see “Modules” below). plum new scaffolds a minimal starter project:

plum new myapp
myapp/
  main.plum
// myapp/main.plum
let main (): Unit = println("hello, plum")

Compile and run it in one step:

plum run myapp

(The bare plum myapp form, with no run, still works too, for backward compatibility; plum run is the recommended, explicit spelling, symmetric with plum build.)

Or keep the binary:

plum build myapp -o myapp/out
./myapp/out

plum build’s -o/--output is optional; if omitted, the output binary is named after the project directory itself (written to the current working directory), mirroring go build/cargo build --bin.

Building for another platform

plum build takes a --target <triple>:

PLUM_CC="zig cc" plum build myapp --target aarch64-linux-musl

Plum does not implement cross-compilation so much as get out of its way. The IR it emits carries no target triple and no datalayout, so clang --target= retargets it directly, and Os.platform() is compiled in by the C compiler’s own #ifdef, so it reports the target’s platform, not the build machine’s, with no work on our part.

What Plum does not ship is a sysroot. clang can emit code for any target but cannot link one without that target’s libc, so --target needs a C driver that has one. PLUM_CC is where you name it; zig cc above, or a corporate cross toolchain. This is the same arrangement Rust’s ecosystem settled on with cargo-zigbuild, and it is why a native build still needs nothing but clang: nobody pays for a feature they do not use. See DESIGN.md’s cross-compilation section for the measurements behind that choice (bundling Zig’s sysroot would be a ~240x increase on a 712 KB archive).

Two conveniences: a Windows target gets a .exe suffix when you do not pass -o, and a non-64-bit target is refused outright rather than built. The second is not politeness: cell layout assumes 8-byte slots, so a 32-bit target would not fail to link, it would silently miscompile.

plum run and plum test are always native. They execute what they build, and this machine cannot run a foreign binary.

run and build are the SAME path: both compile to a native binary and differ only in whether it is kept. Nothing can behave one way under run and another under build, because there is no second engine for it to behave differently in.

Both use the same main entry point: a single Unit-typed parameter (let main (): ... = ..., invoked by the CLI itself, not called from your own source) returning Unit or any printable value (the CLI prints whatever main returns).

plum help (also --help and -h) prints usage. With no arguments at all, plum does the same. Extra arguments after help are ignored rather than treated as a file to compile.

Errors from either path point at real source locations:

error: type error: operator: type mismatch: expected Str, found Int
  --> <root>:3:15
  |
3 |     let bad = "hello" + 1;
  |               ^^^^^^^^^^^

Examples

Real, runnable projects under examples/, one per theme — each with its output recorded in expected.txt and checked by bootstrap/example-sweep (except asteroids, which opens a window and is only built, not run; see its own entry below):

Generated from RUNNING.md in the repository.