A statically-typed, actor-oriented programming language for concurrent and distributed systems.
Website | Documentation | Playground | Tutorial
curl -fsSL https://hew.sh/install | bashPre-built binaries for Linux (x86_64, ARM64), macOS (x86_64, ARM64), FreeBSD (x86_64, ARM64), and Windows (x86_64) — plus .deb, .rpm, and Arch packages — are published on the Releases page. Also available via Homebrew, Docker, and system packages.
# Hello world
echo 'fn main() { println("Hello from Hew!"); }' > hello.hew
hew run hello.hew
# Start a new project
hew init my_project
cd my_project
# hew init scaffolds hew.toml + main.hew + a merged .gitignore
hew check main.hew
hew fmt --check main.hew
hew doc main.hew --output-dir doc
hew run main.hew
# Interactive REPL
hew evalhew eval can run as an interactive REPL, evaluate a file in REPL context, or
evaluate a one-off inline expression. Top-level items (fn, type, enum,
actor, impl, trait) persist across REPL inputs so you can define a
function then call it later; let/var bindings and bare statements are
evaluated fresh each line and do not carry over. (Hew has no struct
keyword — type Name { ... } declares a record.)
hew eval
hew eval -f script.hew
hew eval "1 + 2"
hew eval --json -f script.hewFor non-interactive runs, -f - reads from stdin and --target wasm32-wasi
uses the WASI eval path.
Use :help inside the REPL to see the command list. Common commands include
:help / :h, :session / :show, :items, :type <expr>,
:load <file>, :clear / :reset, and :quit / :q.
hew init scaffolds a manifest-first project: hew.toml, a starter
main.hew, and a merged .gitignore.
See the Getting Started Guide for more.
The examples/ directory contains structured learning paths for new users:
examples/ux/— 15 quick-start lessons (hello world through hashmaps), each paired with an.expectedoutput file; ideal for a first 20-minute tourexamples/progressive/— 11 numbered lessons building from variables to actors, also with.expectedfilesexamples/playground/— Topic-grouped snippets covering basics, concurrency, and types, with checked-in metadata inmanifest.json
See examples/README.md for the complete directory guide.
If you're looking specifically for multi-file/module layouts, start with
examples/directory_module_demo/README.md
and then examples/multifile/README.md.
When you move from language lessons to library APIs, use std/README.md, the canonical index of shipped stdlib modules.
The sandbox VM runs admitted Hew programs in a deterministic browser-hosted runtime with a virtual clock, seeded randomness, logical heap accounting, and page-owned streams. See the public sandbox VM divergence catalog for the accepted differences from native execution and the native-only APIs rejected by the sandbox profile.
println and print are plain function calls, not macros. Coming from Rust, you might reach for println! — in Hew these are ordinary built-in functions written without a ! suffix, auto-imported into every file:
fn main() {
print("hello "); // no trailing newline
println("world"); // appends newline
println(42); // works with any type that implements Display
}
To use modules beyond the builtins, add an import statement at the top of your file:
import std::fs;
import std::encoding::json;
fn main() {
let data = fs.read("config.json");
let obj = json.parse(data);
println(obj.stringify());
}
json.Value has no Display impl, so print it through stringify() rather
than passing the value straight to println.
See std/README.md for the canonical index of shipped stdlib modules.
When you compile or typecheck a multi-file program with hew check,
hew compile, or hew run, pass one entry .hew file. Imports and
directory-form modules pull in the rest, so pass main.hew, not every file in
the tree.
hew doc is different: it accepts either one .hew file or a directory tree
of .hew files to document.
import foo;resolves to the directory-form module atfoo/foo.hewor tofoo.hewbeside the importer — whichever exists. If both exist the import is a hard error (import `foo` is ambiguous: both ... exist); rename or remove one.- Other top-level
.hewfiles insidefoo/merge into the same module automatically. - Child directories stay separate submodules, so import them explicitly — for
example
import foo::bar;. - Start with
examples/directory_module_demo/README.mdfor the smallest working layout, thenexamples/multifile/README.mdfor selective imports and nested module hierarchies.
Hew resolves imported modules through three tiers; the first tier that produces a result wins and lower tiers are not consulted:
- Explicit override —
HEWPATH(colon-separated entries; each entry is the parent directory that containsstd/) orHEW_STD(the path to thestd/directory itself; Hew uses its parent as a search root). If either is set, only those paths are used. - In-worktree development — otherwise, Hew walks up from the source
file (or the current directory) looking for an enclosing Hew checkout (a
directory containing
std/builtins.hew). This anchors a file inside one Hew worktree to that worktree's ownstd/, even when the binary running it was built in a different worktree. - Installed / external project — otherwise Hew searches, in order: the
FHS layout beside the binary (
<prefix>/share/hew), XDG (~/.local/share/hew),~/.hew,/usr/local/share/hew,/usr/share/hew, and a development fallback to the repo root whenstd/exists two levels above the binary.
hew.toml does not configure module search paths. Use HEWPATH or HEW_STD
when you need Hew to search a non-default stdlib or module root.
To browse shipped stdlib modules, generate docs for the stdlib tree:
hew doc std/ --output-dir doc/stdThis writes a browsable index page for the modules under std/. The canonical
module list also lives in std/README.md.
For import-resolution problems, see
docs/troubleshooting.md.
Wire types define versioned serialization schemas for use with actors and distributed protocols. Each field carries an explicit numeric tag (@1, @2, …) that is the field's stable identity across schema versions. You can safely add new tagged fields or rename existing ones; decoders that encounter an unknown tag skip it. Never reuse a tag number for a different field.
#[wire]
type UserMessage {
name: string @1,
age: i32 @2,
// Adding a new @3 field later is backwards-compatible; reusing @1 is not.
}
See examples/playground/types/wire_types.hew for a runnable example.
Actors communicate across nodes with a wire-tagged message enum and .send(), transparently across the network. The runtime handles transport, registry gossip, and remote dispatch.
// shared by both nodes: the wire-tagged message enum, bound to the actor
#[wire]
enum CounterMsg { Increment(i64); }
actor Counter {
var count: i64;
receive fn handle(msg: CounterMsg) {
match msg { CounterMsg::Increment(n) => { count = count + n; }, }
}
}
impl ActorMsg for Counter {
type Msg = CounterMsg;
type Reply = ();
}
// server node
Node::set_transport("quic-mesh");
Node::load_keys("node.key"); // mints/loads this node's stable identity
Node::start("127.0.0.1:9000");
let counter = spawn Counter;
Node::register("counter", counter);
// client node (separate process)
Node::set_transport("quic-mesh");
Node::load_keys("client.key");
Node::start("127.0.0.1:9001");
Node::connect("127.0.0.1:9000");
let found: Result<RemotePid<Counter>, LookupError> = Node::lookup("counter");
match found {
Ok(counter) => { let _ = counter.send(CounterMsg::Increment(42)); }, // remote message
Err(_) => println("counter actor not found"),
}
impl ActorMsg for Counter { type Msg = CounterMsg; ... } is what makes
RemotePid<Counter>::send accept a CounterMsg — without it the remote send
does not typecheck.
See examples/quic_mesh/ for a complete two-process QUIC mesh demo, and examples/distributed_hello.hew for the full key-backed identity and peer-pinning sequence.
The compiler is a Rust pipeline: frontend → HIR/MIR →
codegen-rs LLVM emission. hew-codegen-rs is the sole backend and is
linked into the hew binary as a normal Cargo dependency.
source.hew → Lexer → Parser → Type Checker → HIR → MIR → LLVM IR/object
(hew-lexer) (hew-parser) (hew-types) (hew-hir/hew-mir)
│
▼
hew-codegen-rs (Rust/Inkwell)
│
▼
hew links object + libhew.a → executable
Detailed diagrams: See
docs/diagrams.mdfor Mermaid diagrams of the compilation pipeline, actor/supervisor state machines, runtime architecture, and wire format.
- hew-cli/ — Compiler driver (
hewbinary) - hew-lexer/ — Tokenizer
- hew-parser/ — Recursive-descent + Pratt precedence parser
- hew-types/ — Bidirectional type checker with Hindley-Milner inference; warnings carry source-module attribution so diagnostics in multi-module programs identify which module triggered each warning
- hew-hir/, hew-mir/ — High-level and middle-level intermediate representations of the typed program
- hew-codegen-rs/ — LLVM-backed code generation via inkwell (the compiler backend, embedded in the
hewbinary) - hew-runtime/ — Pure Rust actor runtime (
libhew_runtime.a) with node mesh networking, QUIC transport, SWIM cluster membership, and cross-node actor registry; also compiles for WASM targets - hew-cabi/ — C ABI bridge for stdlib FFI bindings
- hew-pkg/ — Package-manager library behind the
hewsubcommands (init, add, install, publish, search) - hew-lsp/ — Language server (tower-lsp)
- hew-observe/ — Runtime observability TUI (
hew-observe) - hew-wasm/ — Analysis-only diagnostics frontend compiled to WASM (lexer/parser/type-checker for in-browser editor tooling); the full browser execution runtime is a v0.6.0 deliverable
- std/ — Standard library modules (
.hewsource files + Rust FFI crates)
- editors/ — Editor support (Emacs, Nano, Sublime)
- installers/ — Package installers (Homebrew, Debian, RPM, Arch, Alpine, Nix, Docker) plus install-time shell completion generation
- examples/ — Example programs and benchmarks
- scripts/ — Development scripts
- docs/ — Language specification and API references
Full documentation at hew.sh/docs
- Observability guide:
docs/observe.md - Local troubleshooting guide:
docs/troubleshooting.md - Website source: github.com/hew-lang/hew.sh
| Dependency | Version | Purpose |
|---|---|---|
| Rust | stable (latest) | Compiler, runtime, package manager |
| LLVM | 22.1 | Native/WASM object emission through inkwell/llvm-sys |
Install on Ubuntu/Debian:
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# LLVM 22 development libraries
sudo mkdir -p /etc/apt/keyrings
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key \
| sudo tee /etc/apt/keyrings/llvm.asc >/dev/null
echo "deb [signed-by=/etc/apt/keyrings/llvm.asc] http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" \
| sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null
sudo apt-get update
sudo apt-get install -y llvm-22-dev clang-22Install on macOS:
# Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# LLVM 22 development libraries
brew install llvmmake # Build everything (debug)
make release # Build everything (optimized)
make preflight # Diff-routed, fail-fast pre-PR gate
make test # Run Rust + native codegen tests
make lint # cargo clippySee the Makefile header for all targets.
Use make preflight ARGS="--dry-run" to inspect the selected commands before
running them. The standard pre-PR gate is diff-routed and fails fast after the
first failed command. Reserve make ci-preflight for integration and release
moments, when the run-all failure policy and full timing summary are useful.
The first slice stays conservative: known docs/parser/types/CLI diffs get
narrower checks, and everything else falls back to broader local preflight
commands.
The sandbox VM (hew-sandbox-vm) runs admitted Hew programs in a deterministic browser-hosted runtime with a virtual clock, seeded randomness, M4–M7 actor/channel/supervision semantics, and structured-concurrency coordination. Almost all of Hew runs in a browser today; the native-only class today is features that depend on OS threads (production supervision trees, real-time network I/O). A scoped browser runtime for those thread-dependent features (channel/select/sleep/supervisor/TCP) is a ratified v0.6.0 goal. Parallel work-stealing is a permanent native-only limitation — cooperative single-threaded execution is the final shape for the browser target, not an interim state.
This repo carries the analysis-side browser tooling (hew-wasm) plus the sandbox bytecode emission crate (hew-sandbox-wasm); the downstream browser app and the hew-sandbox-vm TypeScript worker are in hew-lang/playground.
make playground-manifest # regenerate examples/playground/manifest.json
make playground-manifest-check # cheap freshness check for manifest.json only
make playground-check # repo-local preflight: manifest freshness + curated analyze smoke + build hew-wasm
make playground-wasi-check # focused manifest-driven WASI runtime preflightUse make playground-manifest-check when you only need to confirm the checked-in manifest is current. Use make playground-check for the repo-local browser/tooling slice: curated hew-wasm analysis smoke plus the repo-local hew-wasm build (make wasm) that powers browser-side diagnostics tooling. Use make playground-wasi-check in codegen-capable environments when you also want the focused manifest-driven WASI runtime proof. The hew-wasm crate in this repo is analysis-only; the sandbox VM execution target and downstream browser app live in hew-lang/playground.
These are only needed for specific workflows:
| Dependency | Install | Purpose |
|---|---|---|
| wasmtime | curl https://wasmtime.dev/install.sh -sSf | bash |
Run the WASI end-to-end tests (make playground-wasi-check, and the wasi_run_e2e / eval_wasm_* cases inside make test) |
| wasm32-wasip1 target | rustup target add wasm32-wasip1 |
Build WASM runtime (make wasm-runtime) |
| wasm-pack | cargo install wasm-pack |
Build browser analysis bindings (make wasm, make playground-check) |
| Python 3 | system package manager | Playground manifest + other scripts (scripts/) |
| cargo-fuzz | cargo install cargo-fuzz |
Parser fuzzing (hew-parser/fuzz/) |
Hew is distributed under the terms of both the MIT license and the Apache License (Version 2.0).
See LICENSE-MIT and LICENSE-APACHE for details.