Content is user-generated and unverified.

Fresh Eyes on NURL v0.9.0: A Programming Language Built for Machines, Not Humans

Weekly Peer-Review · 24 May 2026

TL;DR

  • NURL v0.9.0 is a remarkably ambitious one-author solo project: a token-minimal, LLVM-backed, self-hosting language whose compiler, formatter, LSP, package manager, HTTP/2 server, MCP stack, MQTT 5 client and Anthropic SDK are all written in NURL itself — and most of it actually works when you press the buttons. The pace (ten tagged releases between 12 May and 24 May 2026) and the breadth (a 79-module stdlib, an HTTP server through h2/WebSocket/mTLS, a bidirectional MCP stack at protocol revision 2025-11-25) are genuinely impressive for a v0.9.0.
  • The headline thesis — "a language for LLMs, not humans" — is the most interesting and the most fragile part. Prefix notation with strict per-operator arity buys real token savings and a regular grammar, but it also produces a category of bug where ^ a b happily compiles as return a and "the rest" is silently discarded. The compiler has been working hard to surface these cases as error:/warning: diagnostics rather than gotchas, but my hands-on testing still found a use-after-free that compiled silently and a missing-parens call that compiled to a dead name lookup.
  • The bootstrap, cross-compile, and MCP claims hold up under PoC testing, but a few quantitative claims should be read with care: the "~1.19 MB fixed point" is plausible but the most recent figure I could pin down in release notes is 1,187,843 bytes (v0.6.1, 17 May 2026), with 1,482,115 bytes quoted in the 23 May 2026 MsgPack-serde entry — one day before the v0.9.0 tag (commit 196fa7e). The "~390 kB WASM toolchain" is a README assertion that I could not directly verify because the WASM build output exceeded my MCP transport limit. For an LLM-driven language ecosystem at v0.9.0, this is the most credible candidate I have personally compiled with — and also the one most in need of more eyes, not fewer.

Key Findings

I came at NURL the way the brief asked me to — as an outsider with the playground, MCP toolchain, and source tree in front of me, and no prior loyalty. The headline impressions, before details:

  1. The "regular grammar fits on a page" claim is mostly true. The EBNF in spec/grammar.ebnf (grammar v2.0, with v2.1 pub-visibility extensions documented in the README) really does fit in a screenful of productions, and the parser is documented as LL(1) with ≤4-token lookahead. Compared to Rust's tree-shaped reference grammar this is a different universe.
  2. Self-hosting is real, with caveats. compiler/nurlc.nu is committed in NURL; compiler/nurlc_lastgood.ll is the stage-0 IR snapshot; build.sh runs the stage1→stage2 fixed-point check on every build. The Python reference compiler was removed in the refactor/pure-nurl branch on 2026-05-23 — i.e. one day before the v0.9.0 tag. That is a very recent change, and the fact that bootstrap stability survives it is the strongest single signal that the toolchain is internally consistent.
  3. The standard library is unusually wide for an LLVM-side-project language. 79 stdlib modules cover the usual suspects (Vec, HashMap, Set, Channel, Mutex, fs, fmt, sort, iter, time, log, hash family) plus TLS-capable HTTP/1.1 + HTTP/2 + WebSocket plus Anthropic Claude (multimodal, prompt caching, extended thinking, streaming SSE tool-use), MCP server+client over both HTTP and stdio, MQTT 5.0 (QoS 0/1/2, TLS, keep-alive), SQLite, PostgreSQL, MsgPack, TOML, regex, UUID v4/v7, gzip/zlib/zstd, an arena allocator and a Rust-PathBuf-style typed Path. The "ext" wing reads like a product-ready batteries-included list, not a hobby project.
  4. The cross-compilation story works. I cross-compiled a NURL hello world to a 1,400,728-byte aarch64-linux-musl fully-static ELF on the first try, via the nurl_build_target MCP tool. The Zig-cc-driven targets (linux-{x64,arm64,riscv64}-musl, linux-arm64-gnu, macos-{x64,arm64}) come straight off the same LLVM IR with no per-target porting — the same trick that has made zig cc famous as a drop-in C cross compiler, applied here to a different language's IR.
  5. The cracks show up exactly where the design philosophy is most aggressive. Prefix-arity strictness gives no closing token and no operator precedence, which means a missing operand silently slurps the next statement. The team is aware (docs/GOTCHAS.md is now an essentially empty file pointing at compiler diagnostics, with a single remaining edge documented in README) but I still hit cases where the diagnostic landed on the wrong line or didn't fire at all.

Details

1. Language Design & Syntax — The ^/^^ Surprise Is Real

NURL's syntax is unambiguously the strangest thing about it. Every expression is prefix: + a b, * x 2, == n 0. Every call is parenthesised — ( puts s ) — and a bare identifier is always a name lookup, never a call. Single-letter type keywords carry full meaning: i u f b s v, with i8 i16 i32 u16 u32 u64 f32 for sized ints. Operators are single sigils: : binds, = assigns, @ defines functions / constructs aggregates, ? is ternary, ?? is exhaustive pattern match, ~ is loop and mutability prefix and bitwise complement (context-disambiguated), \ is try-propagate and lambda (also context-disambiguated), ^ is return, and — here is the brief's "surprise" — ^^ (two adjacent carets) is XOR.

In other words: ^ a b parses as return (a b …), but ^^ a b is bitwise/logical XOR. The README documents this as a remaining edge case in Known Limitations → Grammar, and docs/GOTCHAS.md calls it out explicitly: "^ is the return keyword, not XOR — but ^^ (two adjacent carets) is the native XOR operator. The lexer pairs ^^ only when the carets are adjacent, so a stray space (^ ^) still means two returns."

Hands-on, the trap fires exactly as advertised. I wrote this and it compiled clean:

@ main → i {
  : i a 5
  : i b 3
  : i x ^ a b    // intended XOR; actually "return a"
  ^ 0
}

The compiler emitted no diagnostic, produced a 68,728-byte ELF, and the ^ 0 at the end was effectively dead code because ^ a b already returned a from main. The compiler did catch a different misuse — ^ ^ a b (return-of-return-of-XOR) inside a typed function — with a useful pointer:

caret_surprise.nu:2:9: return expression has no value (expected i)
  — likely a conditional with incompatible branch types
  ^ ^ a b
        ^

That diagnostic is helpful, but it doesn't actually name the ^ vs ^^ confusion. For a language that is otherwise so disciplined about putting cures in error messages, this one deserves a dedicated note: along the lines of "^ is the return operator; for XOR use ^^ (no space)" — exactly the gotchas-table cure, raised to compile-time. That would close the trap for any LLM (or human) that didn't read the spec first.

The other prefix-arity trap I hit is older and well-documented: a missing operand silently consumes the next statement's first token. I forgot the parens on a call:

@ main → i {
  nurl_print `oops, forgot parens\n`
  ^ 0
}

This compiled clean (nurl_print is a bare identifier, which is a name lookup, not a call; the literal becomes a discarded expression). The diagnostic that would have helped — "statement has no effect; did you mean ( nurl_print … )?" — doesn't exist yet, although v0.8.0 did ship the related "a parenthesised operator expression is now a compile error" check (so ( . obj field ) no longer mis-compiles into a call to a function literally named .). This whole class of errors — grammar-legal but semantically dead — is the natural next frontier for the NURL diagnostic suite.

On the positive side: token efficiency is real. The README's worked example (sum 1..n in ~13 NURL tokens vs ~46 Python tokens) holds up across the showcase. The compiler's own LLM grounding prompt (nurl_coding_assistant) is short enough to fit in a small context window without losing the language definition. This puts NURL squarely in the line of recent academic work on "AI-oriented grammar" — SimPy reported 13.5% / 10.4% token reductions for CodeLlama / GPT-4 against ordinary Python on the same tasks; Anka claimed a 40-point accuracy advantage on multi-step pipelines by constraining syntax. NURL takes the same intuition further by giving up infix entirely.

2. Type System & Memory Model — Single-Owner + Optional Borrow Checker

NURL's memory model is the most pragmatic part of the design. It is not trying to be Rust:

  • Default-immutable bindings: : i x 10 is immutable; : ~ i x 10 is mutable. Assignment to an immutable binding is a compile error.
  • Strong, static, inferred types; no implicit conversions, no subtyping.
  • Algebraic types: sum types via : | Name { Variant payload... }, product types via : Name { field... }. Pattern match (??) is exhaustivity-checked.
  • Generic structs and functions: Vec[A], HashMap, Channel[A], Slice[A], Pair[A B]. Monomorphised at compile time (mangled named LLVM types like %Vec__i64, %Vec__str).
  • Single-owner heap with compiler-inserted auto-drop at scope exit. Five phases of this are documented in the README — slice-literal ownership, slice-returning-call transfer, string auto-drop for allocating runtime calls, struct-field auto-drop on construction. There is no garbage collector.
  • Static borrow checker, on by default, disable with --no-borrowck. It catches use-after-move, aliasing of owned heap values, and closures that capture a : ~-mutable struct by pointer and then escape (return, vec_push, thread_spawn, longer-lived assignment). It emits warning: and never changes generated code — a borrow-clean program compiles to byte-identical IR with or without it. Per docs/MEMORY.md, "Aliased-mutation (exclusive-access 'N readers XOR 1 writer') checking is not yet implemented."

This is a strictly weaker model than Rust's. The trade — friendlier to LLM authoring, less proof-of-no-aliasing — is honestly disclosed.

Hands-on, I tried to trip the borrow checker the way I would Rust's:

$ `stdlib/core/string.nu`

@ take String s → v {
  ( string_free s )
}

@ main → i {
  : String s ( string_from `hello` )
  ( take s )
  ( nurl_print ( string_data s ) )   // use after free
  ^ 0
}

This compiled silently with no warning, and (if executed) would touch freed memory. The checker is good at the documented escape-into-longer-lifetime cases (it warns on ^-returning a closure that captures a : ~-mutable struct, on vec_push of such a closure, etc.), but a direct use-after-explicit-free-call sailed through. That is consistent with the documentation — the borrow checker is described as catching aliasing and escape, not every misuse of an explicit _free call — but it does mean newcomers should treat the static checker as a strict help, not a Rust-grade safety net.

A second concrete trip: passing a multi-field generic struct through a slice view. I tried ( vec_as_slice [i] v ) and got:

<generic>:1:21: unexpected token, expected tt=9
@ vec_as_slice__i64
                    ^

That is the exact failure shape the roadmap calls out as Phase 7 follow-up work: "per-instantiation source-line precision for generics, today they point at synthetic <generic>:1 rather than the original decl line." Rebuilding with vec_len/vec_get directly worked first try.

Idiomatically, NURL feels like "Zig minus comptime, plus a real ownership model": bounds-checked slices, tagged unions, exhaustive match, ?T/!T E for nullability and errors, explicit \-propagate (the cousin of Rust's ?), ; { … } defers, no implicit allocation. The aesthetic differs sharply from any of them.

3. Standard Library & ext Modules — Surprisingly Production-Shaped

Reading the stdlib is where my skepticism flipped to interest. The breakdown:

  • core/ (13 files): box, cell, char, errors, io, mem, option, pair, posix, result, slice, string, vec. Standard primitives.
  • std/ (29 files): arc, arena, async, async_ffi, bufio, bytes, channel, cmp, dos, encode, float, fmt, fs, hash + hash_md5/sha1/sha256/sha512, hashmap, int, iter, log, net, panic, path, process, random, rc, set, signal, sort, thread, time.
  • ext/ (36 files): anthropic, compress, csv, env, http + http2_{conn,frame,hpack,server} + http_{auth,full,json,middleware,multipart,proxy,request,response,router,server,static}, json, manifest, mcp + mcp_{client,http,registry,stdio}, mqtt, msgpack, postgres, regex, serde, sqlite, toml, uuid, websocket.

Several pieces stand out for a v0.9.0:

HTTP/2 (RFC 9113 + RFC 7541). Server-side h2 across four modules totalling ~2,130 LOC of NURL plus ~120 LOC of C for the OpenSSL ALPN bridge. Includes a full HPACK codec (61-entry static table, dynamic table with FIFO/size eviction, N-bit prefix integer codec, Huffman decoder over all 257 Appendix B codes) and a stream state machine per RFC 9113 §5.1, with flow control via WINDOW_UPDATE. ALPN dispatch lets the same ( @ HttpResponse HttpRequest ) handler serve both h1 and h2. The 18 May 2026 changelog entry states the codec was "Verified offline against RFC 9113 §4 frame vectors and RFC 7541 Appendix C HPACK + C.4.1 Huffman vectors — regression compiler/tests/http2_basic.nu." v1 is server-only, no h2c, no PUSH_PROMISE, no PRIORITY — a sensible scope.

WebSocket (RFC 6455). Server-side handshake (base64(SHA1(key + GUID)) for Sec-WebSocket-Accept), framing with RSV/opcode/control-frame validation, RFC 3629-strict UTF-8 on text payloads (rejects overlongs, U+D800–U+DFFF surrogates, codepoints > U+10FFFF), client→server unmasked-frame rejection per §5.3, and a full close-handshake state machine. SHA-1 was added to the runtime as a self-contained ~80-LOC public-domain transform. v1 is server-only; no permessage-deflate.

TLS with SNI, mTLS, live cert reload. TLS 1.2 minimum; SNI dispatches per-vhost cert/key pairs through SSL_CTX_set_tlsext_servername_callback; live reload is an atomic SSL_CTX swap under a per-listener mutex (in-flight connections survive via OpenSSL's refcount). mTLS via SSL_VERIFY_PEER plus tcp_peer_cert_subject to read X509 DN for authorisation. This is the kind of HTTPS terminator you would normally reach for nginx or caddy to provide.

Anthropic Claude SDK (ext/anthropic.nu). Streaming SSE with token-by-token and tool-call-argument-by-token deltas (claude_stream_event_input_json_delta, _index, _block_kind, _tool_use_id, _tool_use_name, _stop_reason, _error_type, _error_message). Multimodal inputs, prompt caching, extended thinking, tool-use loops are documented as supported. Practically, this is the lowest-friction way I have seen to write a Claude agent in a compiled language.

MCP, both directions. Server side: mcp.nu low-level builders plus mcp_registry.nu (~550 LOC, closure-based registry framework over the generic-channel/closure-in-Vec compiler fix from 17 May), with stdio (mcp_serve_stdio) and HTTP (mcp_http_dispatch_for_registry) adapters and a bearer-auth middleware. Client side: HTTP via mcp_client.nu, and a duplex-stdio path via mcp_stdio.nu over a process_spawn primitive. Protocol version 2025-11-25 is centralised through mcp_protocol_version, with a tools/mcp_spec_drift_check.sh script designed for CI/cron to detect spec drift against the modelcontextprotocol.io versioning page. Per the MCP Core Maintainers' "One Year of MCP: November 2025 Spec Release" post, 2025-11-25 is indeed the latest stable specification revision: "we're also releasing a brand-new MCP specification version… the 2025-11-25 version of the MCP specification." NURL is, as far as I can tell, the first compiled, LLVM-backed language to ship a bidirectional MCP stack as a first-class stdlib module rather than a third-party SDK — the official MCP SDKs are in Python, TypeScript, C#, Java and Kotlin, with no native compiled-language entry.

MQTT 5.0 (ext/mqtt.nu). Production-grade client over the pure-NURL socket layer: TLS (port 8883) or plain TCP, full QoS 0/1/2 with PUBREC/PUBREL/PUBCOMP, retained messages, MQTT 5 user properties, framed packet reader (handles TCP segment splits + multiple-packets-per-segment), keep-alive, rotating packet-id allocator, background listener via channel.

Two architectural notes I appreciated. First, the pure-NURL FFI pattern: PostgreSQL's libpq, libsqlite3 (mostly), libz/libzstd, libssl, and libc's printf family are all declared with & "library-name" @ name … → type directly from NURL, with no runtime.c glue unless the library needs platform-specific state caching (sqlite3's column_text overwrite-on-next-call semantics; the platform-variable z_stream layout). This is a meaningful architectural choice — most languages route everything through their own C runtime. Second, the compile-time FFI library check: if a program imports a library whose build-time sentinel (stdlib/runtime.<lib>) is missing, the compiler dies at the &-decl site with "FFI library 'pq' is required but no build-time sentinel 'stdlib/runtime.pq' found - install libpq-dev (or equivalent) and run build.sh again." Replaces a cryptic linker undefined reference to PQconnectdb with a one-line cure.

4. Toolchain, Self-Hosting & Bootstrap — PoC Build Results

nurlc.nu (the compiler) is written in NURL. The bootstrap chain is:

  1. Stage 0. clang links the committed compiler/nurlc_lastgood.ll (target-triple-agnostic LLVM IR text) plus stdlib/runtime.o into build/nurlc_lastgood.bin. The only build-time dependency is clang/LLVM 14+. The Python reference compiler (compiler/nurlc.py plus compiler/src/*.py) was deleted on 2026-05-23, one day before the v0.9.0 tag.
  2. Stage 1. nurlc_lastgood.bin compiles compiler/nurlc.nubuild/nurlc_self.llbuild/nurlc_self.
  3. Stage 2. nurlc_self compiles compiler/nurlc.nu again → build/nurlc_self2.llbuild/nurlc_self2.
  4. Fixed-point check. diff nurlc_self.ll nurlc_self2.ll must be empty; mismatch fails the build.

This is the same architectural pattern that Zig and Rust converged on for reasonable bootstrap: a target-agnostic IR blob committed to the repo, plus a deterministic compiler. The wrinkle vs Zig is the bootstrap medium. As Andrew Kelley described in "Goodbye to the C++ Implementation of Zig" (ziglang.org, December 2022): "We provide a minimal WASI interpreter implementation that is built from C source, and then used to translate the Zig self-hosted compiler source code into C code. The C code is then compiled & linked… into a stage2 binary." NURL commits LLVM IR text directly instead — a more compact seed (the committed .ll is in the low single-digit MB range and is diffable in git) that does require trusting the LLVM IR format as a bootstrapping medium. Pragmatically: clang has consumed LLVM IR text reliably for a decade.

On the byte-identical claim. The README and roadmap repeatedly cite "stage1 ≡ stage2 byte-identical IR at N bytes" as the acceptance gate, and historical release notes give concrete numbers — 1,068,160 B for the multi-field-Option fix (14 May), 1,151,786 B for panic-recovery (15 May), 1,187,843 B at v0.6.1 (17 May), 1,478,123 B at the typed-Path ship (22 May), and 1,482,115 B at the MsgPack-serde ship (23 May, one day before the v0.9.0 tag). The "1.19 MB fixed point" quoted in the brief is consistent with the v0.6.1 figure; the v0.9.0 release notes themselves no longer quote a byte count, restating only that the fixed point "held on every shipped phase." The "390 kB WASM toolchain" lives only in the README: "the same POST /build_wasm pipeline can be pointed at compiler/nurlc.nu itself, producing a ~390 KB nurlc.wasm that is the NURL compiler." I tried to build this directly through the MCP nurl_build_wasm tool; the result exceeded my tool transport limit (the response was 306,021 bytes of base64-encoded WASM + logs, which is consistent with a roughly 200 kB raw .wasm for a much smaller program — meaning the README's ~390 kB number is plausible, but my one PoC attempt is not strong evidence for the exact figure). A direct file fetch from a checkout would be the cleanest verification.

Other PoC builds I ran on the public playground:

  • Hello world (native Linux x86_64). Compiled clean: 4,983 bytes of LLVM IR → 68,720-byte ELF.
  • Vec + iteration (native). vec_new[i] / vec_push[i] / vec_len[i] / vec_free[i] round-trip: 14,442 bytes IR → 69,184-byte ELF. No diagnostics.
  • Cross-compile to linux-arm64-musl. Same source IR, 1,400,728-byte fully-static aarch64 ELF, first try.
  • WASM build of hello world. Succeeded; result exceeded my MCP tool's 200 kB response cap, which I read as evidence that the wasm32-wasi pipeline works (the underlying clang --target=wasm32-wasi -O2 toolchain is bundled via the WASI SDK 24.0 inside the API container).

The playground itself was reliable but not bulletproof — two of my build calls stalled with "no progress updates" timeouts after ~30 seconds (one on a moderately large program with stdlib/core/string.nu imported, one on a re-attempt). Retrying succeeded. The build pipeline shells out to clang inside a container; that is fine for prototyping but operators should remember the public endpoint is unauthenticated and source-logged (the README is explicit about this).

5. Developer Experience & Diagnostics — A Genuinely Distinctive Approach

NURL is the first language I have used where docs/GOTCHAS.md is deliberately nearly empty, with the contents replaced by a table mapping symptoms to compiler diagnostic strings:

"The historical 'language gotchas' list is empty as of v0.7.1+. Every trap that previously needed memorisation now surfaces as a compiler error: / warning: with a pointing caret and the concrete cure inline. The remaining edge — prefix-arity strictness — is documented in README's Known Limitations → Grammar section…"

The table is impressive: ^ ?? v { … ^ in arms }error: with the : ~ T rc / ?? { … = rc v } / ^ rc cure inline; string_len i8*error: plus which helper to use; a parameter shadowed by a :-binding → warning:; an @-fn used directly as a closure value → error: with the \ args → R { ( fn args ) } wrap template; wrong-arity calls → error: call to 'f' has the wrong number of arguments: expected N, got M; a prefix operator over-reading the next line → error: naming the offending token and pointing back at the line that was short an argument.

This is the design philosophy that other modern compilers preach but rarely enforce as a documentation policy. Elm and Rust have famously good error messages; NURL is trying to make every historical pitfall a compiler diagnostic, on the explicit grounds that an LLM consuming a compiler error is more reliable than an LLM remembering a docs page. That is genuinely novel.

Where it falls short: my PoC found three cases where the diagnostic was less helpful than the GOTCHAS table promised.

  1. ^ a b (return-of-binop) instead of ^^ a b (XOR) compiled silently when the function return type matched the intended XOR result type. The intended-XOR-mistaken-for-return trap is exactly the kind of case where a note: ("did you mean ^^ for XOR?") would close the loop, and it would cost almost nothing to detect heuristically.
  2. Use-after-_free compiled silently (above). The borrow checker is documented as catching escape, not explicit-free-followed-by-use; making this case a warning: would be a clear win.
  3. The <generic>:1 synthetic line in the diagnostic for vec_as_slice errors is acknowledged in the roadmap as Phase 7 follow-up work for DWARF per-instantiation source lines. Until that ships, generic monomorphisation errors are markedly harder to debug than non-generic ones.

The compiler is also unusual in that it surfaces useful warning text in ASCII only — the README notes that the (now-removed) Python stage-0 lexer had issues with multi-byte UTF-8 in literals, so the diagnostic messages avoid em-dashes and §. With Python now gone (2026-05-23), this restriction may relax.

6. Ecosystem & Tooling — More Than I Expected

The supporting cast at v0.9.0:

  • nurlfmt — deterministic, opinionated, gofmt-style formatter (~750 LOC of NURL across tools/nurlfmt/{nurlfmt,tokenize,pretty}.nu). Round-trip acceptance is enforced: fmt(fmt(x)) == fmt(x) and nurlc(fmt(x)) == nurlc(x) byte-identical, validated across 263 .nu files (251 IR-equivalence-covered, 12 include fragments skipped). This level of formatter rigour matches rustfmt / gofmt in policy, not just intent.
  • nurl-lsp — stdio JSON-RPC language server (~1,850 LOC in NURL). Live diagnostics, go-to-definition (single-file + cross-file via $ import index), hover, document outline, IDENT-prefix completion, workspace symbol search, folding ranges, nurlfmt-backed formatting.
  • VS Code extension (tooling/vscode-nurl/, v0.4.4 per the v0.7.3 release notes) launches nurl-lsp over stdio. ./install.sh is a one-shot install path.
  • nurlpkg — Cargo-shaped package manager with init, info, deps, add, remove, install, lock, verify, version, help. Manifests use a TOML subset compatible with stdlib/ext/toml.nu. Path-deps only in v1 (registry-style version resolution is explicitly out of scope), with lockfile drift detection.
  • DWARF debug info via nurlc --g: gdb/lldb break by name or by .nu:line, print x with NURL-flavoured type names, ptype Point lists struct field roster, print p renders {x = 3, y = 7}. ASan / UBSan under ./build.sh --san — current sanitised corpus reports 188 PASS / 0 SAN_FAIL / 18 deliberately skipped, run manually rather than per-build.
  • Public MCP toolchain endpoint at https://play.nurl-lang.org/mcp exposes 15 build/browse/read tools, 7 resources, and a nurl_coding_assistant prompt. This is the surface I used for every PoC build in this review.

What is missing for v1.0, by the roadmap's own admission: async/await design (the fiber runtime in stdlib/std/async.nu is at Phases 1-8 with noinline workarounds for LLVM/glibc TLS-through-LTO behaviour); generic signal handling; UDP and full DNS resolution; mobile/embedded targets; GitHub Actions CI wiring; structured logging with key-value pairs; a formal docs/spec.md. None of these are blockers for the language as it stands.

7. Grounded Comparison — Rust, Zig, Nim, Hare/Odin/V

A short, evidence-based positioning:

AspectNURL v0.9.0RustZigNim 2.x
Memory modelSingle-owner + auto-drop; static borrow checker on by default; closures use RC for captured envAffine ownership + borrow checker; no GC; lifetime-parametricManual + allocator-passing; no borrow checker; no GCARC/ORC reference counting with cycle collector; deterministic destructors
Self-host bootstrapStage 0 = clang-linked committed .ll; stage1/2 fixed point byte-identicalrustc 1.x.0 built by rustc 1.(x-1).0; six-week beta chain per endoflife.date/rustStage 0 = zig1.wasm → wasm2c → C → stage1 → stage2/3 fixed pointSelf-hosting since 2008 (per Wikipedia); refc/ARC/ORC chosen at compile time
Cross-compilezig cc driven for non-x64 Linux + macOS; clang-direct for x64 Linux/Windows; wasm32-wasi via WASI SDKrustup target add + bring own linker; cross via cross or zigFirst-class: zig build-exe -target … with bundled libcsCompiles to C/C++/JS; cross is reliable but more manual
Compile-time computationNone (deliberate v1 scope)const fn, macros, GATscomptime (pervasive)Macros + static: blocks; meta-programming heavyweight
Stdlib HTTP/2 + MCPYes, in-treeNo (hyper, async-mcp)NoNo
Syntax stylePolish prefix, parens only for calls and groupingsC-family infix with traitsC-family infix with comptime sigilsPython-style indentation

Versus Rust. NURL's borrow checker is intentionally weaker — it does escape analysis and use-after-move on owned heap values, but not exclusive-access / "N readers XOR 1 writer" — and there is no lifetime parameterisation. For an LLM-driven workflow that cannot solve borrow-checker puzzles, this is probably the right trade. The friendliness shows: I had a working HTTP+JSON program compiled and running far faster than I would in Rust. Rust itself has been moving toward more permissive borrow-check semantics with Polonius, exactly because the original analysis is sound-but-incomplete — Will Crichton's "The Usability of Ownership" (arXiv:2011.06171) makes this point in academic depth.

Versus Zig. This is the most relevant comparison. Both languages: LLVM-backed, self-hosting, target-triple-agnostic bootstrap blob, zig cc-driven multi-target. NURL has no comptime (its compile-time evaluation surface is minimal) and no equivalent of Zig's allocator-passing convention; in exchange it has a borrow checker and a single-owner heap. The "compiler-in-a-WASM-blob" trick is the same trick — Zig commits zig1.wasm, NURL produces nurlc.wasm from compiler/nurlc.nu against the running compiler. NURL is much younger, with a much smaller community, but the same architectural pattern is in evidence.

Versus Nim. NURL's prefix notation versus Nim's Python-shaped indentation could not be more different. Both have first-class generics and effects-free move semantics; Nim's ARC/ORC is closer to NURL's single-owner-with-RC-for-closure-environments. Per Wikipedia, Nim's initial public release was 2008, v1.0 was September 2019 and v2.0 (default-ORC) was August 2023 — eighteen years of public history. NURL has no chance of matching that library depth at v0.9.0.

Versus Hare, Odin, V. Hare is the closest "small, conservative C replacement" peer; Drew DeVault wrote on harelang.org (June 2022) that "I have not ruled out the possibility of adding a borrow checker, though", and the hare-rfc list in February 2024 noted "current research on memory safety is focused on linear types" — i.e. Hare has not added one. Odin is data-oriented with a slick build system and no borrow checker. V's public reputation for over-promising is documented by Justinas Stankevičius in "The bizarre world of V" (justinas.org), which catalogues "V's unfulfilled promises, questionable decisions, and its uncertain future"; the vlang/v repository's own issue #35 (2019) opened with "What you have promised does not even come close to what is delivered." NURL distinguishes itself from all three by not targeting humans as the primary reader — the explicit "Non-hUman Readable Language" framing is unique.

What is genuinely novel. First, prefix-arity-strict notation with no operator precedence as a serious language design — the only comparable production language is Lisp, and Lisp insists on closing parens. Second, an in-tree bidirectional MCP stack at the just-current 2025-11-25 protocol revision — no other compiled language has this as a stdlib module today. Third, a public MCP-server-exposed compiler-as-a-service that lets an LLM compile and inspect the toolchain from inside its own loop, no local install required. Fourth, "GOTCHAS.md is empty by policy" — every historical pitfall is a compiler diagnostic.

What is reinventing existing wheels. The HTTP server is comparable in scope to dropwizard/aiohttp/actix-web; the package manager is explicitly Cargo-shaped; the formatter is gofmt-shaped; the LSP/VS Code/DWARF triplet is the standard combo. None of this is bad — it is exactly what a language at v0.9.0 needs — but a prospective adopter should not expect any of it to be more capable than the original wheels.

8. Honest Assessment of Maturity and Gaps

For a v0.9.0 language with a public release tag dated 24 May 2026 (commit 196fa7e333b31606d9bdd6085fed3cac268280d5, GPG-signed by tagger Hindurable), NURL is unusually feature-complete on the breadth axis and unusually thin on the depth/eyes axis. The repository is small in social terms — 4 stars, 2-3 forks, two commits on main (squashed history), and only one tagger producing GPG-signed releases — while the codebase is large in technical terms. The cadence is staggering: ten tagged releases between 12 May 2026 (v0.1.0) and 24 May 2026 (v0.9.0), multiple per day on some dates. That cadence is consistent with a single very productive author iterating with LLM assistance against the language's own MCP toolchain. It is also a cadence that almost any community would struggle to review.

The documentation lag is mild and self-acknowledged: the README's manual VSIX install instructions reference nurl-0.1.0.vsix while the v0.7.3 release shipped nurl-0.4.4.vsix; a v0.6.1 release-notes body has an internal 2025-10-19 date that mismatches its 17 May 2026 publication; docs/spec.md is on the roadmap but absent. These are minor and easily fixed.

What I would worry about as a prospective user:

  1. Audit surface. A single-author language, however productive, has not had hostile eyes on its codegen. Sanitizer runs are encouraging but compiler/nurlc.nu is large and the compiler-fix-per-day cadence increases the surface area for subtle latent bugs (the 21 May "side-effecting ~ while-condition iteration-drop fixed" entry is exactly the kind of bug that needs years of fuzzing to flush out comprehensively).
  2. WASI / async / TLS-through-LTO carry documented platform-leak workarounds (noinline annotations, manual sanitiser invocation, defaults that turn off LTO for debug builds). All of these are reasonable v0.9.0 caveats; none are blockers.
  3. The "1.0" milestone is not the same shape as Zig's 1.0 or Rust's 1.0. Roadmap items still open include async/await design, UDP+DNS, mobile/embedded, GitHub Actions CI, and a formal spec doc. NURL's strategy seems to be "ship the things real consumers ask for and call those done"; that is internally consistent but means versioning is a less reliable signal than in Rust.

What is impressive:

  1. Self-host stability through aggressive change. Removing the Python reference compiler the day before tagging v0.9.0 is the kind of move most projects do not survive. NURL did.
  2. Diagnostic-first culture. The volume of "shipped diagnostic X" entries on the changelog is striking — the project understands that compiler errors are the API.
  3. The breadth of the ext stdlib at one author's level of effort is hard to explain without acknowledging that the author has effectively turned the language and its MCP toolchain into a force multiplier.
  4. The dual MIT/Apache-2.0 license. Quoting the README's License section verbatim: "Copyright (c) 2026 The NURL Project Developers. NURL is dual-licensed under either of: MIT License (LICENSE-MIT), Apache License, Version 2.0 (LICENSE-APACHE) at your option. SPDX identifier: MIT OR Apache-2.0." This matches the Rust/Zig precedent and is the right choice for both adoption and patent-grant safety.

Recommendations

For NURL maintainers (constructive, prioritised):

  1. Close the ^ vs ^^ trap at compile time. When a function declared → i has a body whose ^-returned expression is itself a binary expression on i, and the following statement is grammar-legal but never executed, emit a note: "did you mean ^^ for XOR? ^ returns from the function; ^^ is XOR." This single addition would close the most-cited remaining gotcha by name. Threshold to revisit: when the next significant external review flags a different rough edge.
  2. Make use-after-explicit-free a warning:. The borrow checker already tracks owned-heap escape; extending it to flag a load through a binding whose _free-family helper has been called in the same scope is a small extension with a large quality-of-life win for newcomers. Threshold: when a real user files a bug report.
  3. Fix per-instantiation DWARF source lines (Phase 7). The <generic>:1 synthetic-line issue is by far the largest contributor to confusing error messages I encountered. Tracked on the roadmap; raising its priority would pay off broadly.
  4. Publish the v0.9.0 fixed-point IR byte size in the release notes. The convention of stating the exact byte count in every release entry was abandoned somewhere around v0.7.x; restoring it for tagged releases is a free trust signal. Same for the actual size of nurlc.wasm against a known build.
  5. Add a Polonius-style "next-gen" borrow-checker behaviour switch. The README admits exclusive-access checking is not yet implemented. Even a --strict-borrowck flag that errors instead of warns, plus an aliased-mutation pass behind a feature flag, would let early adopters opt in to stronger guarantees without disturbing the LLM-friendly default.
  6. Document the ~390 kB nurlc.wasm claim with a reproducible recipe in the README, and ideally check the artefact into a release-artifacts/ branch. As a quantitative public claim it deserves a public artefact.
  7. Mild documentation polish (a single mention, as the brief asks): the README's VSIX install path is one revision behind the release notes, and a top-level CHANGELOG.md would make the release history searchable through tools that cannot scrape GitHub Releases. The team is clearly aware that docs trail code in fast-moving phases; this is a small, easy cleanup whenever a docs sprint comes up.

For prospective users:

  1. Reach for NURL today if you are building an LLM-driven agent or pipeline that needs (a) a compiled, cross-platform target with a small toolchain footprint and (b) deep, in-stdlib MCP + Anthropic integration. The friction-to-first-Claude-call is, in my hands, lower than any other systems language.
  2. Stay on Rust / Zig today if you need a stable language with multi-year ecosystem depth, exclusive-access borrow checking, or active CVE triage. Re-evaluate when NURL ships a formal spec.md, a stronger borrow checker, and a regular external-audit cadence.
  3. Pilot NURL on a non-critical service (e.g., the examples/static_server.nu template extended with a few business endpoints, or an MCP-bridge sidecar) before betting a production codepath on it.

Caveats

  • Single-author project at single-digit stars (4 stars, 2-3 forks). I am reviewing the technical artefact, not the social bus-factor. Any production adoption decision should weight that separately.
  • Public playground endpoint is unauthenticated and source-logged. Do not paste secrets into the hosted MCP. Self-hosting the container is straightforward (docker build -f api/Dockerfile -t nurl-api:dev .); use it for anything sensitive.
  • Two of my MCP build calls stalled (~30 s tool timeout). Retries succeeded both times. The build pipeline is reliable but not at SaaS-grade uptime.
  • One PoC was not fully verified. The "~390 kB nurlc.wasm" claim is plausible from my one WASM build (output size ~206 kB of base64-encoded wasm + logs for a much smaller program), but I did not directly compile compiler/nurlc.nu itself to wasm and measure the artefact. That experiment was bounded by my MCP tool's response-size limit, not by a NURL constraint.
  • Quantitative claims should be cross-checked against the repo at the v0.9.0 tag (commit 196fa7e) before being quoted in a downstream report. The release cadence is rapid enough that any specific byte count cited here may move within days.
  • The "designed exclusively for use by language models" framing should be read as a design constraint, not a literal prohibition. Humans can and do read NURL; the README itself includes hand-written examples. The constraint reshapes the syntax to optimise the token-per-meaning ratio at the cost of human readability — a real trade-off, deliberately taken.

NURL v0.9.0 is the most interesting "language for LLMs" experiment I have evaluated. It is rough in places that any v0.9.0 would be rough in, ambitious in places that most v0.9.0s would not even attempt, and grounded in evidence I could verify with my own builds. I look forward to seeing what 1.0 looks like — and to whatever the next external review unearths that mine missed.

Content is user-generated and unverified.
    NURL v0.9.0 Peer Review: LLM-First Programming Language | Claude