Runic Language Reference

A quick reference to Runic’s built-in types and language features. Every entry includes a one-line summary plus an executable snippet pulled from the current design docs so you can mirror the same behavior in your scripts.

Built-in Types

Runic values default to predictable semantics: immutable bindings, explicit mutation, and Zig-inspired wrappers like ?T, !T, and ^T.

Type Str

Immutable UTF-8 strings. Use Str for command arguments, logging, and any textual data.

Example
let greeting: Str = "hello world"
echo greeting.upper()

Type Int

Signed integers that keep arithmetic explicit. Bindings stay immutable unless you declare them with mut.

Example
mut retries: Int = 2
retries = retries + 1

Type Float

Floating-point numbers for precise ratios or measurements. Annotate literal math so conversions are enforced up front.

Example
let pi: Float = 3.14159
const radius: Float = 2.0
const area = pi * radius * radius

Type Bool

Logical true/false values that drive conditionals. Distinguish command success from custom business logic.

Example
let count: Int = 3
const is_plural: Bool = count > 1
if is_plural {
  echo "plural branch"
} else {
  echo "singular branch"
}

Type Bytes

Opaque byte buffers for binary payloads such as HTTP bodies or archive blobs. Keep text/bytes conversions explicit.

Example
let http = import("net/http")

const resp = http.get("https://example.com/status")
const payload: Bytes = resp.body

Type Void

Indicates a function returns no value beyond success. Pair it with !Void to signal commands that only fail or succeed.

Example
fn init() !Void {
  try bootstrap_network()
  echo "Runtime ready"
}

Type Pid

Process identifiers exposed by command handles so you can introspect or manage background work.

Example
let server = tail -f /var/log/app.log &
echo "spawned ${server.pid}"

Type ExitStatus

Structured command results that include the exit code plus helpers such as .ok and .failed_stage.

Example
let status: ExitStatus = (build | tee build.log).status
if !status.ok {
  echo "Pipeline failed at ${status.failed_stage}"
}

Type Array<T>

Homogeneous, indexable collections that interact with for loops and iterators.

Example
let fruits: Array(Str) = ["apple", "banana", "pear"]
for (fruits, 0..) |fruit, idx| {
  echo "${idx}: ${fruit.upper()}"
}

Type Map<K, V>

Key/value dictionaries with literal syntax that preserves structure across commands.

Example
let config: Map(Str, Int) = { port: 8080, retries: 2 }
echo "Listening on ${config.port}"

Type ProcessHandle

Structured results returned by every command invocation. Access stdout, stderr, status, and metadata without shell globals, even when command-producing expressions are chained with &&, ||, or ;.

Example
const combined = printf "hello\n" && printf "warning\n" >&2
echo combined.stdout
echo combined.stderr

const sequenced = printf "hello\n"; printf "warning\n" >&2
echo sequenced.stdout
echo sequenced.stderr
echo sequenced.status.exit_code

TypeError Sets & !T

Declare explicit error families via error enums or unions, then return !T to describe “error or value” results that require handling.

Example
error FileError = union {
  NotFound: { path: Str },
  PermissionDenied: { user: Str },
}

fn read_config(path: Str) FileError!Config {
  if !file.exists(path) {
    return error.FileError.NotFound { path }
  }
  return parse_config(path)
}

Type Optionals (?T)

Wrap any type with ?T to express nullable values; use optional-aware if captures to unwrap safely.

Example
const maybe_port: ?String = $PORT
if (maybe_port) |port| {
  echo "Running on port ${port}"
} else {
  echo "Using default port"
}

Type Background executions

A trailing & starts a command, pipeline, or block expression in the background. When you bind that result, its output stays buffered in memory and .wait blocks until it finishes.

Example
(sleep 0.05; echo "warmup") &
echo "continued"

const job = (sleep 0.05; echo "hello from job" &)
job.wait
printf "%s" "${job.stdout}"

Language Features

Feature highlights mirror features.md so you can cross-reference the philosophy and the syntax.

Feature Command-first execution

Every bare word starts a pipeline stage, keeping shell muscle memory intact while layering structured data on top.

Example
echo "hello world" | upper

Feature Strong, predictable data semantics

Immutable let bindings, opt-in mut, and literal arrays/maps eliminate accidental word splitting.

Example
let greeting = "hello"
var count = 2
const items = ["apples", "oranges"]
const vars = { greeting: greeting, total: count }

Feature Errors as first-class types

Model recoverable failures via typed error sets and handle them explicitly with catch instead of parsing strings.

Example
const config = load_config() catch |err| {
  echo "load failed: ${err}"
}

Feature Mandatory explicit handling

Every error is either propagated with try or handled with catch, mirroring Zig’s guarantees.

Example
fn init() !Void {
  try bootstrap_network()

  read_config("/etc/runic.conf") catch |err| {
    if err == error.FileError.NotFound {
      return err
    }
    echo "Recovered from ${err}"
  }
}

Feature Restrict function error sets

Constrain return signatures (e.g. FileError!Config) so downstream code knows the precise failures to match.

Example
fn read_config(path: Str) FileError!Config {
  if !file.exists(path) {
    return error.FileError.NotFound { path }
  }
  return parse_config(path)
}

Feature Optional data that behaves like Zig

?T pairs with optional-aware if so values only exist inside the capture scope when present.

Example
const maybe_port = $PORT
if (maybe_port) |port| {
  echo "Running on port ${port}"
} else {
  echo "Falling back to default"
}

Feature Background commands with .wait

Background execution uses a trailing &. Bound background executions capture stdout/stderr just like synchronous bindings, and .wait blocks until the work finishes.

Example
const job = (sleep 0.05; echo "done" &)
echo "before wait"
job.wait
printf "%s" "${job.stdout}"

Feature Structured flow control

Functions, conditionals, and loops use modern syntax so scripts read like other languages instead of then/fi pairs.

Example
fn describe(count: Int) {
  if count > 1 {
    echo "plural"
  } else {
    echo "singular"
  }
}

Feature Native iteration constructs

for and while consume any iterator the runtime exposes, keeping capture bindings explicit.

Example
for (fruits, 0..) |fruit, idx| {
  echo "${idx}: ${fruit.upper()}"
}

const reader = file.open("debug.log")
while reader.lines() |line| {
  if line.starts_with("ERR") {
    echo line
  }
}

Feature Command vs. expression separation

Differentiate commands from pure expressions so data pipelines stay structured and word splitting disappears.

Example
let files = ls ./src | lines()
const uppercased = files.map(fn (path) => path.upper())

Feature Processes as first-class values

Every command invocation yields a handle with stdout, stderr, status, and metadata instead of relying on global shell state. Bound command expressions keep that handle data available after &&, ||, and ; sequencing.

Example
let sync_proc = git status --short
echo sync_proc.stdout
echo sync_proc.status.exit_code

const combined = printf "hello\n" && printf "warning\n" >&2
echo combined.stdout
echo combined.stderr

const async_proc = (sleep 0.05; echo "done" &)
async_proc.wait
printf "%s" "${async_proc.stdout}"

Feature Capturing pipelines & multiplexed IO

Capture stdout/stderr independently, tee outputs to variables, and redirect streams without losing handle metadata. Explicit fd redirects such as 1>... , 2>... , and 1>&2 follow shell-style left-to-right ordering.

Example
make all | tee build.log 1>build_stdout
if build_stdout.status.exit_code != 0 {
  echo build_stdout.stdout
}

echo "saved output" 1>"out.log"
echo "saved error" 2>"err.log"
echo "hello" 1>&2 2>"/dev/null"

Feature Error-aware pipelines

Pipelines surface per-stage exit codes so you know exactly where a chain failed before deciding how to recover.

Example
let status = (build | tee build.log).status
if !status.ok {
  echo "Build failed at step ${status.failed_stage}"
  exit 1
}

Feature Module system and reuse

Import reusable helpers from other .rn files. Imported modules are resolved from the importing file, execute once per resolved path, expose pub declarations, and also retain execution-result fields on the imported value.

Example
const math = import "util/math.rn"

echo math.pi
echo math.exit_code