Introduction
Recoil is a Rebol-to-C transpiler that brings memory safety to Rebol through static ownership checking inspired by Rust. It translates .r3 source files into optimized C code via a four-stage pipeline:
Parse
Converts infix expressions to prefix AST using Rebol's powerful parse dialect.
IR
Generates an intermediate representation suitable for optimization.
Analyze
Static borrow checker that enforces ownership rules and prevents memory errors at compile time.
Emit C
Generates clean, optimized C code ready for compilation.
Key Features
Memory Safety: Compile-time ownership tracking prevents use-after-free and double-free errors
Static Typing: All variables have explicit types known at compile time
Zero-Cost Abstractions: High-level Rebol syntax compiles to efficient C
FFI Support: Call C libraries directly from Recoil code
Optimizations: Constant folding and dead code elimination built-in
Quick Start
Installation
Recoil requires Rebol 3 (r3) and a C compiler (GCC/Clang). Clone the repository:
git clone https://github.com/anomalyco/recoil.git
cd recoil
Compile & Run
Create a file called hello.r3:
; Hello World in Recoil
print "Hello, Recoil!"
Run it with:
r3 recoil.r3 hello.r3
r3 rut.r3
Primitive Types
Recoil provides a rich set of primitive types. Types ending with ! follow Rebol naming conventions.
Numeric Types
| Recoil Type | C Type | Semantic |
|---|---|---|
i8! i16! i32! i64! |
int8_t ... int64_t | Copy |
u8! u16! u32! u64! |
uint8_t ... uint64_t | Copy |
f32! f64! |
float, double | Copy |
Other Primitives
| Recoil Type | C Type | Semantic |
|---|---|---|
logic! |
int (0 or 1) | Copy |
char! |
char | Copy |
c-string! |
char * | Move |
string! |
struct { char *data; size_t len; } | Move |
c-pointer! |
void * | Move |
Type Examples
; Copy types - safe to reuse
i32! x: 42
f32! pi: 3.14
logic! flag: true
; Move types - ownership transfers
string! msg: "hello" ; string! with automatic memory management
c-string! s: "raw C string" ; direct C string pointer
Functions
Functions are defined using the func keyword with type annotations for parameters and return values.
Basic Function
add: func [
a [i32!]
b [i32!]
return: [i32!]
] [
return a + b
]
i32! result: add 5 10
print result ; prints 15
Void Function
log-number: func [
val [i32!]
return: [void]
] [
print val
]
log-number 100 ; prints 100
Function Pointers
; Define a function
adder: func [a [i32!] b [i32!] return: [i32!]] [
return a + b
]
; Create a function pointer
fn-ptr! callback: adder
; Call through pointer
i32! result: call callback [3 4]
Borrowing & Ownership
Recoil's key innovation is a static borrow checker that enforces memory safety at compile time through ownership rules.
Variable States
A variable can be in one of three states:
Owned: The variable holds valid data and can be read or moved
Moved: Ownership was transferred to another variable; accessing triggers a compile error
Borrowed: Used via a reference without transferring ownership
Move Semantics
string! s1: "Hello"
string! s2: s1 ; s1 is now 'moved'
print s1 ; ERROR: Use of 's1' after move
BORROW CHECKER ERROR: 's1' used after move
Ownership Recovery
Assigning a new value restores ownership:
string! s1: "Hello"
string! s2: s1 ; s1 moved to s2
s1: "Fresh Start" ; s1 is now 'owned' again
print s1 ; OK - prints "Fresh Start"
Borrowing with Get-Words
Use :variable (get-word) to borrow without moving:
greet: func [
:name [string!] ; borrowed parameter
return: [void]
] [
print name
]
string! msg: "Hello"
greet :msg ; borrowed - msg stays alive
print msg ; valid - msg was borrowed, not moved
Borrowing in Conditionals
When a variable is moved in one branch of either, it's considered moved in the entire scope:
string! s1: "hello"
logic! condition: true
either condition [
print s1 ; s1 is used here (not moved)
] [
s1: "new" ; s1 is moved here
]
print s1 ; OK - s1 was reassigned in else branch
Control Flow
While Loop
i32! i: 0
while [i < 5] [
print i
i: i + 1
]
Repeat Loop
; Repeat with index variable (0 to count-1)
repeat i 10 [
print i
]
If / Either
i32! a: 10
i32! b: 20
; Bare expression condition
if a > b [
print "a is greater"
]
; Block condition
if [a > b] [
print "a is greater"
]
; Either (if-else)
either a > b [
print "yes"
] [
print "no"
]
Structs
Structs define custom compound types with named fields.
Defining and Using Structs
; Define a struct type
point!: make struct! [x: i32! y: i32!]
; Create an instance
point! p1: [x: 10 y: 20]
; Access fields
print p1/x ; prints 10
print p1/y ; prints 20
; Modify fields
p1/x: 42
print p1/x ; prints 42
Vectors
Vectors are fixed-size arrays with optional heap/stack allocation.
Heap Vectors (Default)
; Heap-allocated vector (default)
vector! [i32! 5] arr: [1 2 3 4 5]
print arr/0 ; prints 1
print arr/4 ; prints 5
Stack Vectors
; Stack-allocated vector
vector! [i32! 5 #stack] arr: [1 2 3 4 5]
Mutable Vectors
; Mutable heap vector
mut vector! [i32! 5 #heap] arr: [0 0 0 0 0]
arr/0: 42
print arr/0 ; prints 42
Closures
Closures capture variables from their enclosing scope.
Basic Closure
i32! x: 10
fn-ptr! adder: func [#capture [x] a [i32!] return: [i32!]] [
return a + x
]
i32! result: call adder [5]
print result ; prints 15
Auto-Capture
i32! counter: 100
; Closure auto-captures free variable
fn-ptr! inc: func [a [i32!]] [
counter: counter + a
]
call inc [7]
print counter ; prints 107
Foreign Function Interface (FFI)
Call C libraries directly from Recoil using the foreign keyword.
Defining Foreign Functions
; Declare a foreign namespace
curl: foreign <curl> [
easy-init: ["curl_easy_init" return: :CURL]
easy-setopt: ["curl_easy_setopt" :CURL string i32! return: i32!]
]
Using Foreign Functions
; Call foreign functions
CURL c: curl/easy-init
i32! result: curl/easy-setopt c "URL" 1
Operators
Recoil supports standard arithmetic and logical operators with defined precedence.
Precedence (Higher = Tighter)
| Level | Operators |
|---|---|
| 20 | * / |
| 10 | + - |
| 5 | = <> < > <= >= |
| 3 | and or |
Examples
; Arithmetic
i32! a: 10 + 5 * 2 ; = 20 (multiplication binds tighter)
i32! b: (10 + 5) * 2 ; = 30
; Comparison
logic! eq: 10 = 10 ; true
logic! ne: 10 <> 5 ; true
; Logical
logic! both: and true false ; false
logic! either: or true false ; true
Optimizations
Recoil includes built-in optimizations that run during compilation.
Constant Folding
Arithmetic expressions are evaluated at compile time:
i32! a: 3 + 5 ; becomes: int a = 8;
i32! b: 4 * 5 ; becomes: int b = 20;
Dead Code Elimination
Unreachable branches are removed when conditions are constant:
either true [
print "always true"
] [
print "never" ; removed at compile time
]