hare-lang
Use when building or evaluating a Hare program: hare build, run, or test, tagged-union error handling, or calling C through bodyless fn declarations and -l. Not for Zig or C: use zig-cinterop or gcc.
SKILL.mdplugins/odin-native/skills/hare-lang/SKILL.md
GitHubHare
Hare is a small compiled systems language with a C-like execution model, tagged unions for errors, and a standard library that ships with the compiler. Current release 0.26.0.1 (point release of 0.26.0, 2026-02-13); there is no 1.0 and the project states it has not reached its planned 1.0 freeze. Supported targets: x86_64, aarch64, riscv64 on Linux, FreeBSD, OpenBSD, NetBSD, and DragonFlyBSD. No generics, no macros, no package manager.
Contract
| Field | Bound contract |
|---|---|
| Trigger | The user writes, builds, tests, or evaluates Hare code, needs the C FFI pattern, or asks whether Hare fits a utility, daemon, or tool compared with C or Zig. |
| Authority | Reversible local: writes only Hare source files, build outputs named by -o, and the toolchain install under the prefix the user chose; rollback is deleting those files or make uninstall in the toolchain clone. No remote mutation. |
| Side effect | Source and binary files in the project directory. A toolchain bootstrap compiles qbe, harec, and hare and installs them under /usr/local by default. |
| Done | The program builds with hare build, its tests pass under hare test, every error path is propagated with ? or handled in a match, and any C boundary is declared with the bodyless-prototype form and linked with -l. |
Inputs
- Source directory: a Hare module is a directory holding one or more
.hafiles. Dependencies resolve by scanningusedirectives, first in the working directory and then alongHAREPATH(a colon-separated list;hare version -vprints the default). There is no manifest file. - Toolchain:
qbe(backend),harec(compiler),hare(build driver and standard library). A C11 compiler andscdoc(for man pages, optional) are the bootstrap dependencies. - Build tags when a file is platform- or mode-specific:
+linux,+test,+libcin the file name or via-T. - For C interop: the C library name for
-land, when the header lives off the default path,-L.
Procedure
Install or confirm the toolchain. Check with
hare version. To bootstrap: clone andmakethenmake installharecfromhttps://git.sr.ht/~sircmpwn/harec, then the same forharefromhttps://git.sr.ht/~sircmpwn/hare(make checkruns its test suite first). Installqbefromhttps://c9x.me/compile/beforeharec. Root is needed only because the default prefix is/usr/local. Done when:hare versionprints a version.Lay out the module. Create a directory and a
main.ha:use fmt; export fn main() void = { fmt::println("Hello, Hare!")!; };Build with
hare build -o mytooland run with./mytool, orhare run .to do both. Done when: the binary prints the greeting.Handle errors as values. A fallible function returns a tagged union of its result and its error types.
?propagates an error to the caller;!asserts success and aborts on error;matchhandles each case.use fmt; use fs; use io; use os; use strings; use encoding::utf8; fn read_file(path: str) (str | fs::error | io::error | utf8::invalid) = { const file = os::open(path)?; defer io::close(file)!; const buf = io::drain(file)?; return strings::fromutf8(buf)?; }; export fn main() void = { match (read_file("config.txt")) { case let s: str => fmt::println(s)!; case let err: fs::error => fmt::fatalf("error: {}", fs::strerror(err)); case let err: io::error => fmt::fatalf("error: {}", io::strerror(err)); case utf8::invalid => fmt::fatal("config.txt is not UTF-8"); }; };os::openreturns(io::file | fs::error);osdefines no error type of its own.io::drainallocates and reads to end of file;io::readallfills a caller-supplied buffer instead.strings::fromutf8returns(str | utf8::invalid), never a barestr. Done when: the compiler accepts the function and every union member has acase.Use the type system as written. Tagged unions:
type color = (u8 | u16 | void);. Slices are pointer plus length:fn sum(nums: []i32) i32withfor (let i = 0z; i < len(nums); i += 1). Size literals carry thezsuffix. There are no implicit numeric conversions; cast withv: u16.abort()andabort("message")end the program;fmt::fatalfprints and exits. Done when: no conversion is implicit and everymatchon a union is exhaustive or has acase =>arm.Cross the C boundary with a bodyless prototype. Import
types::cfor C-compatible types and declare the foreign function without a body; add@symbol("name")only when the Hare identifier must differ from the linker symbol:use types::c; export type FILE = opaque; export @symbol("fopen") fn fopen(pathname: const *c::char, mode: const *c::char) nullable *FILE;Export a Hare function to C with the same attribute in reverse:
export @symbol("greet_user") fn greet_user(user: const *c::char) int = { ... };. Link withhare build -l <libname>; any-lalso links libc and adds the+libctag. There is no@externattribute. Done when: the program links and calls across the boundary.Write tests next to the code in
+test.hafiles with the@testattribute andassert:@test fn sum_small() void = { assert(sum([1i32, 2, 3]) == 6); };hare testadds the+testtag and runs them; a trailing glob selects tests by name.-vprints the compiler and linker commands, not per-test detail. Done when:hare testexits 0.Reach for the standard library before writing code:
fmt,io,os,fs,strings,bufio,memio,net(withnet::tcp,net::udp,net::dial,net::dns,net::ip,net::uri),time(withtime::date,time::chrono),encoding(base32,base64,hex,pem,utf8,asn1),types::c. JSON is not in the core library;hare-jsonis a separate extended-library repository providingencoding::json. Done when: no hand-written code duplicates a stdlib module.Decide fit. Hare suits command-line utilities, build tools replacing shell, small network daemons, and code that must stay auditable without a runtime. Choose Zig instead when compile-time generics or metaprogramming carry the design (
zig-cinteropcovers its C boundary), and C when the project is glue over a large existing C code base (gcc). Done when: the recommendation names the deciding constraint.
Failure and recovery
| Failure | Cause | Fix |
|---|---|---|
unknown type or unresolved identifier |
Missing use module; or module not on HAREPATH |
Add the use line; check hare version -v for the search path |
| Link error on a C symbol | Library not passed | Add -l <libname> and, for a non-default path, -L <dir> |
| Compiler rejects an unhandled union member | match not exhaustive |
Add the missing case or a case => arm; or propagate with ? |
utf8::invalid at runtime |
Bytes are not UTF-8 | Handle the strings::fromutf8 error case instead of asserting with ! |
| Test not run | File lacks the +test tag or function lacks @test |
Rename to name+test.ha and add the attribute |
| No stdlib module for a platform call | Library gap | Declare the libc function as a bodyless prototype and link with -l |
| Code from an older tutorial fails to compile | Pre-1.0 churn; 0.26.0 replaced @offset with _ padding fields |
Read the release notes for the installed version |
Output
A Hare module that builds with hare build, passes hare test, propagates or handles every error, and declares any C boundary with the bodyless-prototype form and the -l flags recorded; plus, when the question was fit, a recommendation naming the deciding constraint.