Memory safety,
without the GC tax.
Riddle eliminates memory errors at compile time with move semantics and borrow checking, and keeps every value that never outlives its frame on the stack via interprocedural escape analysis. Not one lifetime annotation, and no garbage collector running the whole way.
struct Point {
x: i32,
y: i32,
}
fun distance_squared(point: Point) -> i32 {
point.x * point.x + point.y * point.y
}
fun main() {
let point = Point { x: 3, y: 4 };
let value = distance_squared(point);
print(value)
} - lifetime annotations
- 0 lifetime annotations
- runtime ABI functions
- 5 runtime ABI functions
- compiler stages
- 9 compiler stages
- backend output
- C11 backend output
Language features
Why Riddle
Ownership, escape analysis and deterministic destruction together — safety that needs neither annotations nor an always-on collector.
Ownership is settled at compile time
Assignment, argument passing and returning all transfer ownership. The move checker catches use-after-move, conflicting borrows, and assignment or movement during a borrow — all at compile time, with no runtime checks.
struct Foo {
x: i32,
y: i32,
}
fun main() {
let a = Foo { x: 1, y: 1 };
let b = a;
print(a); // error: a has already been moved
print(b);
} Scalars, shared references, raw pointers and named function items are copyable by default; &mut T and closure values are not.
- User types opt into copy semantics by implementing
std::marker::Copy; the compiler verifies every field and enum payload - Field access alone does not move the whole struct;
matchdestructuring records per-field partial moves, and untouched sibling fields stay usable -
Option<T>andResult<T, E>areCopyonly when every payload type isCopy
Return a reference without writing a lifetime
Riddle has no lifetime syntax. An interprocedural fixpoint escape analysis computes a "leaked parameters / returned-from parameters" summary for every function, and uses it to decide whether a local stays on the stack or is promoted to the conservative non-moving GC heap. Storage location never relaxes move or borrow checking.
struct Foo {
x: i32,
y: i32,
}
fun make_ref() -> &Foo {
let foo = Foo { x: 1, y: 2 };
&foo // escapes the frame, so foo is promoted to the GC heap
} | Analysis result | MIR allocation |
|---|---|
| Non-escaping local that needs no stable address | SSA value, no allocation emitted |
| Non-escaping local that is mutable or captured by reference | Alloca, stack storage |
| Escaping local | HeapAlloc, GC heap storage |
| Non-escaping / escaping closure environment | Alloca / HeapAlloc |
The GC only decides addresses — destruction stays deterministic
Types implementing `std::ops::Drop` run their destructor deterministically when the owning scope ends. Escaping to the GC heap changes a value’s address, not when it is destroyed; drop flags make sure a moved-out value is never destroyed twice.
struct FileHandle {
raw: i32,
}
impl Drop for FileHandle {
fun drop(&mut self) {
// release the external resource behind raw
}
} Locals, parameters, pattern bindings, iteration elements, aggregate fields and closure environments are all covered.
-
Drop + Copy, calling the destructor directly, and moving a field out of an explicitDroptype are all rejected - A
forloop’s current element, its iterator and its early-exit paths each get their own drop scope
Abstraction that costs nothing at runtime
Traits support default methods, associated types with defaults, supertrait declarations and transitive bounds. Generics are monomorphized in the C backend, and const generics lift lengths into the type.
trait Summary {
fun title(&self) -> &str;
fun summarize(&self) -> &str {
self.title()
}
}
struct Buffer<T, const N: usize> {
data: [T; N],
} An impl that does not override a method inherits the trait’s default body.
-
<T: A + B>andwhereclauses;whereon an impl is checked against the Paterson condition - Operators dispatch to user types through
#[lang = "..."]traits; scalar operations lower straight to native C operators with no wrapper functions -
==/!=checkPartialEq, ordering comparisons checkPartialOrd, and heterogeneous right-hand-side impls are supported
The compiler tells you exactly which arm is missing
`match` performs recursive exhaustiveness checking over enums, booleans, `()`, integers, tuples and structs. A non-exhaustive integer match does not report a vague error — it reports the uncovered contiguous ranges.
fun classify(n: i32) -> i32 {
match n {
x if x < 0 => -1,
0 => 0,
_ => 1,
}
} Exhaustiveness gaps are reported as E0039 together with the uncovered ranges.
- A failed guard falls through to the following arms; guarded arms do not count toward static exhaustiveness
- Unit, tuple and struct enum variant patterns are supported, and payload bindings are visible in both the guard and the arm
Talking to C, with the boundary written down
Imports inside an `unsafe extern "C"` block are unsafe by default, and you opt individual functions back in with `safe fun`. The C backend provides no built-in helpers keyed on function names — every `extern "C"` declaration becomes an ordinary external symbol.
unsafe extern "C" {
safe fun abs(x: i32) -> i32;
fun malloc(size: usize) -> *mut u8;
}
fun main() {
let value = abs(-42);
let pointer = unsafe { malloc(16) };
} Dereferencing and indexing raw pointers requires an unsafe context.
-
&strmaps toconst char*in C imports; exported definitions with a body keep the{ ptr, len }fat pointer -
unsafe funandunsafe fun(...) -> Tfunction types, with one-way safe-to-unsafe conversion
Compilation pipeline
Source to C, in nine stages
riddlec already runs the complete frontend and the C backend. Every stage is covered by tests in the repository.
- 01
Lexing and parsing
IncrementalParserexposes a partial re-parse API - 02
AST wrapping
A uniform syntax tree; attributes travel with their items
- 03
HIR lowering
Includes the E0040 / E0050 / E0051 / E0052 diagnostics
- 04
Scope graph and name resolution
Fragment-based incremental scope graph with partial invalidation
- 05
Type checking
A reusable
IncrementalTypeChecker - 06
Escape analysis
Interprocedural fixpoint deciding stack versus GC heap allocation
- 07
Move checker
Use-after-move, conflicting borrows, assignment and movement during a borrow
- 08
MIR lowering
SSA form with Phi nodes, basic blocks and
Alloca/HeapAlloc - 09
C code generation
Emits C11 that calls the
rgcruntime ABI
With no backend selected, riddlec stops after move and borrow checking; MIR lowering only continues when backend code is actually needed.
Runtime
Five functions make up the runtime ABI.
Every runtime provider implements these five symbols: initialize the stack, allocate, resize and free memory, and trigger collection.
void rgc_init(void *stack_bottom);
void *rgc_alloc(size_t size);
void *rgc_realloc(void *ptr, size_t size);
void rgc_free(void *ptr);
void rgc_collect(void); crates/gc ships the default conservative, non-moving mark-sweep implementation; point [runtime].source in Clue.toml at your own provider to replace it.
- No Boehm GC
clue buildcompiles the generated C and the runtime source with your system C compiler — no extra third-party runtime dependency.- Conservative and non-moving
- Only values that escape analysis proves outlive the current frame reach the GC heap. Everything else stays on the stack, and the collector never relocates objects.
- Each allocation hook has a job
rgc_reallocgrowsVectorbuffers andrgc_freereleases provider-owned memory; a non-GC provider may ignore the stack bottom and makergc_collecta no-op.- Fully replaceable
- The runtime provider is chosen by
clueand accepts custom providers — embedded or specialised targets can bring their own allocator.
What's new in v0.2.3
A more expressive language and a broader library
This release adds bracket lambdas, vec! and range expressions, iterator adapters, new file / time / random / parse modules, and a sharper LSP with nine quick fixes and Clue.toml support.
- 01
Bracket lambdas
[v -> v * 2]writes a short anonymous function with inferred parameter and return types, andvalues.map [v -> v * 2]calls it inline. They lower to the same closures asfun;vec![a, b]and0..5ranges round out the new syntax. - 02
A broader standard library
Collections gained
remove,insertandsort; iterators gained lazymap/filter/zipand eagercount/fold/find; newstd::fs,std::time,std::randomandstd::parsemodules cover files, sleeps, randomness and number parsing. - 03
A sharper language server
Nine diagnostic-derived quick fixes, lambda-parameter and method-chain type hints,
Clue.tomlschema diagnostics with completions, organize imports, expand selection, document links and pull diagnostics. - 04
Compiler foundations
riddlecaccepts multiple input files, constants evaluate at compile time for array lengths and const generics, slices crossunsafe extern "C"as pointer/length pairs, and the GC uses a hashed object registry with an adaptive threshold.
Toolchain
One manager, four binaries
ridup manages Riddle versions; a toolchain is clue, riddlec, riddle and riddle-lsp. Prebuilt archives are on GitHub Releases; installing from source needs a recent Rust stable.
ridup
Toolchain manager
Selects and runs installed toolchains. The stable, nightly and canary channels each get their own copy and never overwrite one another. It manages Riddle versions only — the selected clue still finds a host C compiler.
ridup toolchain install stable | nightly | canary
-
stableandnightlytake the GitHub Release archive and verify its SHA-256 before replacing the old toolchain -
canaryfetches the latestmaincommit and runscargo build --workspace --releaselocally — Rust and Cargo are enough, Git is not needed -
ridup toolchain link dev <dir>turns a local build directory into a toolchain -
riddle-toolchain.toml,RIDUP_TOOLCHAINandclue +dev buildall change the selection - Copy or hard-link ridup as
clue,riddlec,riddleorriddle-lspand it proxies into the selected toolchain - Downloads and Canary builds honour
HTTPS_PROXYand the other standard proxy variables
riddlec
Compiler CLI
Checks Riddle source and generates C — the frontend checks and the C backend live in one binary.
riddlec [--verbose] [--backend c] [--target <triple>] [--output <file>] <file>...
-
--backend cemits C that calls thergcABI -
--target <triple>selects a target;RIDDLE_TARGETandClue.tomlcan set it too - Automatically appends
std/lib.ridto your source - Without a backend it only runs the frontend checks and skips MIR lowering
riddle
Unified tools CLI
Provides the unified Riddle tools entry point, currently with source formatting.
riddle fmt [--emit files|stdout|check] [--check] <file>...
- Formats files or standard input, or checks without writing
-
--tab-sizesets indentation width and--hard-tabsselects tabs - Shares the formatter with LSP
textDocument/formatting
clue
Project builder
Creates, checks, builds and runs Riddle projects. Binary projects produce a native executable; library projects emit .rlib / .rmeta metadata plus static or dynamic libraries.
clue init | new | check | build | run [--target <triple>]
- Uses
CCstrictly when set, otherwise finds a system compiler that can complete a C11 compile and link -
--target,RIDDLE_TARGETand[build].targetinClue.tomloverride the host in that order -
ridup target add <triple>installs the target runtime; linking still needs the target C toolchain - Path, git and registry dependencies resolve into a lockfile that pins exact versions
-
clue buildkeeps.clue/build/<name>.cso you can inspect the output -
[runtime].sourceinClue.tomlcan point at a custom runtime implementation
riddle-lsp
Language server
Built on tower-lsp. Parse errors, HIR diagnostics, type errors and move / escape diagnostics are all pushed over LSP.
- Project-wide completion that prefers unsaved content from every open file
- Semantic tokens that distinguish free functions, methods, structs, enums and traits
- Error codes link into the error index; notes and fixes are attached as
note:/help:
Editor support
In the editor you already use
The editors directory in the repository ships ready-to-use configuration.
- Helix
- VS Code
- Zed
- IntelliJ IDEA 2026.1+
Working today
-
.ridfile recognition - Diagnostics across Clue projects, unsaved files and unopened modules
- Parse, type, move and borrow diagnostics
- Semantic highlighting for functions, methods, structs, enums, traits, parameters and mutable bindings
- Inlay hints for locals whose return type comes from another module
- Cross-file completion including fields, methods, enum variants and associated functions
- A code action for mutable closure bindings
- Incremental document sync and semantic token deltas
- Hover, go to definition and implementation, references, rename and formatting
- Workspace indexing and automatic imports
Still evolving
- More extensive semantic refactoring actions
Get started
Five commands to your first program
Install a toolchain with ridup and switch channels whenever you want, or download the archive for your platform from GitHub Releases and add it to PATH.
- 1
Install the toolchain
Installing a channel does not select it, so pick the default too.
ridup showthen tells you which toolchain is active and why.cargo install --git https://github.com/riddle-lang/ridup ridup toolchain install stable ridup default stable - 2
Create and run a project
clue runperforms the same build asclue build, then runs the resulting executable.clue new hello cd hello clue check clue build clue run
clue build keeps .clue/build/hello.c. When CC is set Clue uses it strictly; otherwise it searches for cc, gcc, clang and their versioned variants — plus clang-cl and cl on Windows. To type clue or riddle directly, copy or hard-link ridup under that name; without that proxy, run ridup run stable clue new hello.
Project status
This is a technology preview, and we say so
The v0.2.3 language and toolchain may still change incompatibly. Here is the honest boundary of what works.
Working today
- Type checking, the move checker, borrow and escape analysis
- Generics, const generics, traits and associated types
- Closures with
Fn/FnMut/FnOncecall-capability checking -
matchwith recursive exhaustiveness checking -
fordriven byIntoIterator/Iterator -
unsafesemantics and C FFI - Arrays, slices, strings and the Vector standard library
- Drop, operator overloading and deterministic C backend behavior
- Tuple types, tuple patterns and tuple enum variants
- Procedural macros and standard derives including Default, Hash and Ord
- Formatted output with positional, named and debug placeholders
- Bracket lambdas
[v -> v * 2]alongsidefunclosures - Iterator adapters, collection removal and
vec! - File, time, random and parse modules in the standard library
-
riddle fmtsource formatting CLI sharing its formatter with the LSP - Cross-target builds and target runtimes for seven supported triples
- A built-in standard library, C11 codegen, project tooling and an LSP
Current limitations
- Formatting supports positional, named and
:?specifiers; width, alignment and fill are not yet implemented - Floating-point remainder is unsupported;
Rem/RemAssignare integer-only for now - Generics lean on monomorphization and do not yet cover all of Rust’s generic power
- The C backend is the only backend; producing an executable relies on a system C compiler
- Escape analysis works at whole-local granularity, without field-level splitting
- Closures capture whole bindings, without field-level precision
- There is no bare function-pointer type; callables are declared with
impl Fn(...) -> Tordyn Fn(...)
Syntax and ABI stability are not guaranteed. Treat Riddle as a language worth trying seriously — not one to ship to production.
Roadmap
What comes next
This roadmap is the project’s actual priority order: correctness first, language capability second, then toolchain and ecosystem. It reorders with feedback and promises no dates.
The language core
Correctness first — finish the foundations of the safety semantics.
- Labeled
break/continue, and or / range / slice patterns
Standard library and toolchain
As the language can express more, the library and the tools have to keep pace.
- A larger standard library: buffered IO, full format specifiers and a richer parsing surface
After the core stabilizes
These are deliberately sequenced after the core semantics settle — no head start.
- Concurrency and async / await
- Backends beyond C
- The stability promise for syntax and the runtime ABI
Want to shift the priorities? Tell us on GitHub Issues which item you need most.
Write a few lines of Riddle
Nothing to install — compile and run it right in your browser.