Chicken Scheme: A Practical Guide for Systems Engineers
Scheme is a language that has been quietly doing serious work for decades. It is the language SICP was written in. Naughty Dog used a Racket-derived Scheme as the scripting engine for Uncharted and The Last of Us. GNU Guile is the official extension language of the GNU project. And CHICKEN Scheme — the subject of this guide — compiles your Scheme to portable C, producing standalone native binaries that run on Linux, macOS, Windows, ARM, MIPS, SPARC64, and anything else with a C toolchain.
CHICKEN is not an academic curiosity. It has a substantial egg (package) ecosystem, a clean FFI that makes C library bindings feel native, green-thread concurrency, full POSIX integration, and one of the most elegant garbage collection architectures in any compiled language implementation. The current stable release is CHICKEN 5.4.0, released July 2024.
This guide is aimed at engineers who already know at least one systems language and want a thorough map of the territory: Scheme fundamentals, CHICKEN internals, the egg ecosystem, FFI mechanics, GUI programming with IUP, and where CHICKEN sits among its Scheme peers.
Scheme Fundamentals
What Scheme Is, and What It Is Not
Scheme is a dialect of Lisp. It is deliberately small — the R7RS standard document runs to 88 pages; the Common Lisp HyperSpec is over 1,000. That minimalism is the point. Scheme gives you a powerful, consistent core and gets out of the way. There are no ten ways to do loops; the language gives you tail calls and higher-order functions and trusts you to build what you need.
The most recent standard is R7RS (Revised 7 Report on the Algorithmic Language Scheme), finalized in 2013. It defines “R7RS small” as the portable core and “R7RS large” as a collection of optional libraries via SRFIs (Scheme Requests for Implementation). CHICKEN 5 targets R7RS small, with R7RS large libraries available as eggs.
Scheme vs Common Lisp: The Key Differences
These are cousins that diverged decades ago and serve different philosophies.
Lisp-1 vs Lisp-2. The most fundamental difference. Common Lisp maintains separate namespaces for functions and variables — a name can simultaneously refer to a function and a variable. Scheme uses a single unified namespace. In Scheme, define defines everything. Functions are just values bound to names. This forces you to use funcall in CL but means Scheme code is considerably cleaner when passing functions as arguments.
|
|
Mandatory tail call optimization. Common Lisp does not require TCO — it is implementation-defined. Scheme mandates it. Every conforming Scheme implementation must eliminate tail calls, making recursion as stack-safe as iteration. This is not a convenience; it changes how you write programs.
Hygienic macros. Common Lisp macros are textual — they operate on raw s-expressions and require gensym tricks to avoid variable capture. Scheme provides hygienic macros via syntax-rules (pattern-based, declarative) and syntax-case (procedural, more powerful). Hygienic macros respect lexical scope by construction.
Philosophy. CL is a batteries-included, multi-paradigm industrial language with CLOS, the condition system, restarts, and decades of accumulated libraries. Scheme is minimal by design — fewer primitives, more power per primitive, extensible through macros and higher-order functions.
Tail Call Optimization in Depth
TCO is not just about recursion. It is about the entire semantics of function calls in tail position. A call is in tail position when its result is directly returned — no further computation wraps it.
|
|
In CHICKEN specifically, TCO is implemented through the CPS transformation — tail calls compile to direct C function calls with no stack growth, equivalent to a goto.
Proper Tail Calls in Practice
Named let is idiomatic Scheme iteration — it creates a local recursive function and immediately calls it, and the recursive call is in tail position:
|
|
The do form provides imperative-style iteration when that reads more clearly:
|
|
Continuations and call/cc
A continuation is the “rest of the computation” — everything that will happen after the current expression returns. call-with-current-continuation (aliased as call/cc) captures this as a first-class procedure. Calling that procedure abandons whatever is currently happening and resumes from the captured point.
The most common use is escape continuations — early exit from nested loops or recursive searches:
|
|
Continuations can also be used to implement coroutines, generators, and cooperative threading — CHICKEN’s green thread system (srfi-18 egg) is built on continuations.
|
|
Hygienic Macros
syntax-rules defines pattern-matching macros that are hygienic by construction — introduced bindings cannot accidentally capture variables from the call site.
|
|
syntax-case (available in CHICKEN via (chicken syntax)) gives you the full power of procedural macro writing, including the ability to inspect sub-expressions, generate identifiers with datum->syntax, and write macros that compose correctly:
|
|
CHICKEN Scheme Internals
The Cheney-on-the-MTA Architecture
CHICKEN’s garbage collector is described in Henry Baker’s 1994 paper “CONS Should Not CONS Its Arguments, Part II: Cheney on the M.T.A.” — the M.T.A. refers both to the New York subway system and to “making the allocation fast.” Understanding it explains most of CHICKEN’s behavior and performance characteristics.
The key insight: after CPS transformation, no Scheme function ever returns to its caller in the conventional sense. Functions call continuations, which call continuations, which call continuations. Since no function returns, the C stack never unwinds. Baker’s observation: if the stack never unwinds, we can use the C stack as the allocation arena for newly created objects.
Step 1: CPS transformation. The compiler rewrites every Scheme function into continuation-passing style. Every function takes an extra argument k (the continuation). Instead of return value, it calls k(value). The stack grows continuously as CPS calls chain together.
Step 2: Stack as nursery. New objects — pairs, strings, closures, vectors — are allocated directly on the C stack using alloca(). Allocation is a single stack pointer bump. This is extremely fast and has excellent cache locality.
Step 3: Minor GC via longjmp. Each allocation checks whether the stack has grown past a threshold (typically around 256KB). When it has, a minor GC fires:
- The GC identifies live objects by starting from the current function’s arguments and continuation (the “tip of the iceberg”).
- It copies all live objects from the stack into the heap using Cheney’s two-pointer breadth-first copying algorithm.
- It calls
longjmp()back to the top-level trampoline, resetting the stack pointer. The stack is now empty again; all live data is in the heap. - Execution resumes from where it left off — the continuation knows where to go.
Step 4: Major GC. The heap is divided into two equal regions: fromspace and tospace. When the heap’s fromspace fills up during minor GC:
- All live fromspace objects are copied to tospace (again using Cheney’s algorithm).
- Fromspace and tospace swap roles.
- If insufficient garbage was collected, the heap grows and the process repeats.
The result is a two-generation collector where generation 0 lives on the C stack and generation 1 is the heap. Short-lived objects (most objects in a functional program) never reach the heap at all — they are allocated on the stack and discarded when the stack is reclaimed via longjmp.
Write barriers. Because mutation can create references from old generation objects to young ones, CHICKEN maintains a write barrier. When a heap object is mutated to point to a stack object, that reference is added to a remembered set and treated as a GC root.
This architecture has concrete implications:
- Allocation is very fast (stack pointer bump)
- Minor GC is triggered predictably (stack threshold)
- The pause for a minor GC is proportional to live data, not heap size
- Long-lived mutation-heavy programs with large heaps incur higher major GC cost
Compilation Pipeline
CHICKEN’s compilation pipeline has five distinct phases:
-
Canonicalization: Macro expansion, alpha-conversion (unique naming to track scoping), and normalization of toplevel defines to assignments.
-
CPS conversion: The entire program is transformed into continuation-passing style. Every expression gets an explicit continuation parameter. This is what makes tail calls free — in CPS, a tail call is just a function call.
-
Optimization passes (five rounds):
- Eliminate double negations and other algebraic redundancies
- Contract procedures called only once (inline them at the call site)
- Eliminate unnecessary local variables
- Lower builtins to primitive operations
- Final contraction pass
-
Closure conversion: Free variables in lambda expressions are captured into explicit closure records. The stack-allocated closure is what gets promoted to the heap during minor GC.
-
C code generation: The CPS closures become C functions. The compiler emits stack overflow checks, allocation code using
C_alloc, continuation invocations, and trampoline infrastructure.
The generated C is not meant to be read by humans, but the structure is regular and predictable. Tight inner loops often compile down to a handful of C statements. CHICKEN also emits type annotations that GCC or clang can use for further optimization.
csi vs csc: The Two CHICKEN Entry Points
csi — The Interpreter
csi is CHICKEN’s read-eval-print loop. It interprets Scheme directly without going through C, which makes it slower than compiled code but useful for exploration, testing, and scripting.
|
|
Inside the REPL, comma-prefixed commands provide introspection:
#;1> ,?
; available commands:
; ,d EXPR -- describe a value
; ,p EXPR -- pretty-print
; ,x EXPR -- expand macros and pretty-print
; ,m MODULE -- switch to module context
; ,l FILE -- load a file
; ,t EXPR -- time an expression
; ,c -- show call trace from last error
; ,q -- quit
The REPL supports result history with #0 (last result), #1 (second-to-last), etc. Adding the breadline or linenoise egg provides tab completion and persistent history.
csc — The Compiler
csc is the compiler driver. It invokes the Scheme-to-C translator, then hands the generated C to your system’s C compiler (GCC or clang), then links the result.
|
|
Optimization levels:
| Level | Effect |
|---|---|
-O0 |
No optimization |
-O1 |
Optimize small non-allocating leaf procedures |
-O2 |
General inlining of small known procedures (default) |
-O3 |
Inline top-level procedures, type specialization |
-O4 |
O3 + unsafe mode (removes runtime checks) |
-O5 |
O4 + disable interrupts, disable trace, clustering |
Optimization declarations can be placed in source files for fine-grained control:
|
|
Profiling workflow:
|
|
The Module System
CHICKEN’s module system is syntactical rather than environmental — it maps identifier names to bindings without creating separate runtime environments. This is a meaningful design choice: modules are resolved at compile time, not runtime.
Basic Module Syntax
|
|
Import Specifiers
|
|
Modules Across Files
|
|
R7RS define-library
For portability with other R7RS implementations, CHICKEN supports define-library via the r7rs egg:
|
|
The Egg System
Eggs are CHICKEN’s extension packages. The repository at eggs.call-cc.org hosts over 400 eggs, installable with chicken-install.
chicken-install Basics
|
|
Essential Eggs by Category
Data and serialization:
json— JSON parsing and generationcjson— C-based JSON for performance-sensitive codemsgpack— MessagePack binary serializationcsv— CSV parsing
HTTP and networking:
http-client— High-level HTTP client with SSL supportintarweb— Low-level HTTP library (request/response objects)spiffy— Embedded HTTP serveruri-common— URI parsing and construction
Databases:
sqlite3— Full SQLite 3 bindingspostgresql— libpq bindings for PostgreSQLredis— Redis clientlmdb— Lightning Memory-Mapped Database bindings
Parsing:
comparse— Combinator parserspackrat— Packrat (PEG) parsing with memoizationlalr— LALR(1) parser generator
Testing:
test— Lightweight test framework (assert-based)srfi-64— Standard R7RS test suite APIcluckcheck— QuickCheck-style property-based testing
Concurrency:
srfi-18— Green threads (continuations-based)mailbox— Thread-safe message queues
Cryptography:
tweetnacl— NaCl/libsodium-compatible modern cryptoopenssl— SSL/TLS via OpenSSLsha2,md5— Hash functionshmac— Message authentication
Systems:
posix-utils— Extended POSIX wrappersscsh-process— Shell-style process managementargs— Command-line argument parsing
Data structures:
srfi-1— Comprehensive list operationssrfi-69— Hash tablesrb-tree— Red-black treeskd-tree— k-d trees for spatial indexing
Tooling:
lsp-server— Language Server Protocol (editor integration)chicken-doc— Documentation browsing in the REPL
Creating an Egg
An egg is a directory containing at least your source file and an .egg descriptor:
my-egg/
├── my-egg.scm
└── my-egg.egg
|
|
|
|
|
|
SRFI Support
CHICKEN ships with a large set of SRFIs built in, and many more are available as eggs.
Built-in SRFIs (available without installing anything): SRFI-0 (feature testing), SRFI-4 (homogeneous numeric vectors), SRFI-8 (receive), SRFI-9 (define-record-type), SRFI-16 (case-lambda), SRFI-17 (generalized set!), SRFI-23 (error), SRFI-26 (cut/cute), SRFI-28 (basic string format), SRFI-39 (parameter objects), SRFI-46 (syntax-rules extensions), SRFI-62 (S-expression comments), SRFI-98 (environment variables).
Key eggs to install for day-to-day work:
srfi-1— List operations:fold,filter,iota,append-map,take,drop,every,anysrfi-13— String library:string-contains,string-prefix?,string-splitsrfi-14— Character setssrfi-18— Multithreading (green threads)srfi-19— Date and timesrfi-69— Hash tablessrfi-133— Vector librarysrfi-158— Generators and accumulators
The Foreign Function Interface
CHICKEN’s FFI is one of its strengths. You can call any C function, access C structs, and call Scheme from C, all without writing a separate binding layer in pure C.
Basic foreign-lambda
|
|
foreign-lambda*: Inline C Code
When you want to write a small C snippet directly in your Scheme file:
|
|
Custom Foreign Types
|
|
Accessing C Structs
Use the foreigners egg for idiomatic struct bindings:
|
|
|
|
Callbacks: Calling Scheme from C
When C code needs to call back into Scheme, use foreign-safe-lambda and define-external:
|
|
A Complete FFI Example: Wrapping libcurl
|
|
Practical Scheme Patterns
let, let*, letrec, letrec*
|
|
define-record-type (SRFI-9 / R7RS)
|
|
Higher-Order Functions
|
|
Error Handling
CHICKEN uses condition-case (built-in) and the with-exception-handler / guard forms from R7RS:
|
|
IUP: Cross-Platform GUI with Native Controls
What IUP Is
IUP is a cross-platform GUI toolkit developed at PUC-Rio (Pontifícia Universidade Católica do Rio de Janeiro), the same institution behind the Lua programming language. IUP’s core design principle: use the platform’s native controls rather than drawing its own. On Windows, IUP uses Win32 controls. On Linux, it uses GTK+. On older Unix systems, Motif.
This means IUP applications look and behave like native applications. A button on Windows uses a Windows button; on Linux it is a GTK button. There is no custom widget renderer. The tradeoff is that the API surface is somewhat constrained to what the platforms share.
IUP’s C API is small and consistent. Attributes (visual properties and state) are strings set and read by name. Callbacks (event handlers) are function pointers registered by attribute name. This uniformity makes it easy to bind to other languages, which is exactly how the CHICKEN iup egg works.
Platform support: Windows (Win32), Linux (GTK+), macOS (via GTK+ on X11 — native Cocoa support is partial), and older Unix (Motif).
Installing the iup Egg
The iup egg requires libiup to be installed as a system library before the egg can be installed.
|
|
The egg provides several modules:
iup-base— Core: object creation, attributes, callbacks, layout, dialogs, event loopiup-controls— Standard widgets (buttons, text boxes, lists, etc.)iup-dialogs— System dialogs (file picker, color picker, font picker)iup-glcanvas— OpenGL canvasiup-pplot— 2D plotting
IUP Core Concepts
Everything is a handle. Every widget — button, label, dialog, container — is an IUP handle. You create handles with constructor procedures, set attributes on them, register callbacks, assemble them into a hierarchy, and show a dialog.
Attributes are strings. IUP uses a string-based attribute system. Attribute names are usually Scheme keywords in the CHICKEN binding. Values are auto-converted from Scheme types where it makes sense.
|
|
Callbacks return control symbols. Most callbacks return one of:
'default— normal continuation, keep the dialog open'close— close the containing dialog'ignore— suppress default action (used for key events, etc.)
Hello World
|
|
A Complete Example: Form with Validation
|
|
Layouts: vbox, hbox, fill
IUP uses a box model — no absolute coordinates. Containers arrange their children.
|
|
Timers and Idle Callbacks
|
|
Canvas for Custom Drawing
|
|
File Dialog and Message Boxes
|
|
IUP vs Other Scheme GUI Options
| Option | Approach | Maintenance | Platform |
|---|---|---|---|
iup egg |
Native controls via IUP | Active, Thomas Chust | Win/Linux/Mac(GTK) |
chicken-tk |
Tcl/Tk via FFI | Maintained | Win/Linux/Mac |
| GLFW3 egg | OpenGL window management | Available | Win/Linux/Mac |
| GTK bindings | Direct GTK+ | Fragmented | Linux-primary |
IUP’s advantage for CHICKEN is the tight integration, the clean Scheme API (keywords as attributes, symbols as callback return values), and the native control appearance. If you need a complex custom-drawn UI, GLFW3 with OpenGL or a web-based approach (spiffy + browser as UI) is more appropriate. For standard desktop utility GUIs — forms, dialogs, settings panels — IUP is the most practical choice.
The iup egg is actively maintained by Thomas Chust at a BSD license. The upstream IUP library from PUC-Rio continues to receive updates, and new backends (including improved macOS Cocoa support via “IUP Next”) are in development.
CHICKEN in the Scheme Ecosystem
The Major Implementations
Chez Scheme (now open source, maintained by Cisco/community) is the performance leader. It has a sophisticated native code compiler, excellent garbage collection, and fast startup. Chez was open-sourced in 2016 and became the backend for Racket (Racket-on-Chez). If raw numeric or functional performance is your primary concern, Chez is likely the fastest option.
Racket (formerly PLT Scheme) is the most complete Scheme platform. It has a macro system powerful enough to implement entire new languages, a large standard library, typed/untyped gradual typing, documentation tools, a package manager, and an IDE (DrRacket). It diverged from standard Scheme enough to be its own language. Racket has been used in game scripting (Naughty Dog) and is the primary academic Scheme platform. If you want a full language workbench, Racket is the answer.
GNU Guile is the official extension language of the GNU Project, embedded in GDB, Lilypond, Gnucash, and GNU Make. Guile’s C API is stable and well-documented, making it the best choice when you need to embed a Scheme interpreter in a C or C++ application. Guile 3.0 added a JIT compiler and improved performance substantially.
MIT Scheme is the oldest actively maintained Scheme, descended from the original MIT Lisp Machine work. It is the reference implementation for SICP, has an excellent debugger, and is well-suited for learning Scheme’s semantics. Performance on production workloads is variable.
Gambit Scheme compiles to C similarly to CHICKEN but uses a different GC strategy. It has excellent performance on numerical code and a clean FFI.
Where CHICKEN Fits
CHICKEN occupies a specific niche that none of the others fill as well:
Systems and CLI programming. csc produces genuine native binaries with no runtime dependency. You can ship a single executable, cross-compile for different architectures, or distribute C source that compiles anywhere. The POSIX API is comprehensive and idiomatic.
Embedding and extension. CHICKEN’s C FFI is among the best in the Scheme world for going in both directions — calling C from Scheme and calling Scheme from C. The foreign-lambda* inline C syntax makes tight C/Scheme integration practical without a separate binding generation step.
Scripts that compile. A common CHICKEN workflow is to develop in csi for fast iteration, then csc for deployment. The same source file works in both contexts.
Modest footprint. CHICKEN itself requires only a C toolchain and GNU Make. The compiled binaries are small. This matters in embedded contexts and constrained environments.
Performance benchmark context. CHICKEN performs well on certain benchmarks but trails Chez and Racket on compute-heavy numeric work. The Cheney-on-the-MTA GC is excellent for allocation-heavy functional code but major GC pauses can be visible in latency-sensitive applications with large heaps. For most systems and infrastructure use cases, CHICKEN’s performance is more than adequate.
Benchmark summary (r7rs-benchmarks on i3-N305, 2024):
Chez Scheme — consistently fast across all benchmarks
Racket — fast on control-flow heavy code
Guile 3.0 — strong on string and I/O benchmarks
MIT Scheme — variable, excellent on some GC benchmarks
CHICKEN 5.4 — competitive on allocation-heavy functional code,
trails on heavy numeric computation
A Practical CHICKEN Project: HTTP Health Checker
Pulling it together — a small but complete program that demonstrates modules, records, error handling, HTTP, and compilation:
|
|
|
|
Getting Started
|
|
Useful references:
- Manual:
https://wiki.call-cc.org/man/5/index - API search:
https://api.call-cc.org/5/doc/ - Egg index:
https://eggs.call-cc.org/5/ - Community:
#chickenon Libera.Chat IRC
CHICKEN rewards the investment in learning it. The Cheney-on-the-MTA architecture is genuinely novel and worth understanding regardless of what you end up using it for. The FFI is one of the cleanest in any Lisp implementation. The compilation model — iterate in csi, ship with csc — maps naturally onto how engineers actually work. And Scheme itself, once the parentheses stop feeling foreign, is one of the most expressive ways to write programs that work correctly.
Comments