Type Str
Immutable UTF-8 strings. Use Str for command
arguments, logging, and any textual data.
let greeting: Str = "hello world"
echo greeting.upper()
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.
Runic values default to predictable semantics: immutable bindings,
explicit mutation, and Zig-inspired wrappers like ?T,
!T, and ^T.
Immutable UTF-8 strings. Use Str for command
arguments, logging, and any textual data.
let greeting: Str = "hello world"
echo greeting.upper()
Signed integers that keep arithmetic explicit. Bindings stay
immutable unless you declare them with mut.
mut retries: Int = 2
retries = retries + 1
Floating-point numbers for precise ratios or measurements. Annotate literal math so conversions are enforced up front.
let pi: Float = 3.14159
const radius: Float = 2.0
const area = pi * radius * radius
Logical true/false values that drive conditionals. Distinguish command success from custom business logic.
let count: Int = 3
const is_plural: Bool = count > 1
if is_plural {
echo "plural branch"
} else {
echo "singular branch"
}
Opaque byte buffers for binary payloads such as HTTP bodies or archive blobs. Keep text/bytes conversions explicit.
let http = import("net/http")
const resp = http.get("https://example.com/status")
const payload: Bytes = resp.body
Indicates a function returns no value beyond success. Pair it
with !Void to signal commands that only fail or
succeed.
fn init() !Void {
try bootstrap_network()
echo "Runtime ready"
}
Process identifiers exposed by command handles so you can introspect or manage background work.
let server = tail -f /var/log/app.log &
echo "spawned ${server.pid}"
Structured command results that include the exit code plus
helpers such as .ok and .failed_stage.
let status: ExitStatus = (build | tee build.log).status
if !status.ok {
echo "Pipeline failed at ${status.failed_stage}"
}
Homogeneous, indexable collections that interact with
for loops and iterators.
let fruits: Array(Str) = ["apple", "banana", "pear"]
for (fruits, 0..) |fruit, idx| {
echo "${idx}: ${fruit.upper()}"
}
Key/value dictionaries with literal syntax that preserves structure across commands.
let config: Map(Str, Int) = { port: 8080, retries: 2 }
echo "Listening on ${config.port}"
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 ;.
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
!T
Declare explicit error families via error enums or
unions, then return !T to describe “error or value”
results that require handling.
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)
}
?T)
Wrap any type with ?T to express nullable values;
use optional-aware if captures to unwrap safely.
const maybe_port: ?String = $PORT
if (maybe_port) |port| {
echo "Running on port ${port}"
} else {
echo "Using default port"
}
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.
(sleep 0.05; echo "warmup") &
echo "continued"
const job = (sleep 0.05; echo "hello from job" &)
job.wait
printf "%s" "${job.stdout}"
Feature highlights mirror features.md so you can
cross-reference the philosophy and the syntax.
Every bare word starts a pipeline stage, keeping shell muscle memory intact while layering structured data on top.
echo "hello world" | upper
Immutable let bindings, opt-in mut,
and literal arrays/maps eliminate accidental word splitting.
let greeting = "hello"
var count = 2
const items = ["apples", "oranges"]
const vars = { greeting: greeting, total: count }
Model recoverable failures via typed error sets and handle them
explicitly with catch instead of parsing strings.
const config = load_config() catch |err| {
echo "load failed: ${err}"
}
Every error is either propagated with try or
handled with catch, mirroring Zig’s
guarantees.
fn init() !Void {
try bootstrap_network()
read_config("/etc/runic.conf") catch |err| {
if err == error.FileError.NotFound {
return err
}
echo "Recovered from ${err}"
}
}
Constrain return signatures (e.g. FileError!Config)
so downstream code knows the precise failures to match.
fn read_config(path: Str) FileError!Config {
if !file.exists(path) {
return error.FileError.NotFound { path }
}
return parse_config(path)
}
?T pairs with optional-aware if so
values only exist inside the capture scope when present.
const maybe_port = $PORT
if (maybe_port) |port| {
echo "Running on port ${port}"
} else {
echo "Falling back to default"
}
.wait
Background execution uses a trailing &. Bound
background executions capture stdout/stderr just like synchronous
bindings, and .wait blocks until the work finishes.
const job = (sleep 0.05; echo "done" &)
echo "before wait"
job.wait
printf "%s" "${job.stdout}"
Functions, conditionals, and loops use modern syntax so scripts
read like other languages instead of then/fi pairs.
fn describe(count: Int) {
if count > 1 {
echo "plural"
} else {
echo "singular"
}
}
for and while consume any iterator the
runtime exposes, keeping capture bindings explicit.
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
}
}
Differentiate commands from pure expressions so data pipelines stay structured and word splitting disappears.
let files = ls ./src | lines()
const uppercased = files.map(fn (path) => path.upper())
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.
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}"
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.
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"
Pipelines surface per-stage exit codes so you know exactly where a chain failed before deciding how to recover.
let status = (build | tee build.log).status
if !status.ok {
echo "Build failed at step ${status.failed_stage}"
exit 1
}
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.
const math = import "util/math.rn"
echo math.pi
echo math.exit_code
No matching types or features. Try another keyword.