- Rebol 55.3%
- C 35.2%
- HTML 5.1%
- C++ 2.2%
- JavaScript 1.1%
- Other 0.8%
|
Some checks failed
Linux (fast) / RUT subset (linux-gcc) (push) Failing after 54s
The fields are parsed and shape-checked only; nothing reads them yet. Type: is limited to module, library and rebol-extension. Documentation fields and Needs: stay legal. Name: moves from extra-fields to header/name (istar #370). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|---|---|---|
| .claude | ||
| .cursor/skills/compact | ||
| .forgejo/workflows | ||
| benchmarks | ||
| docs | ||
| examples | ||
| ext | ||
| lib | ||
| make | ||
| packages | ||
| src | ||
| std | ||
| support | ||
| tests | ||
| tools | ||
| vendor | ||
| web | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| AGENTS.md | ||
| BUILD | ||
| CLAUDE.md | ||
| lib-rebol3-bulk-macos-x64.dylib | ||
| LICENSE | ||
| README.md | ||
| recoil-do.reb | ||
| recoil.r3 | ||
| rut.r3 | ||
Recoil
A statically typed language with ownership-based memory safety, a static borrow checker, a rich type system, and Rebol-inspired syntax.
New to Recoil? Start with the quick-start build below, then follow the user guide for the language and package workflow.
On this page
- Quick start
- Language overview
- Types at a glance
- Syntax examples
- Ownership model
- Testing
- Compiler internals
- Full documentation
Quick start
Recoil transpiles to C. To build programs from this checkout, install:
- an Oldes Rebol3 interpreter, 3.22.0 or
newer (shown below as
r3; use your local executable name if it differs). Earlier builds cannot parse->in generic constraints and fail to load the test suite; - a C toolchain — GCC is the default, and a supported target toolchain may be
selected with
--targetor--cc.
Compile and run a program:
r3 -s recoil.r3 myprogram.rcl
Transpile without compiling the generated C:
r3 -s recoil.r3 --transpile -o myprogram.c myprogram.rcl
Build a library:
r3 -s recoil.r3 --lib mylib.rcl # shared library
r3 -s recoil.r3 --lib mylib.rcl --static # static library
Run the test suite:
r3 -s rut.r3
-s disables Rebol security for the wrapper process; builds and tests need it
to write generated C, artifacts, and cache files. The CLI also supports project
and package commands such as init, install tls, install f00, fetch,
doctor, and repair. See the command-line reference for
the complete syntax, targets, and compiler/linker flag options.
Docker build environments are available in
support/docker/: Debian slim uses glibc and Alpine uses
musl.
Documentation
Build and run
- User guide — language overview and package workflow
- Command-line reference — options, targets, and toolchain flags
- Packages — project files, dependencies, and security modes
- ESP32 / ESP-IDF builds
- Concurrency: tasks, timers, and yielding I/O
Learn the language
Contribute or extend
- Compiler pipeline
- Authoring Recoil extensions
- Diagnostic contract for compiler work
- Release notes (0.11.0)
Overview
Recoil is a low-level, statically-typed programming language that uses Rebol's expressive syntax while providing compile-time memory safety. It transpiles to C, making it suitable for:
- Systems programming with memory safety
- Embedding in other applications
- Writing small, dependency-free binaries
- Interfacing with C libraries via FFI
The compilation pipeline:
.rcl source → Prepare / parse → AST → Monomorphize generics → Borrow check
→ Fact analysis → IR → Optimize → C → target toolchain / linker
For an explanation of each stage, see the compiler pipeline.
Compiler Diagnostics
Compile-time failures are reported in two forms:
- a human-readable string such as
TYPE ERROR: ... - a structured diagnostic object attached to the raised Rebol
error!
The user-facing format, code ranges, and helper API are documented in docs/user/diagnostics.md.
If you are changing compiler stages, do not introduce new raw do make error!
sites for user-facing diagnostics. Use the stage-local diagnostic helpers
documented in
docs/user/diagnostic-contract.md.
Types at a glance
The built-in types reference is the authoritative guide to construction, ownership, actions, and C representations. The common groups are:
- Copy values: numeric types,
logic!,char!,size!,none!,file!(a path value), andenum!. - Move-sensitive values include
error!,port!,c-pointer!, andmap!. Normalstring!,block!,vector!, andarray!values, and typeddict!handles, are reference-counted and shared on assignment; add#uniquewhen a native series must be move-only. - Collections:
vector!,array!,dict!,map!,block!,binary!, andc-array!. - Concurrency:
channel!— a typed, bounded queue for passing values betweengocoroutines (see concurrency). - Borrowed views:
slice!— a zero-copy view over a series. - User-defined types:
struct!,enum!,sum!,fn-ptr!,fsm!, andfst!.
c-string!, c-struct!, and c-fn! are ABI-facing forms for FFI and runtime
boundaries. Prefer string! for ordinary owned text.
none! vs void!
-
none!is a safe value representing "absence of value":x: none ; valid — inferred none! -
void!represents "no return value" and cannot be assigned:f: func [] [] ; returns void! x: f ; ERROR: Cannot assign void
Syntax
Variables
Bindings are implicitly typed — the compiler infers the type from the
right-hand side. Use make only for a specific type or a typed container.
x: 42 ; i32! (inferred)
pi: 3.14159 ; f64!
big: make i64! 42 ; explicit non-default width
msg: "hello" ; inferred string!
Functions
add: func [
a [i32!]
b [i32!]
return: [i32!]
] [
return a + b
]
print add 10 20 ; 30
Borrowed parameters use get-word (:name):
greet: func [:name [string!] return: [none!]] [print name]
msg: "hello"
greet :msg ; borrow — msg stays owned
Vectors
; Heap-allocated (default)
arr: make [vector! [i32! 5]] [1 2 3 4 5]
; Stack-allocated storage must be local to a function
show-stack-vector: func [
"Print the first element of a stack-allocated vector."
] [
stack-arr: make [vector! [#stack i32! 5]] [1 2 3 4 5]
print stack-arr/0 ; 1
]
show-stack-vector
; Mutable
nums: make [#mutable vector! [i32! 5]] [10 20 30 40 50]
nums/0: 42 ; element access
print nums/0 ; 42
Structs
point!: make struct! [x: i32! y: i32!]
p: make [#mutable point!] [x: 10 y: 20]
print p/x ; 10
p/y: 30 ; field assignment
Control Flow
; If
x: 12
if x > 10 [print "big"]
if [x > 10] [print "big"] ; block condition also works
; Either (if-else)
either x > 10 [print "big"] [print "small"]
; While
i: mutable 0
while [i < 5] [print i i: i + 1]
; Repeat (0-indexed)
repeat i 10 [print i] ; prints 0-9
Method Chaining (Refinements)
add: func [a [i32!] b [i32!] return: [i32!]] [return a + b]
mul: func [a [i32!] b [i32!] return: [i32!]] [return a * b]
result: make i32! 10 /add 5 /mul 2 ; mul(add(10, 5), 2) = 30
Closures
; Explicit capture (by value)
x: 10
adder: make fn-ptr! func [#capture [x] a [i32!] return: [i32!]] [
return a + x
]
result: make i32! adder 5 ; 15
; By-reference capture (mutation)
count: mutable 0
counter: make fn-ptr! func [#capture [:count] return: [i32!]] [
count: count + 1
return count
]
Enums
Color!: make enum! [red green blue]
c: make Color! Color!/red
if c = Color!/blue [print "blue"]
Enum values are integer-backed copy values. By default, variants count up from
0 in declaration order. You can also give explicit integer values; later
implicit variants continue from the previous value:
Status!: make enum! [idle 10 busy done]
print Status!/idle ; 10
print Status!/busy ; 11
print Status!/done ; 12
Use Type!/variant path access at call sites. Variant names are scoped by their
enum type in generated C, so natural names such as connect, idle, or error
are safe inside different enums.
Slices
Zero-copy views into vectors or strings:
nums: make [#mutable vector! [i32! 8]] [1 2 3 4 5 6 7 8]
; slice! s points at nums[2..4]
s: make slice! at nums 2 3
; Read through a slice
val: make i32! s/0 ; nums[2]
; Write through a slice
s/1: 99 ; nums[3] = 99
Error Handling
Recoil has a structured error system. Arithmetic overflow, division by zero, and out-of-bounds access all set a global error state automatically.
; Create a user error
e: make error! "something went wrong"
; Check the global error state (overflow, div-zero, bounds)
divisor: 0
result: make i32! (10 / divisor)
if errored? [print "math error"]
; Check a specific error value
if error? e [print "local error"]
Defer
defer schedules cleanup code to run at function exit in LIFO order:
run: func [] [
defer [print "always runs"]
defer/error [print "runs only after an error"]
]
run
defer/error runs only when the enclosing function returns with an active
runtime error. See Error handling for the
complete model.
addr-of
Take the address of a variable:
x: 42
p: addr-of x
Unsafe Namespace
Direct access to C standard library functions without FFI boilerplate:
buf: unsafe/malloc 1024
unsafe/memset buf 0 1024
unsafe/free buf
FFI
math: foreign <stdlib.h> [
abs: "abs" [i32! return: i32!]
]
x: make i32! math/abs -7
print x
The header tag emits the matching C #include. For opaque C pointers, callback
types, exported symbols, and external globals, see the FFI and ABI
reference. Declare imported C globals with #extern:
#extern c-pointer! some_global_from_c
Type Casting
a: 10 ; i32!
x: make f32! as f32! a ; explicit cast i32! -> f32!
c: make f32! (x + as f32! 5) ; mixed numeric types require an explicit cast
Ownership Model
Recoil uses Rust-inspired ownership semantics for memory safety without garbage collection. See Ownership, Borrowing, And Mutability for the detailed current model.
Copy Types
Primitives (i32!, f64!, logic!, etc.) are copied on assignment:
a: 10
b: a ; a is copied
print a ; OK — a is still valid
Shared and move-sensitive values
Native series such as string!, block!, vector!, and array!, and typed
dict! handles, are shared by default, so an ordinary assignment keeps the
original binding usable. Add #unique to opt a native series into move-only
ownership.
Other values, including error!, port!, c-pointer!, and map!,
move on an owned transfer:
e1: make error! "oops"
e2: e1 ; ownership moves to e2
if error? e2 [print "handled"]
if error? e1 [print "unreachable: e1 was moved"] ; ERROR: use after move
Borrowing
Pass by reference with get-word (:var):
greet: func [:name [string!]] [print name]
msg: "hello"
greet :msg ; msg is borrowed, not moved
print msg ; OK, msg is still owned
print itself borrows owned strings. print msg does not move msg; owned
temporary string expressions passed to print are cleaned up after printing.
Mutation
Prefer expression-based flow and use mutation only where an API intentionally updates existing state. Type attributes describe values that need mutation or growth capabilities:
ready: true
count: 1
status: either ready ["ok"] ["failed"]
next-count: count + 1
s: make string! "hello"
s/0: #"H" ; ERROR: 's' is not mutable
buf: make [#mutable string!] "hello"
buf/0: #"H" ; OK
grow: make [#flexible [string! 16]] none
Branching
Values moved in any branch are conservatively considered moved afterward:
handle: file/open %data.txt
condition: true
either condition [
moved: handle
] [
print "not moved in this branch"
]
file/close handle ; ERROR: handle might have moved
Module Reference
| File | Role |
|---|---|
recoil.r3 |
Entry point - CLI, GCC execution |
src/compiler.r3 |
Orchestrates all stages |
src/parser.r3 |
Source → AST (to-ast) |
src/compiler/generics.r3 |
Generic monomorphization |
src/borrow-checker.r3 |
Ownership analysis (analyze) |
src/fact-analysis.r3 |
Refined-type and constraint flow analysis (analyze-facts) |
src/ir.r3 |
AST → IR (ast-to-ir) |
src/compiler/optimize.r3 |
IR optimization (optimize) |
src/c-generator.r3 |
IR → C code (emit-c) |
src/core/ffi.r3 |
FFI parsing and type mapping |
src/core/runtime.r3 |
Runtime declarations and helper registrations |
src/core/tools.r3 |
Operator table, name mangling utilities |
src/core/datatypes.r3 |
Type-action mappings |
src/core/shared.r3 |
Global state containers |
src/parser/*.r3 |
Parser helper modules (specs, types, grammar, comptime) |
src/parser/rules/*.r3 |
Runtime-loaded parser rule assets (term, grammar, control, statements, type-decls) |
src/codegen/*.r3 |
Codegen helper modules (expr, statements, decls, parse, module-globals) |
src/compiler/*.r3 |
Compiler helper modules (modules, generics, alpha, optimize, header/C support) |
Testing
# Run all tests
r3 -s rut.r3
# Run specific group
r3 -s rut.r3 --group borrow-checker
r3 -s rut.r3 -g types
# Run specific test
r3 -s rut.r3 -t "Mandelbrot"
# Filter by tag
r3 -s rut.r3 -tg closure -tg fn-ptr
# Fast compiler/C verification pass
r3 -s rut.r3 --transpile-only
# Native compile/run from cached transpiled C
r3 -s rut.r3 --compile-only
# Other options
r3 -s rut.r3 --fail-fast # Stop on first failure
r3 -s rut.r3 --quiet # Less output
r3 -s rut.r3 --shuffle 42 # Shuffle with seed
r3 -s rut.r3 --list # List available tests
RUT source-backed tests now declare a single source: value plus optional transpile: and compile: blocks. The transpile phase caches emitted C with a content-derived cache key, and the compile phase reuses that cached C instead of running the Recoil compiler again.
RUT also enforces a single active runner via cache/rut.lock/; overlapping runs fail fast, and stale locks from dead processes are cleared automatically.
Continuous integration
Forgejo runs .forgejo/workflows/linux.yml for a fast Linux/GCC subset on
pushes and pull requests targeting master or releases/*.
.forgejo/workflows/linux-nightly.yml runs the full Linux suite nightly.
GitLab runs .gitlab-ci.yml for a Windows/MSVC smoke build on master only
(the free compute minutes do not stretch to every push), via a push mirror at gitlab.com/rebolek/recoil. GitLab is used for
this rather than GitHub because it can host a SHA-256 repository. That job uses
the core Rebol build: the bulk build imports OPENGL32.dll, which a
headless Windows Server image does not have.
macOS has no workflow. It is the primary development platform and is covered by local runs; GitLab's hosted macOS runners require a paid plan.
What Recoil is for
- Native executables and libraries — Recoil emits C and can build executables plus shared or static libraries.
- Memory-safe low-level code — the static borrow checker tracks moves and borrows without a garbage collector.
- C interoperation — foreign declarations, ABI types, and explicit linker metadata provide a direct path to C libraries.
- Rebol-shaped source — blocks, words, paths, and expression-oriented flow remain central to the language.
License
Apache2 License — see LICENSE