- Rebol 58.7%
- C 34.9%
- HTML 3.1%
- C++ 2.6%
- CSS 0.3%
- Other 0.4%
|
All checks were successful
Linux (fast) / RUT subset (linux-gcc) (push) Successful in 15m41s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|---|---|---|
| .claude/skills | ||
| .cursor/skills/compact | ||
| .forgejo/workflows | ||
| .github/workflows | ||
| docs | ||
| examples | ||
| ext | ||
| lib | ||
| make | ||
| packages | ||
| src | ||
| std | ||
| support | ||
| tests | ||
| tools | ||
| vendor | ||
| web | ||
| .gitignore | ||
| 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 enforcing ownership-based memory safety with a static borrow checker with a rich datatypeset and simple syntax.
Installation and usage
Recoil is transpiled to C so to compile it you need C compiler. So far, Recoil was tested with GCC.
Recoil's transpiler to C is written in Rebol, so you need Rebol also. Rebol was originally written by Carl Sassenrath but he's not developing anymore, so http://rebol.com and http://rebol.net are good only as historical reference. Rebol is currently maintained by @oldes (David Oliva) and you can grab latest version from Github. Grab binary, there are three versions, base, core and bulk which differs how much batteries are included. Personally, I use bulk version. Rebol is available for Linux, MacOS, FreeBSD, OpenBSD, DragonFlyBSD, Haiku and even for Windows.
If you prefer Docker, you can grab Dockerfile from here. There are two versions, Alpine and Debian-slim which differs by used libC. Debian-slim uses more common glibc and Alpine uses musl.
Compile a file
rebol3 -s recoil.r3 myprogram.rcl
Build as shared library
rebol3 -s recoil.r3 --lib mylib.rcl
Build as static library
rebol3 -s recoil.r3 --lib mylib.rcl --static
For the complete command-line reference, including package subcommands, targets, and compiler/linker flag passthrough, see docs/user/cli.md.
Run the test suite
rebol3 -s rut.r3
Documentation
- Latest release notes: docs/release-notes-0.11.0.md
- User guide: docs/user/user-guide.md
- Command-line reference: docs/user/cli.md
- ESP32 / ESP-IDF builds: docs/user/esp32.md
- f00 embedded scripting VM: docs/user/f00.md
- Compiler diagnostics: docs/user/diagnostics.md
- Diagnostic contract for compiler work: docs/plans/diagnostic-contract.md
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 → Parser → AST → Borrow Checker → IR → Optimize → C → GCC
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/plans/diagnostic-contract.md.
Primitive Types
| Recoil | C | Semantics |
|---|---|---|
i8! i16! i32! i64! |
int8_t ... int64_t |
Copy |
u8! u16! u32! u64! |
uint8_t ... uint64_t |
Copy |
f32! f64! |
float double |
Copy |
logic! |
int (0 or 1) |
Copy |
char! |
char |
Copy |
none! |
void * |
Safe null value |
void! |
void |
Function return only |
c-string! |
char * |
Move |
string! |
struct { char *data; size_t len; } |
Move |
c-pointer! |
void * |
Move |
file! |
FILE * |
Move |
error! |
error_t * |
Move — structured error value |
slice! |
struct { void *ptr; size_t len; size_t elem; } |
Zero-copy view |
enum! |
int (named constants) |
Copy |
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: make string! "hello" ; owned string! (a bare "..." literal is a c-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 [c-string!] return: [none!]] [print name]
greet :msg ; borrow — msg stays owned
Vectors
; Heap-allocated (default)
arr: make [vector! [i32! 5]] [1 2 3 4 5]
; Stack-allocated
arr: make [vector! [i32! 5 #stack]] [1 2 3 4 5]
; 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
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
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: 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: adder 5 ; 15
; By-reference capture (mutation)
count: make [#mutable i32!] 0
counter: make fn-ptr! func [#capture [:count] return: [i32!]] [
count: count + 1
return count
]
Enums
Color!: make enum! [red green blue]
c: 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: 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 global error state (overflow, div-zero, bounds)
result: 10 / 0
if error? [print "math error"]
; Check a specific error value
if error? e [print "local error"]
Errors are propagated via defer for cleanup:
; Always runs on exit (LIFO)
defer [free-resource r]
; Runs only when an error occurred
defer/error [print "cleaning up after error"]
Defer
defer schedules cleanup code to run at function exit in LIFO order:
f: make port! file/open %data.txt
defer [file/close f] ; always runs on return
; defer/error runs only when __recoil_last_error is set
defer/error [log-failure]
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
#include <curl/curl.h>
#link "-lcurl"
curl: foreign <curl> [
easy-init: "curl_easy_init" [return: :CURL]
easy-setopt: "curl_easy_setopt" [:CURL int c-string! return: int]
easy-perform: "curl_easy_perform" [:CURL return: int]
easy-cleanup: "curl_easy_cleanup" [:CURL]
]
handle: curl/easy-init
curl/easy-setopt handle 10002 "http://example.com"
result: curl/easy-perform handle
curl/easy-cleanup handle
Declare external C variables with #extern:
#extern c-pointer! some_global_from_c
Type Casting
a: 10 ; i32!
x: as f32! a ; explicit cast i32! -> f32!
c: 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
Move Types
Move-sensitive owned values such as string!, block!, map!, dict!,
port!, and c-pointer! move on owned transfer:
s1: make string! "hello"
s2: s1 ; ownership moves to s2
print s2 ; OK
print s1 ; ERROR: 's1' used after move
Borrowing
Pass by reference with get-word (:var):
greet: func [:name [string!]] [print name]
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:
status: either ok? ["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
Variables moved in any branch are considered moved afterward:
s1: "hello"
either condition [
moved: s1
] [
print s1 ; OK here
]
print s1 ; ERROR: 's1' used after move
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/borrow-checker.r3 |
Ownership analysis (analyze) |
src/ir.r3 |
AST → IR, optimization (ast-to-ir, 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) |
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
rebol3 -s rut.r3
# Run specific group
rebol3 -s rut.r3 --group borrow-checker
rebol3 -s rut.r3 -g types
# Run specific test
rebol3 -s rut.r3 -t "Mandelbrot"
# Filter by tag
rebol3 -s rut.r3 -tg closure -tg fn-ptr
# Fast compiler/C verification pass
rebol3 -s rut.r3 --transpile-only
# Native compile/run from cached transpiled C
rebol3 -s rut.r3 --compile-only
# Other options
rebol3 -s rut.r3 --fail-fast # Stop on first failure
rebol3 -s rut.r3 --quiet # Less output
rebol3 -s rut.r3 --shuffle 42 # Shuffle with seed
rebol3 -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.
Comparison
| Feature | Recoil | Rust | C3 | Zig | Go | Nim | Rebol | Red |
|---|---|---|---|---|---|---|---|---|
| Memory safety | Ownership | Ownership | Ownership | Allocator | GC | GC/opt | GC | GC |
| Static typing | Yes | Yes | Yes | Yes | Yes | Yes | No | Yes |
| Compiles to C | Yes | No | Yes | Yes | No | Yes | No | No |
| Garbage collection | No | No | No | No | Yes | Optional | Yes | Yes |
| Borrow checker | Yes | Yes | Yes | No | No | No | No | No |
| Closure support | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| FFI to C | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| Zero-cost abstractions | Yes | Yes | Yes | Yes | No | Partial | No | No |
What is Recoil Good For?
- Small binaries — No runtime, minimal C output with no dependencies
- FFI simplicity — Clean, declarative syntax for interfacing with C libraries
- Memory safety without GC — Ownership model prevents use-after-free and double-free at compile time
- Embedding — Small C output is ideal for embedding in other applications or scripting engines
- Rebol syntax — Expressive, declarative syntax for developers who prefer Rebol's style
- Gradual learning — Simple ownership model without Rust's complexity; easier to learn for those coming from dynamic languages
- Library building — Built-in support for generating shared/static libraries with header files
License
Apache2 License — see LICENSE