A small, statically typed scripting language.
Coil gives you real type inference, algebraic data types and pattern matching, coroutines and FFI — compiled to compact bytecode and run on a thin, embeddable VM.
- Source
.hy- Bytecode
.hyc- CLI
coil- Packages
spool
use io::{stdout};
use io::sync::{write_all};
use string::{format, to_bytes};
enum Shape {
Circle(int),
Rect { width: int, height: int },
}
fn area(Shape s) -> int {
return match s {
Shape::Circle(r) => 3 * r * r,
Shape::Rect { width, height } => width * height,
};
}
fn main() {
let shapes = [
Shape::Circle(3),
Shape::Rect { width: 3, height: 4 },
];
for s in shapes {
write_all(stdout(), to_bytes(format("%i\n", area(s))));
}
} More structure than a script. Less ceremony than a systems language.
Every feature below links to the tutorial chapter that teaches it.
Types, inferred
Hindley–Milner inference checks the whole program before it runs. Annotate where it helps the reader, not the compiler.
let nums = [1, 2, 3]; // [int; 3]
let pair = (42, "hello"); // (int, string)
let buf: Vec<int> = Vec::new();
Learn more → Data you can match on
Enums with unit, tuple and record cases, scalar-backed variants, and exhaustive pattern matching.
match s {
Shape::Circle(r) => r * r,
Shape::Rect { width, height } => width * height,
}
Learn more → Errors are values
Built-in Option and Result, with raise, ? , ?? and ?. desugaring to ordinary matches — no exceptions.
fn port(string s) -> Result<int, string> {
if s == "" { raise "empty"; }
return Result::Ok(8080);
}
let p = port(s) ?? 80;
Learn more → Coroutines built in
Stackful coroutines with gen fn, yield and resume — and they plug straight into for-in loops.
gen fn countdown(int n) {
let i = n;
while i > 0 { yield i; i = i - 1; }
}
for n in countdown(3) { … }
Learn more → Reach native code
Declare C functions in an extern block and call them directly, or load libraries at runtime through libffi.
extern "c" {
fn strlen(string s) -> int;
}
let n = strlen("hello");
Learn more → Real programs
Non-blocking files, TCP and UDP streams, OS threads with channels, and a multi-file module system.
use io::{stdout, open};
use thread::{spawn, join, channel};
use env::{args, var};
Learn more → From source to bytecode, once.
The CLI caches the compiled archive and reuses it until sources, version or entry point change — so the second run starts straight on the VM.
- 01 Parse Pratt parser → AST
.hy - 02 Typecheck Hindley–Milner inference
- 03 Codegen Stack IL, lowered & fused
- 04 Archive Versioned bytecode
.hyc - 05 Run Thin VM executes main
One command for the whole loop
Build, test, format, package and serve the language server from the same coil binary.
Dependencies are fetched by spool.
-
$ coil app.hyCompile (cached) and run -
$ coil testRun test blocks under ./tests -
$ coil fmt src/Format sources in place -
$ coil package app.hyBuild a single executable -
$ coil lspLanguage server for your editor
Two ways in.
Follow the tutorial from the first line of code, or jump straight to the reference when you need an exact answer.
The tutorial
Eleven chapters, in order. Each one ends where the next begins.
- 01 Basics
- 02 Types & variables
- 03 Collections
- 04 Classes
- 05 Enums & match
- 06 Error handling
- 07 Traits & generics
- 08 Functions as values
- 09 Modules & packages
- 10 Files & IO
- 11 Testing
- 12 Generators
- 13 Tasks
- 14 Threads
- 15 Macros
- 16 Calling C
The reference
Grammar, types and every module, organised for lookup.
Small core, userland packages.
The compiler ships the essentials as virtual modules. Everything else lives in ordinary Coil packages — starting with coil-stdlib.
Write your first .hy file.
Grab the toolkit from GitHub Releases — or, until the first tagged release, the latest release-binaries build.