Architecture
Rex is implemented as a small set of focused crates that form a pipeline:
- Parsing (
rex-parser): converts source text into arex_ast::CompilationUnit { decls, body }. - Typing (
rex-typesystem): Hindley–Milner inference + ADTs + type classes; produces arex_typesystem::TypedExpr. - Execution (
rex-engine): builds the host environment, prepares typed code into aCompiledProgram, and evaluates it to a runtimerex_engine::Handle.
The crates are designed so you can use them independently (e.g. parser-only tooling, typechecking-only checks, or embedding the full evaluator).
Crates
rex-ast: shared AST types (Expr,Pattern,Decl,TypeExpr,CompilationUnit, symbols, spans).rex-parser: source parser. Entry point:rex_parser::parse.- Parsing enforces a fixed cap on AST nesting.
rex-typesystem: type system. Entry points:TypeSystem::new()to create an explicit typing environment.infer_typed(&mut ts, expr)/infer(&mut ts, expr)for type inference.- The inference implementation itself lives in
rex-typesystem/src/inference.rs;typesystem.rsnow holds the shared core types, environments, and registration logic. - For untrusted code, set
rex_typesystem::TypeSystemLimits::safe_defaults()before inference.
rex-engine: host environment builder, compiler, and runtime evaluator. Entry points:Engine::with_prelude(state)?to inject runtime constructors and builtin implementations (statecan be()).standard_type_system()?to create a typing environment with the engine-owned standard prelude.Engine::into_compiler()to consume the prepared engine into a compilation view.Engine::into_evaluator()/Compiler::into_evaluator()to consume preparation state into an evaluator.Compiler::compile_programto prepare a parsed program entry point intoCompiledProgram;Compiler::infer_*for type-only checks.Evaluator::run(compiled, inputs).awaitto execute one prepared program.inputsis aBTreeMap<String, Handle>for the program’s externalmaininterface;runconsumes the evaluator, compiled program, and input map.Enginecarries host state asEngine<State>(State: Clone + Send + Sync + 'static); typedexportcallbacks receive&Stateand returnResult<T, EngineError>, typedexport_asynccallbacks receive&Stateand returnFuture<Output = Result<T, EngineError>>, while handle-based native APIs (export_native*) receiveContext<State>.- compile and evaluation APIs return
EngineError; convenience entry points that cross phases returnExecutionError. - Host module injection API:
Module+Export+Engine::inject_module.
rex-proc-macro:#[derive(Rex)]bridge for Rust types ↔ Rex ADTs/values.rex: CLI front-end around the pipeline.rex-lsp/rex-vscode: editor tooling.
rex-engine is organized internally around the same phases:
builder/owns engine construction, module injection, import qualification/rewrite, host export registration, and registry markdown.compiler/owns typechecking andCompiledProgramconstruction.evaluator/owns execution, scheduling, native dispatch,Context, and the runtime core.modules/,value.rs, andconfig.rshold shared module identities, heap values/GC roots, and runtime options.
Design Notes
- Typed preparation:
rex-engineprepares code into a typed form before execution. The currentCompiledProgramstores a typed AST plus the environment snapshot needed to run it. - Single-shot execution: evaluation is intentionally one-shot.
CompiledProgramis moved intoEvaluator::runwith its runtime input map, consuming the evaluator as well. Prepare all required declarations/modules before constructing or consuming the evaluator. - Same-lineage runtime model: a
CompiledProgramis intended to run on the evaluator produced from the same compiler. Rex programs are supplied as source and compiled per run, so the engine does not expose a portable compiled-artifact or cross-runtime linking model. - Prelude ownership:
rex-engineowns the standard prelude source, standard typing environment, and runtime contract. The split is:- typeclass and instance declarations written in Rex at
rex-engine/src/prelude/typeclasses.rex rex-engine/src/prelude/type_system.rsbuilds the prelude-enabledTypeSystem, including ADTs, parsed declarations, and primop schemesrex-engine/src/prelude/mod.rsparses the Rex source and injects runtime method bodies/native implementations forEngine::with_prelude(state)?rex-typesystemexposes generic registration/inference APIs and does not own the standard prelude
- typeclass and instance declarations written in Rex at
- Depth bounding: Some parts of the pipeline are naturally recursive (parsing deeply nested parentheses, matching deeply nested terms). The parser enforces a fixed AST-depth cap, and the typechecker-limit API provides bounded recursion for production/untrusted workloads.
- Import-use rewrite/validation: module processing resolves import aliases across expression vars, constructor patterns, type references, and class references; unresolved qualified alias members are rejected as module errors before runtime.
Intentional String Boundaries
Rex now prefers structured internal representations (for example NameRef, BuiltinTypeId,
CanonicalSymbol, and module/type/class maps) across parser, type system, evaluator, and LSP
rewrite paths. Remaining string usage is intentional in these boundary layers:
- Source text and parsing: the parser accepts source strings by definition.
- Human-facing diagnostics and display: error messages, hover text, CLI rendering, and debug output stringify symbols/types for readability.
- Protocol/serialization boundaries: JSON/LSP payloads are string-based and convert structured internal symbols/types at the edge.
- Filesystem/module specifiers: import specifiers and path labels are textual before being resolved into structured module identities.
Non-goal for this pass:
- Eliminating all
.to_string()calls globally. The design target is to avoid stringly-typed core semantics, not to remove string conversion at UI/protocol boundaries.