OCaml-style safety on Go’s runtime.
Exhaustive ADTs · branded IDs · maps · Go interop · same binaries and stdlib
Try Goop in the browser →
Playground: check, compile, and copy generated Go — no install
Editors: VS Code · Zed · Neovim · Helix
Sum types you can trust — every variant must be handled:
type OrderAck =
| Filled of { order_id: string; qty: int }
| Rejected of { reason: string }
let describe (ack: OrderAck) : string =
match ack with
| Filled { order_id; qty } -> order_id ^ " filled " ^ int_to_string qty
| Rejected { reason } -> "rejected: " ^ reason
Branded IDs — order_id and symbol are not interchangeable strings:
type order_id = | Order_id of string
type symbol = | Symbol of string
let place (sym: symbol) (oid: order_id) : string =
"placed"
let main () =
println (place (Symbol "ETH-USD") (Order_id "ord-1"))
Maps + option — lookups are Some / None, not “did I check ok?”:
let main () =
let prices : map[string] int = Map.make () in
let _ = Map.add prices "ETH" 3200 in
match Map.get prices "ETH" with
| Some px -> println (int_to_string px)
| None -> println "missing"
Go when you need it — import packages, or drop in inline Go / C:
import go "strings"
@[go] {
func greet(name string) string {
return "Hello, " + name + "!"
}
}
val greet : string -> string
@[c] {
int c_add(int a, int b) { return a + b; }
}
val c_add : int -> int -> int
let main () =
begin
println (strings.ToUpper "goop");
println (greet "world");
println (int_to_string (c_add 40 2))
end
Go lets these slip through. Goop stops them at compile time.
Forgot a match case
type Severity = | Low | High | Critical
let should_alert (s: Severity) : bool =
match s with
| Low -> false
| High -> true
✕ EXHAUST003: non-exhaustive match: missing constructor(s): Critical
╭─[example.goop:4:3]
3 │ let should_alert (s: Severity) : bool =
> 4 │ match s with
· ╰── EXHAUST003: non-exhaustive match: missing constructor(s): Critical
5 │ | Low -> false
╰────
Forgot None
let greet (name: string option) : string =
match name with
| Some n -> "hi, " ^ n
✕ EXHAUST003: non-exhaustive match: missing constructor(s): None
╭─[example.goop:3:3]
2 │ let greet (name: string option) : string =
> 3 │ match name with
· ╰── EXHAUST003: non-exhaustive match: missing constructor(s): None
4 │ | Some n -> "hi, " ^ n
╰────
Open the playground — type Goop, run check/compile in the browser, copy generated Go. No install, no local toolchain.
# One-line install (latest release binary)
curl -fsSL https://raw.githubusercontent.com/Macho0x/Goop/main/scripts/install.sh | bash
# Or build from source
cd src && go build -o ../goop ./cmd/goop
../goop new hello && cd hello
../goop check main.goop
../goop build main.goop # → ./goop-out
./goop-outShow what the compiler emits (no cache write):
goop compile --stdout docs/examples/hello.goopGenerated Go stays under $GOOP_HOME/build by default (unless --stdout or --in-tree).
Lowering is one-way — there is no Go → Goop auto-translator. Teaching pairs:
docs/examples/gallery/.
Tutorial (maps) · CLI artifacts · maps.goop · branded_ids.goop
Compile-time checks Go leaves to tests or panics — without giving up Go’s runtime.
| Go | Rust | OCaml | Goop | |
|---|---|---|---|---|
Sum types + exhaustive match |
❌ | ✅ | ✅ | ✅ |
No null by default (option) |
❌ | ✅ | ✅ | ✅ |
Recoverable errors as result |
❌ (error) |
✅ | ✅ | ✅ |
| Branded / nominal IDs | ❌ | ✅ | ✅ | ✅ |
| First-class maps | ✅ | ✅ | ✅ | ✅ |
| Effect handlers (OCaml 5-style) | ❌ | ❌ | ✅ | ✅ |
| Nil-channel / close safety | ❌ (runtime) | N/A | N/A | ✅ |
| Data-race awareness | -race |
✅ | ❌ | ✅ (conservative) |
| Refinement contracts | ❌ | ✅ (optional Z3) | ||
Inline Go embeds (@[go]) |
— | ❌ | ❌ | ✅ |
Inline C / cgo (@[c]) |
✅ | ❌ | ❌ | ✅ |
| Native Go stdlib + deploy | ✅ | ❌ | ❌ | ✅ |
| Compiles to ordinary Go binaries | — | ❌ | ❌ | ✅ |
STYLE.md · extern_demo.goop · cgo_demo.goop
v1.20.0 — @[go] import hoist + multi-file import merge. Playground is the fastest way to try Goop.
CHANGELOG · RELEASE_NOTES · Benchmarks · Language-update checklist
Production-ready? Compiler, checker, codegen, LSP, and CI e2e ship today. We are not claiming production load readiness yet.
Need OCaml or Z3? No. Pattern matching from Rust/Swift/Kotlin transfers. Z3 is optional ([check] smt = true).
| Playground | Try Goop in the browser (check, compile, copy Go) |
| Tutorial | Getting started through maps |
| Stdlib | Prelude and std.* |
| Examples | Checked in CI |
| Goop ↔ Go gallery | Hand-written teaching pairs (not a translator) |
| Writing tools | Files + maps via import go |
| Language-update checklist | Required sweep after language changes |
| Contributing | Build and test |
MIT / Apache-2.0 dual license.