A fast, expressive programming language with a clean syntax and powerful features, built in Rust.
Walrus is a dynamically-typed programming language that combines simplicity with power. It runs on a bytecode virtual machine, with a mark-and-sweep garbage collector and comprehensive support for modern programming paradigms.
- Bytecode VM: High-performance stack-based virtual machine with call frames and tail call optimization
- Disassembler: Inspect generated bytecode and analyze program structure
- First-Class Functions: Functions as values with nested function support
- Structs & Methods: Object-oriented programming with struct definitions and static methods
- Comprehensive Data Types: integers, floats, strings, booleans, lists, dictionaries, ranges
- Control Flow: if/else conditionals, while loops, for loops with iterators, break/continue
- Format Strings: Embedded expressions within string literals (f-strings)
- Short-Circuit Evaluation: Truthiness-based
and/oroperators with short-circuit semantics - Recursion: Full support for recursive functions with tail call optimization
- String Manipulation: Slicing, indexing, and string operations
- Collection Operations: List/dict concatenation, negative indexing, in-place modification
- Stack-Based Execution: Efficient push/pop operations for expression evaluation
- Call Frame Stack: Proper function call semantics with frame pointers (not child VMs)
- Shared Locals Vector: Memory-efficient local variable storage across frames
- Tail Call Optimization:
TailCallopcode reuses frames for recursive functions - 80+ Opcodes: Comprehensive instruction set including specialized zero-operand variants
- Constant Folding: Arithmetic and boolean expressions evaluated at compile time
- Specialized Opcodes:
LoadConst0,LoadLocal0-3,LoadGlobal0-3for common indices - Stack Operations:
Dup,Swap,Pop2,Pop3for efficient manipulation - Memory-Optimized: u32 indices reduce instruction size from 16 to 12 bytes
- Forward Declaration: Two-pass compilation for mutual recursion support
- Mark-and-Sweep GC: Automatic garbage collection with configurable thresholds
- Arena Allocation: Separate
DenseSlotMaparenas for each heap type - Memory Tracking: Byte-level allocation tracking triggers GC intelligently
- String Interning: Via
strenafor deduplicated string storage - Custom Allocator:
mimallocfor improved allocation performance
| Function | Description |
|---|---|
core.len(x) |
Get length of string, list, dict, or module |
core.str(x) |
Convert value to string |
core.type(x) |
Get type name of a value |
core.input(prompt) |
Read user input with prompt |
core.gc() |
Manually trigger garbage collection (VM mode) |
core.heap_stats() |
Get heap statistics as a dict (VM mode) |
core.gc_threshold(n) |
Configure GC allocation threshold (VM mode) |
core is auto-imported by the prelude in every file.
You only need an explicit import if you want a different alias:
import "std/core" as c;
Walrus provides a standard library through an import system. Modules are imported using the import statement and return a module value backed internally by a hash map.
import "std/io";
import "std/sys" as system;
import "std/math";
import "std/net";
import "std/http";
Walrus supports package-style imports with pinned lock entries.
[package]
name = "my-app"
version = "0.1.0"
[dependencies]
greeter = { version = "1.2.3", path = "./deps/greeter" }Current scope: local path dependencies.
walrus --sync-lockThis creates or updates Walrus.lock with pinned package versions and resolved paths.
Example Walrus.lock:
version = 1
[packages.greeter]
version = "1.2.3"
path = "deps/greeter"import @greeter as g;
println(g.message);
println(g.double(21));
Imported modules/packages are module values (map-backed internally):
- Preferred: dot access (
g.message,g.double(21)). - Use indexing (
g["name"]) only when the member name is dynamic or not a valid identifier.
- If
Walrus.tomlis present,import @nameresolves throughWalrus.lock. - If the manifest version and lock version differ for a package, execution fails with a pin mismatch error.
- If no
Walrus.tomlis found, Walrus falls back to resolving@nameas./name/main.walrusrelative to the importing file.
| Function | Description |
|---|---|
io.file_open(path, mode) |
Open a file, returns a handle. Modes: "r", "w", "a", "rw" |
io.file_read(handle) |
Read entire file contents as string |
io.file_read_line(handle) |
Read a single line (returns void at EOF) |
io.file_write(handle, content) |
Write string to file, returns bytes written |
io.file_close(handle) |
Close a file handle |
io.file_exists(path) |
Check if a file exists (returns bool) |
io.read_file(path) |
Convenience: read entire file in one call |
io.write_file(path, content) |
Convenience: write entire file in one call |
Example: File Operations
import "std/io";
// Simple read/write (convenience functions)
io.write_file("hello.txt", "Hello, Walrus!");
let content = io.read_file("hello.txt");
println(content); // Hello, Walrus!
// Handle-based operations (for larger files or streaming)
let handle = io.file_open("data.txt", "w");
io.file_write(handle, "Line 1\n");
io.file_write(handle, "Line 2\n");
io.file_close(handle);
// Reading with handles
let reader = io.file_open("data.txt", "r");
let line = io.file_read_line(reader);
while line != void {
println(line);
line = io.file_read_line(reader);
}
io.file_close(reader);
| Function | Description |
|---|---|
sys.env_get(name) |
Get environment variable (returns void if not set) |
sys.args() |
Get command line arguments as a list |
sys.cwd() |
Get current working directory |
sys.exit(code) |
Exit the program with the given status code |
Example: System Information
import "std/sys";
// Environment variables
let home = sys.env_get("HOME");
if home != void {
println(f"Home directory: {home}");
}
// Command line arguments
let args = sys.args();
println(f"Program: {args[0]}");
if core.len(args) > 1 {
println(f"Arguments: {args[1..]}");
}
// Current directory
println(f"Working directory: {sys.cwd()}");
// Exit with status
if core.len(args) < 2 {
println("Usage: walrus script.walrus <args>");
sys.exit(1);
}
| Function | Description |
|---|---|
net.tcp_bind(host, port) |
Bind a TCP listener and return a listener handle |
net.tcp_accept(listener) |
Accept one incoming connection and return a stream handle |
net.tcp_connect(host, port) |
Connect to a host/port and return a stream handle |
net.tcp_local_port(listener) |
Return the listener's local bound port (useful with port 0) |
net.tcp_peer_addr(stream) |
Return the remote peer address for a connected stream |
net.tcp_stream_local_addr(stream) |
Return the local socket address for a connected stream |
net.tcp_set_read_timeout(stream, ms_or_void) |
Set or clear the read timeout on a stream |
net.tcp_set_write_timeout(stream, ms_or_void) |
Set or clear the write timeout on a stream |
net.tcp_set_nodelay(stream, enabled) |
Enable or disable TCP_NODELAY |
net.tcp_shutdown(stream, how) |
Shutdown read, write, or both sides of a TCP stream |
net.tcp_read(stream, max_bytes) |
Read up to max_bytes bytes (returns void at EOF) |
net.tcp_read_line(stream) |
Read one line (returns void at EOF) |
net.tcp_write(stream, content) |
Write UTF-8 content and return bytes written |
net.tcp_close(stream) |
Close a stream handle |
net.tcp_close_listener(listener) |
Close a listener handle |
Example: Loopback Round Trip
import "std/net" as net;
let listener = net.tcp_bind("127.0.0.1", 0);
let port = net.tcp_local_port(listener);
let client = net.tcp_connect("127.0.0.1", port);
let server = net.tcp_accept(listener);
net.tcp_write(client, "ping\n");
println(net.tcp_read_line(server)); // ping
net.tcp_write(server, "pong\n");
println(net.tcp_read_line(client)); // pong
net.tcp_close(client);
net.tcp_close(server);
net.tcp_close_listener(listener);
std/http is backed by Hyper HTTP primitives (hyper::http) plus httparse for HTTP/1 request parsing.
| Function | Description |
|---|---|
http.parse_request_line(line) |
Parse request line into { ok, method, target, path, query, version } |
http.parse_query(query) |
Parse query string into a dict |
http.parse_query_pairs(query) |
Parse query string into a decoded list of [key, value] pairs |
http.normalize_path(path) |
Normalize path (collapse repeated slashes, trim trailing slash) |
http.match_route(pattern, path) |
Route matching with :param segments and trailing * wildcard |
http.status_text(status) |
Return status reason phrase (for example 404 -> "Not Found") |
http.response(status, body) |
Build an HTTP/1.1 response string with default headers |
http.response_with_headers(status, body, headers) |
Build response with caller-provided headers dict |
http.make_response(status, body) |
Build a response object {status, body, headers, header_pairs} |
http.make_response_with_headers(status, body, headers) |
Build a response object with headers |
http.serialize_response(response) |
Serialize a response object back into an HTTP/1.1 response string |
http.read_request(stream, max_body_bytes) |
Read/parse one HTTP request (void on EOF, { ok: false, error } on malformed input) |
Example: Minimal HTTP Echo
import "std/net" as net;
import "std/http" as http;
let listener = net.tcp_bind("127.0.0.1", 8081);
let stream = net.tcp_accept(listener);
let req = http.read_request(stream, 8192);
if req != void and req["ok"] {
let body = "echo\n" + req["body"] + "\n";
let response = http.response_with_headers(200, body, {"x-walrus": "http"});
net.tcp_write(stream, response);
}
net.tcp_close(stream);
net.tcp_close_listener(listener);
HTTP Benchmark Snapshot
Representative local loopback run using benchmarks/http_bench/run_http_bench.py: ab -k, 10,000 requests per scenario, 1,000 warmup requests, concurrency 32, peak RSS sampled from the server process.
| Target | GET /health |
GET /users/42/posts/7 |
POST /echo |
Peak RSS |
|---|---|---|---|---|
| Walrus | 23,441 req/s, p95 2 ms, p99 2 ms |
21,285 req/s, p95 2 ms, p99 2 ms |
17,633 req/s, p95 3 ms, p99 5 ms |
23.42 MiB |
| Flask + Waitress | 5,740 req/s, p95 11 ms, p99 21 ms |
6,806 req/s, p95 8 ms, p99 13 ms |
6,953 req/s, p95 8 ms, p99 9 ms |
44.23 MiB |
| FastAPI + Uvicorn | 6,680 req/s, p95 6 ms, p99 11 ms |
5,883 req/s, p95 7 ms, p99 13 ms |
9,528 req/s, p95 4 ms, p99 5 ms |
59.62 MiB |
On this run, Walrus delivered roughly 3.5x FastAPI/Uvicorn's throughput on GET /health while using under half its memory. See benchmarks/http_bench/README.md for methodology and rerun instructions.
| Function | Description |
|---|---|
math.pi() |
Returns π |
math.e() |
Returns Euler's number e |
math.tau() |
Returns τ (2π) |
math.inf() |
Returns positive infinity |
math.nan() |
Returns NaN |
math.abs(x) |
Absolute value |
math.sign(x) |
Sign of x as -1, 0, or 1 |
math.min(a, b) |
Smaller of two numbers |
math.max(a, b) |
Larger of two numbers |
math.clamp(x, lo, hi) |
Clamp value to a range |
math.floor(x) |
Floor |
math.ceil(x) |
Ceiling |
math.round(x) |
Round to nearest integer value |
math.trunc(x) |
Truncate fractional part |
math.fract(x) |
Fractional part |
math.sqrt(x) |
Square root (x must be >= 0) |
math.cbrt(x) |
Cube root |
math.pow(x, y) |
x raised to y |
math.hypot(x, y) |
Euclidean norm sqrt(x*x + y*y) |
math.sin(x) |
Sine (radians) |
math.cos(x) |
Cosine (radians) |
math.tan(x) |
Tangent (radians) |
math.asin(x) |
Inverse sine (x in [-1, 1]) |
math.acos(x) |
Inverse cosine (x in [-1, 1]) |
math.atan(x) |
Inverse tangent |
math.atan2(y, x) |
Inverse tangent with quadrant awareness |
math.exp(x) |
e^x |
math.ln(x) |
Natural log (x > 0) |
math.log2(x) |
Base-2 log (x > 0) |
math.log10(x) |
Base-10 log (x > 0) |
math.log(x, base) |
Logarithm with custom base (x > 0, base > 0, base != 1) |
math.lerp(a, b, t) |
Linear interpolation between a and b |
math.degrees(r) |
Convert radians to degrees |
math.radians(d) |
Convert degrees to radians |
math.is_finite(x) |
Returns true if finite |
math.is_nan(x) |
Returns true if NaN |
math.is_inf(x) |
Returns true if infinite |
math.seed(n) |
Seed RNG for deterministic random sequences |
math.rand_float() |
Random float in [0.0, 1.0) |
math.rand_bool() |
Random boolean (50/50) |
math.rand_int(a, b) |
Random integer in [a, b] (inclusive) |
math.rand_range(a, b) |
Random float in [a, b) |
Example: Math + Random
import "std/math";
println(math.pi());
println(math.sqrt(49));
println(math.pow(2, 10));
println(math.lerp(10, 20, 0.25)); // 12.5
math.seed(1337);
let id = math.rand_int(1000, 9999);
println(f"ID: {id}");
println(math.rand_bool());
let angle = math.radians(45);
println(math.sin(angle));
- Debugger Support: Breakpoints, step-through execution, and stack inspection
- Stack Traces: Human-readable call stack on errors (truncated for deep recursion)
- Comprehensive Error Messages: Detailed error reporting with source locations
- Benchmark Suite: 20+ benchmarks comparing against Python
Walrus includes an optional Just-In-Time (JIT) compiler powered by Cranelift that can dramatically speed up hot loops by compiling them to native machine code.
| Benchmark | VM | JIT | Speedup |
|---|---|---|---|
| 10K iterations × sum(0..1000) | 0.68s | 0.01s | ~68x |
# Build with JIT feature enabled
cargo build --release --features jit
# Or add to your Cargo.toml
[features]
jit = ["cranelift-codegen", "cranelift-frontend", "cranelift-jit", "cranelift-module", "cranelift-native"]# Run with JIT enabled
./target/release/walrus program.walrus --jit
# Show JIT profiling statistics
./target/release/walrus program.walrus --jit --jit-stats
# Disable profiling (baseline comparison)
./target/release/walrus program.walrus --no-jit-profileThe JIT compiler targets hot integer range loops that follow specific patterns. A loop becomes "hot" after 1000 iterations.
JIT-Compatible Patterns:
// Pattern 1: Sum accumulation (acc += i)
let sum = 0;
for i in 0..n {
sum = sum + i;
}
// Pattern 2: Simple iteration with reassignment
let result = 0;
for i in 0..n {
result = result + some_value;
}
// Pattern 3: Count iterations
let count = 0;
for _ in 0..n {
count = count + 1;
}
// Pattern 4: Simple println of loop variable
for i in 0..n {
println(i); // Single println of integer is JIT-compiled
}
// Pattern 5: Sum with println
let sum = 0;
for i in 0..n {
sum = sum + i;
println(i); // Can be combined with accumulation
}
NOT JIT-Compatible (falls back to interpreter):
// Function calls inside loop
for i in 0..n {
result = compute(i); // Contains Call opcode
}
// Multiple print operations per iteration
for i in 0..n {
print(i); // Multiple prints = not JIT-compatible
print(" "); // String printing not yet supported
}
// Complex operations (method calls, string ops, etc.)
for i in 0..n {
list.push(i); // Method calls / heap operations
}
- Hot-Spot Detection: The VM profiles loop iterations and function calls
- Type Analysis: Tracks runtime types to ensure type stability
- Bytecode Analysis: Examines loop body for JIT-compatible operations
- Native Code Generation: Uses Cranelift to compile to native machine code
- External Callbacks: Print operations call back into Rust for I/O
- Seamless Execution: JIT code reads/writes the same local variables as the interpreter
Example JIT Output (with --jit-stats):
Hot-spot Statistics:
Total tracked regions: 3
Hot loops: 1
Hot functions: 1
JIT compiled regions: 1
Hottest spot: loop@5..5 (402000x)
Type Profile: 4 locations observed
JIT Stats: 1 functions compiled
For a simple sum loop, the JIT generates optimized native code:
// Walrus: for i in 0..n { sum = sum + i; }
// Cranelift IR:
block1(i, acc):
cmp i < end
branch to body or exit
block2:
acc = acc + i
i = i + 1
jump block1
block3:
return acc
- Rust (latest stable version)
git clone https://github.com/sb2bg/walrus.git
cd walrus
cargo build --releaseReleases are automated from GitHub Actions:
- When
Cargo.toml'sversionchanges onmaster, theReleaseworkflow creates a GitHub release with tagv<version>. - If the version did not change (or the tag already exists), the workflow exits without creating a release.
# Start the interactive REPL
cargo run --
# Execute a file
cargo run -- file.walrus
# Compile mode (VM execution)
cargo run -- -c file.walrus
# Disassemble bytecode
cargo run -- -d file.walrus
# View help
cargo run -- --helpWalrus now includes a language server and a VS Code extension scaffold in vscode/walrus.
# Build the language server
cargo build --release --bin walrus-lsp
# Install extension dependencies
cd vscode/walrus
npm installThen open vscode/walrus in VS Code and press F5 to launch an Extension Development Host.
Open any .walrus file to get:
- Parse diagnostics
- Rich hover for symbols, keywords, and standard library APIs
- Scoped completion (locals, functions, structs, modules, keywords)
- Signature help for function and standard library calls
- Document symbols (nested outline)
- Go-to-definition
- Find references
- Rename symbol
- Document highlights (read/write)
- Syntax highlighting
println("Hello, World!");
Walrus supports both single-line and multi-line comments:
// This is a single-line comment
/* This is a multi-line comment
that can span multiple lines */
let x = 42; // Comments can appear after code
/* Multi-line comments can also be inline */ let y = 84;
let number = 42;
let pi = 3.14159;
let name = "Walrus";
let active = true;
let items = [1, 2, 3, 4, 5];
let data = {"key": "value", "count": 10};
Tuple values exist at runtime (for example, dictionary iteration yields key/value pairs as tuples), but tuple literals are not yet part of the surface syntax.
Walrus conditions and short-circuit logic are truthiness-based.
Falsy values:
falsevoid0and0.0- Empty string/list/dict/module/tuple/range
Truthy values:
- Everything else
and/or short-circuit and return operand values. not returns a boolean.
let name = "Alice";
let age = 30;
let score = 95.5;
// Basic interpolation
println(f"Hello, {name}!");
// Multiple expressions
println(f"User: {name}, Age: {age}");
// Arithmetic expressions
println(f"Next year you'll be {age + 1}");
// Complex expressions
let items = [1, 2, 3];
println(f"List has {core.len(items)} items: {items}");
println(f"Score: {score * 100 / 100}%");
fn factorial : n {
if n <= 1 {
return 1;
}
return n * factorial(n - 1);
}
let result = factorial(5); // 120
let square = : x {
return x * x;
};
let result = square(7); // 49
struct Point {
fn new : x, y {
return {"x": x, "y": y};
}
fn distance : p1, p2 {
let dx = p1["x"] - p2["x"];
let dy = p1["y"] - p2["y"];
return (dx * dx + dy * dy) ** 0.5;
}
}
let p1 = Point.new(3, 4);
let p2 = Point.new(0, 0);
let dist = Point.distance(p1, p2); // 5.0
// Conditionals
if score >= 90 {
println("A");
} else if score >= 80 {
println("B");
} else {
println("C");
}
// While loop
let i = 0;
while i < 5 {
println(i);
i = i + 1;
}
// For loop with range
for x in 0..10 {
println(x);
}
// For loop with list
for item in [1, 2, 3, 4, 5] {
println(item);
}
for n in 1..101 {
let result = "";
if n % 3 == 0 {
result += "Fizz";
}
if n % 5 == 0 {
result += "Buzz";
}
if result == "" {
result = n;
}
println(result);
}
fn quicksort : arr {
if core.len(arr) <= 1 {
return arr;
}
let pivot = arr[0];
let less = [];
let equal = [];
let greater = [];
for x in arr {
if x < pivot {
less.push(x);
} else if x == pivot {
equal.push(x);
} else {
greater.push(x);
}
}
return quicksort(less) + equal + quicksort(greater);
}
let sorted = quicksort([64, 34, 25, 12, 22, 11, 90, 88]);
The Walrus VM is a stack-based bytecode interpreter with proper call frames:
- 80+ Opcodes: Comprehensive instruction set for all language features
- Call Frame Stack: Each function call pushes a frame with return address, frame pointer, and bytecode reference
- Shared Locals Vector: All frames share a single locals vector, indexed by frame pointer + local index
- Global Variables: Stored in
Rc<RefCell<Vec<Value>>>for cross-frame persistence - Tail Call Optimization: Recursive tail calls reuse the current frame instead of pushing new ones
- Automatic GC: Collection triggered by allocation count or memory threshold
- Two-Pass Compilation: Forward declarations for mutual recursion support
- Constant Folding: Evaluates
1 + 2 * 3at compile time - Jump Patching: Forward references resolved after compilation
- Loop Context Tracking: Break/continue with proper local cleanup
- Scope Management: Depth tracking distinguishes local vs. global variables
- Symbol Tables: Separate tracking for locals and globals with indices
The mark-and-sweep GC traces from roots and frees unreachable objects:
- Root Collection: Stack, locals, globals, and constants are all roots
- Configurable Thresholds: Default 1024 allocations or 8MB estimated memory
- Memory Estimation: Tracks approximate bytes for lists, dicts, functions, etc.
- Statistics Tracking: Total bytes freed, collection count available via
core.heap_stats() - Manual Trigger:
core.gc()for explicit collection
- Arena-Based Heap: Separate
DenseSlotMaparenas for lists, tuples, dicts, functions, iterators, structs - String Interning: All strings stored once via
strena::Interner - Generation-Indexed Keys: Type-safe references prevent use-after-free
- Copy-on-Write Semantics: Values are small (key-based), heap data shared until modified
# Run test suite
./scripts/run-tests.sh
# Run benchmarks (Python vs Walrus comparison)
cd benchmarks && ./run_benchmarks.sh# Release build
./scripts/build-optimized.sh
# Platform-specific
./scripts/build-linux-optimized.sh
./scripts/build-windows-optimized.sh# Memory profiling with dhat
./scripts/dhat.sh
# CPU profiling (macOS)
./scripts/instruments.shCheck out the showcase.walrus file for a comprehensive demonstration of all language features, including:
- Bank account system
- Game character battles
- Prime number generation
- Text processing and palindromes
- Mathematical sequences
- Higher-order functions
Run it with:
cargo run -- showcase.walrusAdditional example projects:
examples/json_parser/- A simple recursive-descent JSON parser project in Walrus.examples/pathfinding_visualizer/- A deterministic A* maze pathfinding demo with ASCII search snapshots.examples/terminal_roguelike/- A simulation-style ASCII roguelike demo using structs, RNG, file I/O, and VMtry/catch.
Contributions are welcome! Areas of interest:
- Standard library expansion
- Performance optimizations
- Additional language features
- Documentation improvements
Please check our issue tracker for open tasks.
Walrus is licensed under the MIT License. See LICENSE for details.
Built with:
- LALRPOP - Parser generator
- Cranelift - Native code generation for JIT compilation
- SlotMap - Arena-based memory management
- mimalloc - High-performance allocator
- rustc-hash - Fast hashing (FxHashMap/FxHashSet)
- strena - String interning
- float-ord - Orderable floats for hash maps
Happy Coding with Walrus! 🦭
