Weekly Peer-Review · 24 May 2026
^ 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.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.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:
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.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.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.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.^/^^ Surprise Is RealNURL'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.
NURL's memory model is the most pragmatic part of the design. It is not trying to be Rust:
: i x 10 is immutable; : ~ i x 10 is mutable. Assignment to an immutable binding is a compile error.: | Name { Variant payload... }, product types via : Name { field... }. Pattern match (??) is exhaustivity-checked.Vec[A], HashMap, Channel[A], Slice[A], Pair[A B]. Monomorphised at compile time (mangled named LLVM types like %Vec__i64, %Vec__str).--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.
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.
nurlc.nu (the compiler) is written in NURL. The bootstrap chain is:
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.nurlc_lastgood.bin compiles compiler/nurlc.nu → build/nurlc_self.ll → build/nurlc_self.nurlc_self compiles compiler/nurlc.nu again → build/nurlc_self2.ll → build/nurlc_self2.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:
vec_new[i] / vec_push[i] / vec_len[i] / vec_free[i] round-trip: 14,442 bytes IR → 69,184-byte ELF. No diagnostics.linux-arm64-musl. Same source IR, 1,400,728-byte fully-static aarch64 ELF, first try.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).
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.
^ 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._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.<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.
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.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.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.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.
A short, evidence-based positioning:
| Aspect | NURL v0.9.0 | Rust | Zig | Nim 2.x |
|---|---|---|---|---|
| Memory model | Single-owner + auto-drop; static borrow checker on by default; closures use RC for captured env | Affine ownership + borrow checker; no GC; lifetime-parametric | Manual + allocator-passing; no borrow checker; no GC | ARC/ORC reference counting with cycle collector; deterministic destructors |
| Self-host bootstrap | Stage 0 = clang-linked committed .ll; stage1/2 fixed point byte-identical | rustc 1.x.0 built by rustc 1.(x-1).0; six-week beta chain per endoflife.date/rust | Stage 0 = zig1.wasm → wasm2c → C → stage1 → stage2/3 fixed point | Self-hosting since 2008 (per Wikipedia); refc/ARC/ORC chosen at compile time |
| Cross-compile | zig cc driven for non-x64 Linux + macOS; clang-direct for x64 Linux/Windows; wasm32-wasi via WASI SDK | rustup target add + bring own linker; cross via cross or zig | First-class: zig build-exe -target … with bundled libcs | Compiles to C/C++/JS; cross is reliable but more manual |
| Compile-time computation | None (deliberate v1 scope) | const fn, macros, GATs | comptime (pervasive) | Macros + static: blocks; meta-programming heavyweight |
| Stdlib HTTP/2 + MCP | Yes, in-tree | No (hyper, async-mcp) | No | No |
| Syntax style | Polish prefix, parens only for calls and groupings | C-family infix with traits | C-family infix with comptime sigils | Python-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.
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:
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).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.What is impressive:
MIT OR Apache-2.0." This matches the Rust/Zig precedent and is the right choice for both adoption and patent-grant safety.For NURL maintainers (constructive, prioritised):
^ 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.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.<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.nurlc.wasm against a known build.--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.~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.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:
spec.md, a stronger borrow checker, and a regular external-audit cadence.examples/static_server.nu template extended with a few business endpoints, or an MCP-bridge sidecar) before betting a production codepath on it.docker build -f api/Dockerfile -t nurl-api:dev .); use it for anything sensitive.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.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.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.