coil
coil is a statically typed scripting language with type inference. Programs are compiled to bytecode and executed on a custom virtual machine. Source files use the .hy extension (short for henry, the SI unit of inductance — the measure of a coil); compiled archives are stored as .hyc files.
For developers who want a small typed scripting language with more structure than a typical script, and a bytecode runtime they can keep thin.
The language targets embeddable scripting: you get real type checking and inference without a heavyweight build pipeline, plus optional FFI for calling into C libraries or host-provided Rust closures.
Quick start#
Download the toolkit for your OS. When a GitHub Release exists, use GitHub Releases (latest). There is no tagged release yet. Until then, the current build is the latest release-binaries workflow on main (binaries-<triple> artifacts, 30-day retention). See Getting Started.
use io::{stdout};
use io::sync::{write_all};
use string::{to_bytes};
fn main() {
write_all(stdout(), to_bytes("hello"));
}
With coil on your PATH, from a clone of the repository with coil-stdlib cloned beside it:
coil --root ../coil-stdlib/src examples/print_literal.hy
Expected output: hello
How programs run#
Parse → typecheck → typed HIR (inlining, optimization passes) → stack IL + MIR tiers / lower/fuse-select → bytecode → VM executes main. coil <file.hy> compiles in memory on every run; coil compile writes a versioned .hyc archive (packed major.minor) that coil run executes. Full stage notes: Internals — Pipeline.
Language at a glance#
| Area | Status |
|---|---|
| Primitives | int, float, string, bool, byte |
Functions, let / const, if/else, while / for | Supported |
Named call-site arguments (f(name: v)) | Supported (positional prefix then named; named holes on partials allowed) |
Arity / type overloads / first-class fn values / lambdas (use) | Supported (examples/overload.hy, type_overload.hy, fn_value.hy, lambda.hy) |
Rest parameters (T... xs / tuple ... xs) | Supported (trailing only; T... packs to Vec<T>, bare ... packs to a tuple) |
Call-site spread (f(...pack)) | Supported (tuple, [T; N], and Vec<T> operands) |
User-defined attr decorators | Supported (attr decl + #[name(...)] on fn, methods, class constructors) |
Let destructuring (let (a, b) = …, let { x, y } = …) | Supported (tuple / record; no enum ctor patterns in let) |
break / continue | Supported |
Enums, match, record variants, scalar #[repr] | Supported (Enum::Variant; catch-all is default =>) |
Built-in Option / Result, raise, ?, ??, ?. | Supported (desugar to match/return) |
Tuples, fixed arrays ([T; N] / len), Vec<T>, records { x: 1 } | Supported |
Type aliases (type Name = T;, lexically scoped) | Supported |
| Generics and traits | Supported: generic functions/enums/aliases/classes, higher-kinded type parameters, associated types/GATs, existentials, coherence checks |
Modules / namespaces (use, mod) | Supported (multi-file CLI via --root; spool reads coil.toml) |
Field access (p.x, chained p.x.y) | Supported |
FFI (extern blocks, dload/declare/invoke, C varargs ..., struct/callback returns) | Supported (requires libffi) |
IO streams (use io::{…};, Vec<byte>, files, sync adapters, TCP, UDP) | Supported (non-blocking L0) |
Classes (class / impl / new, fields, methods) | Supported |
Coroutines (gen fn, yield, resume, yield from, done) | Supported |
for x in (Iterator / IntoIterator) | Supported (arrays, homogeneous tuples, ranges, coroutines, user impls) |
| Integer arithmetic | + - * / % ** and unary - on int panic on overflow in every build; float is IEEE. Wrapping / checked / saturating helpers are the userland arith module |
Effects and capabilities (pure fn, uses {…}, --allow-*) | Supported (effects, permissions) |
Contracts (requires, ensures, invariant, decreases) | Supported; coil verify proves them with an SMT solver (contracts) |
Tasks (task::scope, spawn, join, channels) and OS threads | Supported (tasks, threads) |
Ranges (a..b / a..=b) | Supported — lazy Range<T: Ord>; for / .to_vec() step int/byte/float; non-numeric Ord is a type error (syntax) |
String concat via + | Supported (string + string → string) |
string::format(...) | Supported compiler intrinsic (returns string; literal specifiers are checked) |
Browse runnable demos in Examples. Multi-file showcase apps (todo board, text adventure, TCP echo) live under examples/projects/.
Documentation#
Docs are split into three trees:
| Tree | Audience | Start here |
|---|---|---|
| Manual | Learners | Getting started, tutorial chapters 01–16, examples catalog |
| References | Lookup | Syntax, types, keywords, per-API builtin pages |
| Internals | Contributors / embedders | Pipeline, debug info, opcodes, grammar |
Manual (tutorial)#
| Chapter | Topic |
|---|---|
| Getting started | Install, first program, spool projects |
| 01 — Basics | Running files, functions, let, control flow |
| 02 — Types and variables | Numbers, strings, bools, conversions, constants |
| 03 — Collections | Arrays, Vec, tuples, HashMap, ranges |
| 04 — Classes | Fields, methods, new, derives |
| 05 — Enums and match | Cases with data, match, Option, if let |
| 06 — Error handling | Result, ?, ??, panics |
| 07 — Traits and generics | Generic functions and types, traits, impl |
| 08 — Closures | Lambdas, captures, functions as values |
| 09 — Modules | Files as modules, use, mod, packages |
| 10 — Files and IO | Reading and writing files, streams, stdin |
| 11 — Testing | coil test, assertions, test files |
| 12 — Generators | gen fn, yield, resume, yield from |
| 13 — Tasks | task::scope, channels, timeouts, a TCP server |
| 14 — Threads | spawn, join, channels, mutexes |
| 15 — Macros | Function-style macros, attribute macros, derives |
| 16 — Calling C | extern blocks, dload, permission flags |
| Examples catalog | Runnable demos in examples/ (see catalog for expected output) |
| Showcase projects | Multi-file apps + co-located tests |
| Userland stdlib | coil-stdlib: consume, modules, IO adapters |
Full API index: Reference.
Repository layout#
coil/
├── common/ # Shared types: opcodes, values, archive format
├── parser/ # Pratt parser and AST
├── compiler/ # Typechecker, HIR optimizations, stack IL / MIR lowering, pipeline
├── machine/ # VM, heap/GC, FFI (libffi)
├── coil-*/ # CLI helpers: test, fmt, lsp, debug, dissect, verify, embed
├── examples/ # Runnable .hy demos (see manual/examples.md)
│ └── projects/ # Showcase multi-file apps + co-located tests
├── docs/internals/ # Pipeline, VM notes, grammar (user docs live on this site)
└── src/main.rs # CLI: default build+run, compile, run, test
Running#
| Command | Role |
|---|---|
(no subcommand) <file.hy> | Compile in memory → run (no out.hyc) |
compile <file.hy> [-o path] | Compile entry file to a .hyc archive |
run <file.hyc> | Execute a compiled archive |
package <file.hy> [-o path] [--check-native] | Single executable (embeds .hyc into coil-embed by default) |
test [path] [--fail-fast] | Compile+run all [path]/**/*.hy (default ./tests); continue after failures unless --fail-fast |
lsp | Start the Coil language server over stdin/stdout |
For FFI examples you also need libffi (e.g. libffi-dev on Debian/Ubuntu, libffi on Arch). To build from a checkout, see Getting Started.
Learn by example#
| Goal | Start with |
|---|---|
| First program | getting-started.md → examples/fib.hy |
| Enums & pattern matching | examples/option.hy, examples/result.hy |
| Record-shaped variants | examples/record.hy, examples/mixed.hy |
| Dicts / anonymous records | examples/dict.hy |
| Generics & traits | examples/generics.hy, examples/hkt_bifunctor.hy, examples/gat_pointer.hy, examples/existential_show.hy |
| Modules | examples/modules.hy (see examples.md for setup) |
| FFI | examples/ffi_extern.hy, examples/ffi_sum.hy, examples/ffi_varargs.hy |
| IO streams | examples/io_bytes.hy, examples/io_file.hy, examples/io_eof.hy, examples/io_udp.hy |
| Coroutines | examples/coro.hy, examples/coro_gen.hy, examples/coro_send.hy, examples/for_in_coro.hy |
| Full catalog | examples.md |
Source for the language is ardax-corp/coil-lang. Internals stay in that repository.