Skip to content

TheLakeMan/rusty

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

106 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rusty — A Modern Lisp in Rust

A complete, feature-rich Lisp interpreter implemented in Rust with first-class support for AI agent orchestration, tool calling, LLM integration, and symbolic reasoning.

Version: 0.30.0 | Status: Production-ready — REPL, file runner, Python bridge, AI agent loop


🎯 Vision: The Symbolic Transformation Layer for AI/ML

Rusty is the language you reach for when you need computation that reasons about computation:

  • LLM as creative planner — Generate high-level strategies
  • Rusty as reliable executor — Deterministically execute with symbolic reasoning
  • Verifiable agents — Prove correctness using formal methods

See the full 5-year roadmap →


☯ Built with Rusty

wuwei — a provably-gated agent runner: LLM agents that can't act until the action is proven safe. Every tool call is contract-checked before it runs, and every tool is statically verified honest about its effects — all built on Rusty's certify-tool-chain / check-effects / safe-call, with zero new interpreter code. The flagship demonstration of Rusty's verifiable-agent thesis.


Quick Start

# Build
cargo build --release

# Interactive REPL
cargo run

# Run a Lisp file
cargo run -- path/to/script.lisp

# Install `rusty` onto your PATH (used by apps built on Rusty, e.g. wuwei)
cargo install --path . --bin rusty --root ~/.local

# Python bridge
maturin develop
python3 -c "import rusty; print(rusty.eval('(+ 1 2)'))"

Architecture

Source (.lisp) or REPL input
    ↓  src/lexer.rs       — tokenizer
    ↓  src/parser.rs      — S-expression parser  
    ↓  src/eval.rs        — evaluator, TCO loop, special forms, LLM + tool builtins
    ↓  src/env.rs         — lexical environments, closures
    ↓  src/interp.rs      — builtins, stdlib loader, shared core
    ↓  src/lib.rs         — PyO3 Python bindings
    ↓  src/main.rs        — REPL, file runner

Key Files

File Purpose
src/eval.rs Evaluator — TCO loop, special forms, deftool, react-loop, llm
src/interp.rs 60+ builtins, stdlib loader, JSON, shell, format
src/env.rs Environment frames — Value enum including Tool, Lambda, Macro
src/lib.rs Python bindings via PyO3 — Rusty, RustySession, rusty.eval()
agent-tools.lisp 10 filesystem + shell + LLM tools, ReAct entry point (auto-loaded)
std.lisp Standard library — 230+ lines of Lisp utilities

Deep dive: Full architecture guide →


AI Agent System

Rusty is designed as the symbolic execution layer for local AI agents:

LLM (planner)     → decides what to do
Rusty (executor)  → deterministically does it

deftool — Register Agent Tools

(deftool create-dir (path)
  "Create a directory at the given path"
  (shell (format "mkdir -p ~a" path)))

(deftool read-file (path)
  "Read file contents"
  (shell (format "cat ~a" path)))

(deftool ask-llm (prompt)
  "Query the local LLM"
  (llm prompt 0.7 500))

tool-call — Execute Tools

; Direct tool invocation
(tool-call "create-dir" "my-project")
(tool-call "write-file" "my-project/README.md" "# Hello from Rusty!")
(tool-call "read-file" "my-project/README.md")
(tool-call "list-dir" "my-project")
(tool-call "file-exists" "my-project/README.md")
(tool-call "ask-llm" "What is machine learning?")

list-tools — Inspect Registry

(list-tools)
; => ((create-dir ("path") "Create a directory...")
;     (write-file ("path" "content") "Write content...")
;     ...)

(show-tools)   ; Pretty-print all registered tools

react-loop — Autonomous Agent

; agent-tools.lisp is auto-loaded by std.lisp — tools are already registered
(agent "Create a folder called notes with an index.md file")

The ReAct loop:

  1. Sends goal + tool descriptions to the LLM
  2. Parses ACTION: / INPUT: / FINAL: from response
  3. Executes the tool call via Rusty (real system calls)
  4. Feeds OBSERVATION: back to LLM
  5. Repeats until FINAL: or max steps

llm — Direct LLM Access

; Requires llama-server running on localhost:8080
(llm "What is 2+2?" 0.7 100)
(llm "Summarize this" 0.3 500)

Rusty talks to any OpenAI-compatible /v1/chat/completions endpoint. The simplest is llama.cpp's llama-server:

# Build llama.cpp (once)
git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
cmake -B build && cmake --build build --config Release -j

# Serve any GGUF model on localhost:8080 (grab one from Hugging Face)
./build/bin/llama-server -m /path/to/model.gguf --port 8080

Override the defaults with env vars if needed: RUSTY_LLM_URL, RUSTY_MODEL, RUSTY_LLM_TIMEOUT_SECS (default 120 — raise it for slow reasoning models).


Built-in Tools (agent-tools.lisp)

Tool Args Description
create-dir path Create directory (mkdir -p)
write-file path content Write content to file
append-file path content Append content to file
read-file path Read file contents
list-dir path List directory (ls -la)
delete-file path Delete a file
file-exists path Check if path exists → bool
shell-run command Run any shell command
ask-llm prompt Query local LLM
search-files pattern grep -r in current directory

Python Bridge

import rusty

# One-shot eval
print(rusty.eval("(+ 1 2)"))                    # "3"
print(rusty.eval("(->> '(1 2 3) (filter odd?) (map square) sum)"))  # "35"

# Stateless instance
r = rusty.Rusty()
print(r.eval("(json-encode (list (list \"x\" 42)))"))  # {"x": 42}

# Stateful session — definitions persist across calls
s = rusty.RustySession()
s.eval("(define (fact n) (if (= n 0) 1 (* n (fact (- n 1)))))")
print(s.eval("(fact 10)"))   # 3628800

Build the Python package:

maturin develop        # install into active venv
maturin build          # build wheel for distribution

Language Reference

Core Special Forms

(define x 42)                          ; bind
(define (f x y) (+ x y))              ; define function
(def f (x y) (+ x y))                 ; SimpleLisp-style define
(set! x 43)                            ; mutate existing
(set x 44)                             ; create or mutate
(lambda (x y . rest) body...)          ; anonymous function
(if test then else)                    ; conditional
(cond (test expr)... (else expr))      ; multi-branch
(and a b c) (or a b c)                ; short-circuit logic
(when test body...) (unless test body...)
(begin e1 e2 ... en)                   ; sequence
(let ((x 1) (y 2)) body...)           ; local bindings
(let* ((x 1) (y (+ x 1))) body...)    ; sequential let
(letrec ((f (lambda (n) ...))) body...) ; recursive let
(let loop ((i 0)) body... (loop (+ i 1)))  ; named let / loop
(do ((var init step)...) (test result...) body...)  ; do loop
(quote x) 'x                          ; literal data
(quasiquote x) `x  ,splice  ,@splice  ; template / unquote
(eval-when (phase...) body...)         ; run body now (phase unused outside macros);
                                        ; inside defmacro, runs once at definition time

Macros

(defmacro my-when (test . body)
  `(if ,test (begin ,@body) ()))

(defmacro swap! (a b)
  (let ((tmp (gensym "tmp")))
    `(let ((,tmp ,a)) (set! ,a ,b) (set! ,b ,tmp))))

(gensym "prefix")    ; unique symbol for hygienic macros

Macro Profiler

(macro-profile-on)          ; start recording expansion counts/timing (off by default)
(macro-profile-report)      ; => ((name call-count total-microseconds) ...) sorted by time desc
(show-macro-profile)        ; pretty-print the above
(macro-profile-reset)
(macro-profile-off)

Native Codegen (defrust) & Symbolic Differentiation

;; Compiles a restricted numeric subset to real Rust via rustc and
;; dynamically loads it: numbers, params, let/let* locals, + - * /,
;; sqrt expt abs mod floor ceiling round min max, if/cond (comparison
;; conditions), and self-recursive calls — everything is f64.
;; ~1000x faster than the tree-walked equivalent once compiled (measured
;; on fib(30): ~8.2s interpreted vs. ~0.007s compiled, cached).
(defrust fib (n)
  (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))
(fib 30)   ; => 832040, runs as native code

(defrust dist (x1 y1 x2 y2)          ; locals + math builtins
  (let ((dx (- x2 x1)) (dy (- y2 y1)))
    (sqrt (+ (* dx dx) (* dy dy)))))

;; defrust* compiles a GROUP into one .so — the functions can call each
;; other (mutual recursion), which separate defrust invocations cannot.
(defrust* (F (n) (if (= n 0) 1 (- n (M (F (- n 1))))))   ; Hofstadter
          (M (n) (if (= n 0) 0 (- n (F (M (- n 1)))))))
(F 12)   ; => 8, both functions native, calling each other directly

;; True symbolic differentiation (AST rewriting via calculus rules), not
;; numeric approximation — grad returns a new callable derivative function.
(define d/dx (grad (lambda (x) (+ (* x x) 1))))   ; d/dx[x^2 + 1] = 2x
(d/dx 3)   ; => 6

C ABI export — call Rusty-compiled code from anywhere

Every defrust function is a plain extern "C" symbol in an ordinary shared library (a defrust* group exports every member from its one .so) — there is no bridge to build, because the artifact rustc produces is already callable from C, Python, PyTorch custom ops, or anything else with an FFI. Nothing calls back into Rusty; the .so is self-contained.

  • Location: ~/.rusty/jit-cache/<symbol>_<source-hash>.so (.dylib on macOS, .dll on Windows), with the generated .rs source next to it.
  • Symbol name: the Lisp name, sanitized — rusty_ prefix, every non-[A-Za-z0-9_] character replaced by _ (so fib-nativerusty_fib_native).
  • Signature (one fixed shape regardless of arity): extern "C" fn(args: *const f64, len: usize) -> f64 — pass the arguments as a f64 array plus its length.
# verified end-to-end: no Rusty process involved
import ctypes, os
lib = ctypes.CDLL(os.path.expanduser("~/.rusty/jit-cache/rusty_fib_native_<hash>.so"))
f = lib.rusty_fib_native
f.restype  = ctypes.c_double
f.argtypes = [ctypes.POINTER(ctypes.c_double), ctypes.c_size_t]
args = (ctypes.c_double * 1)(30.0)
f(args, 1)   # => 832040.0, same native code Rusty calls

The cache is keyed by a hash of the generated source, so the path is stable until the function's definition changes; re-running defrust prints nothing new and reuses the same .so.

Graph IR

;; A computation-DAG IR (inspired by XLA/TVM) over the same restricted
;; numeric subset defrust compiles. Common-subexpression elimination falls
;; out of hash-consing during construction; constant folding (incl. pruning
;; an if-branch with a constant condition) and dead-code elimination are
;; explicit passes run afterward. graph-eval runs the optimized IR through
;; its own small interpreter (rebuilding the graph each call).
(graph-node-count (lambda (x) (+ (* x x) (* x x))))  ; => 3 (not 5 — CSE)
(graph-ir (lambda (x) (+ (* 2 3) x)))                ; => (((0 const 6) (1 param 0) (2 add 0 1)) 2)
(graph-eval (lambda (x y) (if (> x y) (- x y) (+ x y))) 5 2)  ; => 3

;; Kernel fusion (scalar): compile the optimized DAG to ONE native function
;; via the defrust pipeline — CSE/folding/DCE done once, at compile time.
;; Measured on a 282-node kernel: ~29x faster than tree-walking the same
;; lambda, and the residual cost is call dispatch, not the kernel body.
(define fk (graph-compile (lambda (x y) (+ (* (+ x y) (+ x y)) (/ x y)))))
(fk 3.0 2.0)   ; => 26.5, runs as native code (cached under ~/.rusty/jit-cache/)

;; Tensor ops flow through the same pipeline (tensors enter via params —
;; CSE means a shared (matmul x w) is computed once):
(graph-node-count (lambda (x w) (tensor-add (matmul x w) (matmul x w))))  ; => 4 (not 6)
(graph-eval (lambda (x w b) (tensor-add (matmul x w) b)) X W B)           ; linear layer, optimized

Tensor Autodiff (graph-grad)

;; Reverse-mode autodiff (backpropagation) over the Graph IR: one backward
;; sweep yields the gradient of a scalar loss w.r.t. EVERY argument, returned
;; as (loss grad-per-param...). Gradient rules emit more graph nodes, so
;; forward and backward pass share subexpressions via CSE, and the whole
;; thing goes through fold/DCE before a single evaluation pass.
(graph-grad (lambda (x) (* x x)) 5)          ; => (25 10)
(graph-grad (lambda (x) (relu x)) -3)        ; => (0 0)

;; the full deliverable: y = relu(xW + b), mean-squared-error loss —
;; gradients match PyTorch float64 autograd bit-for-bit:
(define r (graph-grad
  (lambda (x w b t)
    (/ (tensor-sum (tensor-mul (tensor-sub (relu (tensor-add (matmul x w) b)) t)
                               (tensor-sub (relu (tensor-add (matmul x w) b)) t)))
       4))
  X W B T))
(car r)     ; loss
(nth r 2)   ; dLoss/dW — ready for (tensor-sub w (tensor-mul (nth r 2) lr))

Benchmarked against single-thread float64 PyTorch 2.12.1 on the same machine (identical inits, final losses matching to 12+ significant digits): an 8×16→8 layer trained 1000 SGD steps runs ~11.8× faster in Rusty (49.5ms vs 585.8ms); at 64×256→64 Rusty is still ~1.4× faster (316ms vs 433ms per 100 steps). PyTorch is the yardstick, never a dependency. Data- dependent if and comparisons are not differentiable and refuse cleanly; non-scalar losses are rejected (reduce with tensor-sum or a mean).

Fused training kernels (graph-compile-grad)

;; graph-grad rebuilds and re-optimizes the graph on every call. The fused
;; alternative compiles the WHOLE forward+backward graph to one native
;; function, shape-specialized to the example arguments (values are only
;; used for their shapes) — every buffer size, loop bound, and matmul
;; dimension becomes a compile-time constant in the generated Rust:
(define step! (graph-compile-grad loss-fn X W B T))
(step! X W B T)   ; => (loss gX gW gB gT) — bit-identical to graph-grad

Same workloads, fused vs interpreted (results bit-identical): the 8×16→8 training loop drops 44ms → 15.8ms (~3×, and ~37× vs the PyTorch number above); 64×256→64 is parity (~125ms both ways) because naive-O(n³) matmul flops dominate — though that matmul itself got ~2.5× faster in v0.20.0 (slice-based ikj loops, same summation order, so every bit-for-bit claim still holds). Calling a kernel with differently-shaped tensors is an error — compile again for new shapes, as you would re-trace a JIT.

Agent / Tool Forms

(deftool name (params) "description" body...)
(tool-call "name" arg...)
(list-tools)
(react-loop goal max-steps)
(llm prompt temperature max-tokens)
(shell "command")

Actor-Model Message Passing

;; Agents are named handlers with FIFO mailboxes (std.lisp, Phase 3.2).
;; A deterministic scheduler pops one message at a time in spawn order and
;; runs the handler to completion; handlers may send! more messages.
(agent-spawn 'square (lambda (msg) (send! 'collector (* msg msg))))
(define total 0)
(agent-spawn 'collector (lambda (msg) (set! total (+ total msg))))
(send! 'square 3)
(send! 'square 4)
(run-agents)          ; => (quiescent 4) — ran until every mailbox emptied
total                 ; => 25

(run-agents 50)       ; explicit step cap: runaway systems return
                      ;    (hit-max-steps 50) instead of looping forever
(send! 'nobody 1)     ; => (error no-such-agent nobody) — errors are data
(agent-names) (mailbox-count 'square) (agent-reset!)

Cooperative and single-threaded by design (Rusty's runtime is Rc-based): concurrency means deterministic interleaved message handling, not threads. An LLM-backed agent is just a handler that calls llm.

See it all together: cargo run -- swarm.lisp — a proposer/verifier/ certifier swarm that synthesizes verified functions through message passing alone. The verifier runs the static gates first (an impure candidate is rejected without ever executing), counterexamples loop back to the proposer as feedback messages, two synthesis tasks progress interleaved, and every hop is traced. Deterministic — it's one of the golden tests.

Execution Tracing

;; Off-by-default event log around tool/LLM/shell/agent execution —
;; Rusty's own trace format, no OTel collector required.
(trace-on)
(tool-call "greet" "world")
(shell "echo hi")
(run-agents)                 ; actor scheduler logs send / agent-handle events
(trace-event 'my-kind 'my-name "custom data")   ; your own events
(trace-report)
; => ((0 12 tool-call greet 184 ()) (1 903 shell shell 2100 "echo hi")
;     (2 3100 send sq () ()) (3 3140 agent-handle sq () ()) ...)
;    rows: (seq t-micros kind name dur-micros data) — pure data, so
;    (save-model "trace.json" (trace-report)) just works.
(trace-off)

Checkpoint / Restore

;; Snapshot the global environment as plain, human-readable Lisp source —
;; data as literals, functions/macros/tools as their defining forms,
;; actor mailboxes and handlers included. Restore is just load.
(checkpoint "state.lisp")   ; => "state.lisp"
(load "state.lisp")         ; in a fresh process: picks up where you left off

;; An interrupted actor run resumes exactly: checkpoint mid-flight with
;; messages queued, restore elsewhere, (run-agents) finishes with the same
;; answer the uninterrupted run produces.

Closures re-close over the restored global environment — top-level definitions round-trip faithfully; keep durable actor state in globals via set!. defrust natives are compiled code and are listed in the checkpoint header as needing their defrust re-run.

Error Handling

(try-catch
  (/ 1 0)
  (e) (format "Caught: ~a" e))

Pattern Matching

(match value
  (("ok" v)    (format "got: ~a" v))
  (("err" e)   (format "error: ~a" e))
  ((_ . rest)  (format "list: ~a" rest))
  (_           "unknown"))

File Loading

(load "tools.lisp")
(load-relative "utils.lisp")

Arithmetic & Math

+ - * / mod expt abs sqrt floor ceiling round max min gcd
; Aliases: add sub mul div

Comparison

= < > <= >= eq? equal? not zero? positive? negative? odd? even?
; Aliases: eq gt lt ge le neq

Lists

cons car cdr list null? pair? list? length append reverse
nth member list-tail map filter foldl foldr for-each apply
; From std.lisp: zip take drop range iota flatten any? all?
; partition find remove-duplicates zip-with

Strings

string-length string-append string-append-list substring
string-ref string=? number->string string->number
symbol->string string->symbol string->list str
format    ; (format "~a + ~a = ~a" 1 2 3)  →  "1 + 2 = 3"
          ; ~a = any, ~s = quoted, ~% = newline, ~~ = tilde
string-join string-repeat string-contains? string-starts-with?

JSON

(json-encode (list (list "key" "val")))   ; → "{\"key\": \"val\"}"
(json-decode "{\"x\": 42}")               ; → (("x" 42))

Types

number? string? boolean? symbol? list? procedure? macro? tool? nil?
type-of    ; → symbol: number / string / boolean / lambda / tool / ...

I/O

(print x y z)      ; space-separated, with newline
(println x)        ; alias for print
(display x)        ; no newline, strings unquoted
(newline)
(error "msg")

Standard Library (std.lisp, auto-loaded)

; Math
square cube inc dec average clamp sign

; Lists  
last flatten zip zip-with take drop take-while drop-while
range iota sum product any? all? none? count find find-index
partition remove-duplicates flatten1 interleave

; Association lists
assoc assq alist-get record-set make-record get-field
(field key record)   ; accessor macro

; Functional
compose curry identity const flip negate memoize
map* filter* foldl*  ; pipeline-friendly (list-first)

; Threading macros
(-> x (f a) (g b))    ; thread first
(->> x (f a) (g b))   ; thread last

; Loop macros
(dotimes (i 10) body...)
(dolist (x lst) body...)
(while test body...)
(repeat n body...)

; Assertions
(assert condition ["message"])   ; message optional — defaults to the literal condition text

; Constraint embedding
(defun-constrained (safe-sqrt x)
  (assert (>= x 0))
  (sqrt x))
;; (safe-sqrt -4) raises "Assertion failed: (>= x 0)" instead of returning NaN

; Logic-driven loss (crisp propositional logic, not fuzzy/differentiable)
(logic-loss (and (implies P Q) (not R)))   ; => 0 if the formula holds, else 1

; Gradual typing — runtime contracts at call time; define/lambda are
; untouched, this is a separate opt-in macro. ti/return-type name an
; existing <type>? predicate (number, string, boolean, symbol, list, ...).
(define-typed (add-typed (x : number) (y : number)) : number
  (+ x y))
(add-typed 3 "oops")   ; => Error: expected number, got a different type

; Flow-sensitive static type checking — walks the body WITHOUT running it,
; narrowing types through if/let. Conservative: unresolvable types are
; "unknown" and never flagged, so this only reports provable mismatches.
(check-types (lambda (x) (string-length x)) '((x number)))
;; => ("string-length: argument 1 is statically known to be number, expected string")

;; narrowing overrides an outer declared type within a branch:
(check-types (lambda (x) (if (number? x) (+ x 1) 0)) '((x string)))  ; => ok

; Effect tracking — walks the body WITHOUT running it, reporting any
; operation it can prove is effectful (set!, print, shell, file I/O,
; llm/tool-call, memory, gensym, load); quoted data is never flagged.
(check-effects (lambda (x) (+ x 1)))            ; => pure
(check-effects (lambda (x) (print x) (+ x 1)))  ; => ("print: performs I/O")
(effectful? 'set!)                              ; => #t

; Bounded exhaustive checking — proves a property over EVERY combination
; of finite domains (not sampling); returns 'verified or counterexamples.
(check-exhaustive (lambda (x y) (= (+ x y) (+ y x)))
                  '((-2 -1 0 1 2) (-2 -1 0 1 2)))       ; => verified
(check-exhaustive (lambda (m e) (member (transition m e) modes))
                  (list modes events))                   ; => verified: transition is total & closed

; Proof-by-checker synthesis — a proposer (scripted, or llm-proposer) suggests
; candidates; only one passing every gate is accepted. Static gates (pure,
; types) run FIRST and never execute the candidate.
(define spec (list (list 'pure #t)
                   (list 'domains '((0 1 2 3)))
                   (list 'invariant (lambda (f x) (= (f x) (* x 2))))))
(synthesize-verified spec my-proposer 5)   ; => (verified #<lambda (x)> <attempts>) or (failed <reasons>)

; Tool specifications & chain certification — contracts for deftool tools.
; Tools are first-class callables; safe-call checks arity/types/precondition
; BEFORE the tool body runs; certify-tool-chain requires every tool to have
; a spec, be honest about its effects, and respect dependency order.
(deftool-spec save-note '((note list)) '(write-file) #f '(mk-note))
(safe-call mk-note "groceries")                     ; contract-checked invocation
(certify-tool-chain (list mk-note save-note))       ; => certified
(certify-tool-chain (list save-note mk-note))       ; => ((save-note deps-not-satisfied (mk-note)))

Native Tensors

; Rusty's own tensors — flat f64 buffers + shape, zero ML-framework deps.
(define t (tensor '((1 2 3) (4 5 6))))       ; shape inferred, ragged input rejected
(tensor-shape t)                              ; => (2 3)
(tensor-add t t)                              ; elementwise; scalars broadcast: (tensor-mul t 10)
(matmul (tensor '((1 2) (3 4))) (tensor '((5 6) (7 8))))   ; => #<tensor 2x2 [19 22 43 50]>
(transpose t) (tensor-map square t) (tensor-sum t)
(zeros '(2 3 4)) (ones '(2))

; a linear layer is three calls:
(define (linear-forward x W b) (tensor-add (matmul x W) b))

; the same expression runs through the Graph IR optimizer too (CSE/DCE) —
; see the Graph IR section above:
(graph-eval (lambda (x W b) (tensor-add (matmul x W) b)) X W B)

Model Serialization

; Rusty's own model format: a versioned JSON envelope over data values —
; numbers, strings, bools, symbols, lists, tensors. Symbols and tensors are
; tagged so they round-trip losslessly (unlike json-encode, which flattens
; symbols to strings); finite f64s are bit-exact across save/load.
(define model (list (list 'W (tensor '((0.5 1) (1.5 2))))
                    (list 'b (tensor '((4 5))))))
(save-model "model.json" model)     ; => "model.json"
(equal? model (load-model "model.json"))   ; => #t

; Graph IR state is already list data, so it serializes as-is:
(save-model "graph.json" (graph-ir (lambda (x w) (matmul x w))))

; code values are rejected by design — serializing live environments is
; checkpoint/restore (roadmap 3.2), not model data:
(save-model "f.json" (lambda (x) x))   ; => error

Tail Call Optimization

Rusty implements TCO via an explicit trampoline loop — stack-safe recursion to arbitrary depth:

(define (sum-to n acc)
  (if (<= n 0) acc
      (sum-to (- n 1) (+ acc n))))

(sum-to 1000000 0)   ; no stack overflow

Testing

./run_tests.sh
# or individually:
cargo run -- tests.lisp
cargo run -- new-features.lisp
cargo run -- hello.lisp

5-Year Roadmap

Phase 1 (Q1–Q4 2025): Symbolic Transformation Layer

Build macro DSLs for computation graph generation and optimization. Generate code on par with PyTorch.

Phase 2 (Q1–Q2 2026): Verifiable AI Systems

Integrate formal verification (Lean/Coq). Prove tool correctness. Add gradual typing.

Phase 3 (Q3–Q4 2026): Production ML Integration

Zero-copy tensor interop with PyTorch/JAX. Multi-agent coordination. 10x performance.

Phase 4 (Q1–Q4 2027): Ecosystem & Libraries

Killer apps: symbolic regression, proof synthesis, robot control. Package manager.

Phase 5 (Q1 2028+): Maturity

v1.0.0 release. IDE support, LSP, debugger. Production deployments.

→ Read the full roadmap →


Contributing

Rusty welcomes contributions! Areas of interest:

  • Performance optimization — Reduce cloning, implement copy-on-write
  • Macro system — New examples, DSL patterns
  • Python bridge — More bindings, better interop
  • Documentation — Tutorials, examples, API docs
  • Tests — Coverage, edge cases, property-based testing

See ROADMAP.md for planned features and ARCHITECTURE.md for deep-dive technical design.


License

Copyright (c) 2026 Nicholas Vermeulen.

Rusty is free software licensed under the GNU Affero General Public License, version 3 or later (AGPL-3.0-or-later) — see LICENSE. In short: you may use, study, modify, and redistribute it, but any modified version you run to provide a network service must make its complete source available to that service's users.

Commercial licensing — if the AGPL's terms don't fit your use (for example, embedding Rusty in a closed-source product or network service), a commercial license is available on inquiry. Contact Nicholas Vermeulen to discuss terms.

In memory of my brother.

About

A modern Lisp implementation in Rust with TCO, macros, and full features. Ported from Python prototype.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors