LLuce

Where Luce stands

Luce is a real language with a real compiler, a real runtime, a real terminal and a real editor written in itself. It is also early, and this page is what that means, written from the repository's own inventory rather than from hope.

Nothing here is rounded up. Where the repository's own notes say a prediction was too optimistic, this page says so too.

The short version#

The language surface is done as designed. Ten conceptual pipeline stages, four executable specifications, and a front end whose diagnostics name the fix rather than the parser's predicament. Optionals closed the absence half of the last semantic hole and errors closed the failure half; nothing that was designed is now unbuilt.

The runtime is not done, but the wall is down. The two items that blocked real programs are both closed: the C-parity backend is reachable from loom run and from luce build --emit=exe, and memory is genuinely given back — object identity first, then String bytes and struct field runs. A Luce program can run all day.

What is left is a short list of library and host builtins, one open language question, and one benchmark row.

What works#

Static typing with inference, no implicit conversionsshipped
Checked arithmetic, bounds checks, UTF-8 boundary checks, in every modeshipped
Scope ownership: give, copy, free, 43 ratified situationsshipped
T?, none, narrowing, elseshipped, both engines
T!, try, catch, errorshipped, both engines
f-strings, compound assignment, nested place assignmentshipped
File-scope constants, folded and inlinedshipped
Modules, and a reserved std. namespaceshipped
Three standard modules: math, strings, filesshipped
LLVM backend: .lcn artifacts, --emit=exe standalone binariesshipped
Trap locations and call traces, identical on both enginesshipped
Two build modes that differ only in what a trap can sayshipped
Map lookups O(1); sort O(n log n) and stable by guaranteeshipped

What is measured#

Against C twins at -O3 -march=native, on one host: 0.78× to 1.09× on loops, math, arrays, matrix multiply and statistics, and 2.31× on strings. That last row is the one genuinely behind, and it is allocation-bound rather than code-generation-bound. The table and its caveats.

Memory, on a churn loop that retains nothing: flat at 1.8 MB on the interpreter where it used to grow linearly to 121 MB. The editor simulation: 1204 MB to 3.3 MB peak. How, and what it cost.

Small-string optimisation was predicted to remove "essentially all" of the cost of giving String bytes an owner. It removed roughly three quarters. The repository records that the prediction was too strong.

Deliberately absent, permanently#

These are decisions with reasons written down, not gaps.

  • Garbage collection and reference counting, at every layer, in the language and in the runtime alike. Also copy-on-write and automatic reference counting. Scope ownership is the model.
  • Shared ownership (share) and weak references. A program that needs genuinely shared ownership restructures, or uses indices into a container it owns.
  • Static borrow checking. Lifetimes and aliasing rules are the opposite of what Luce is aiming at; two dynamic checks cover what the static rules cannot see.
  • errdefer and error return traces. Both refused, with reasons — the one bit errdefer encodes is already a parameter of the unwinder, and a trace would charge the success path.
  • Exceptions. Traps are final.
  • Implicit conversions, shadowing, truthiness, a ternary operator.
  • Interfaces, inheritance, operator overloading, async, reflection.
  • defer for memory. It may return for host cleanup, as a separate decision.
  • Generics for user code. The type union is closed with twenty exhaustive switches over it, and List(T) is a monomorphic heap object rather than a generic. T? became a variant and opened no door at all: nothing about it generalises. What would pay is monomorphised generic functions, and that needs first-class functions first.
  • Closures. The one place their absence draws blood is comparators, and the cheap answer is a sort_by taking a top-level function name — no capture, no lifetime story, no interaction with ownership. That, not closures.

Absent and not decided#

Sum types — no enums, no tagged unions, no match. This is the open language question, and it is a second-order blocker: a Result-style error type was refused because there are no tagged unions, which is what forced T! to be a function attribute instead. That answer turned out better than "probably right", but it is still the third design bent around the same hole.

The corpus pays for it constantly, and the counts are real:

  • editor.luc handles keys with 17 string comparisons and no else. A misspelled "page_dwon" compiles and silently does nothing.
  • editor.luc writes # 1 keyword, 2 type name, 3 builtin, 0 plain — an enum written as an Int with a comment.
  • editor.luc implements is_keyword and is_builtin as 46 word == "…" comparisons: a hash set written as a truth table.

Now that optionals have shipped, this is decidable on evidence rather than on argument. What the corpus does with T? from here is what should settle it.

The short list of what a real program hits#

Read out of programs/ for awkwardness rather than for features. editor.luc is the oldest file in the corpus — it predates the standard library, f-strings and constants — so it is both the most workaround-dense and the proof that the language moved.

  1. No sets and no constant containers. Drives the 46-comparison truth tables.
  2. No character classes in the library. is_digit/is_alpha re-derived by hand three times. Five functions would fix it.
  3. No receivers on user structs. 87 Struct.func(state, …) calls; the receiver is the first parameter of ten of ten functions in one struct. Nine of twelve structs in programs/ have no fields at all — namespaces impersonating types.
  4. No multiple returns. One program declares a struct solely to return two Ints, constructed at 8 sites and destructured at 15.
  5. No sort with a comparator. wordcount.luc produces a top-five listing by destroying the map. The one place the absence of first-class functions draws blood.
  6. Host surface gaps: clock, sleep, exit, environment access, stderr, directory listing, delete/rename, append mode, path manipulation. Each is one builtin plus one wrapper.
  7. No default or named arguments. term_style(fg, bg, bold) is called 16 times and 13 of them end in the same noise word false.
  8. Bytes is unconstructible. var b: Bytes compiles; nothing produces one and nothing consumes one. It is the only thing keeping the backend from lowering everything a script can say. Cut it or grow it.
  9. No integer-division spelling. A decrement is written (tape[pointer] + 255) % 256; neither // nor rem_euclid exists.
  10. No visibility. The standard library leaks two internal helpers. Cheap, and it matters before userland libraries exist.
  11. No bitwise operators, no hex literals, no digit separators. Refused by name rather than misread, which is right — but it caps what userland can reach.
  12. No codepoint iteration. for c in "abc" is refused; every UTF-8 walk is hand-written, and the same function is copied across two namespaces in one file.
  13. m.get(k) -> V? does not exist, so has then index is three hash lookups on the hit path.
  14. strings.find returns -1 because Int? did not exist when it was written. It does now, on both engines, so the sentinel is a wart with nothing holding it up — and it also returns -1 for an argument error, which is not the same fact as "absent".

Tooling#

There is no luce fmt, no luce test, no language server and no debugger. A luce test that discovered func test_*(): would be cheap and very Zig; a formatter and a language server both want a faithful syntax tree that is not written yet.

There is a VS Code syntax definition in the repository, and it is stale — it still lists builtins from a removed era.

The order the work goes in#

  1. The cheap slice: character classes, a frozen container or a Set, a clock, sleep, exit, environment access, stderr, directory listing.
  2. Cut Bytes.
  3. m.get(k) -> V?, and a corpus sweep.
  4. Decide receivers, multiple returns, and integer-division spelling — one memo each.
  5. Sum types, if the experience with T? says the hole is still there.
  6. The faithful syntax tree, which a formatter and a language server both need.

The honest summary#

The language is complete as designed. The front end is in genuinely good shape, and the remaining language work is one open question and a short list of library and host builtins. The runtime's outstanding item is not correctness but speed, in one benchmark row.

If you are looking for a language to build production systems on today, this is not that. If you are interested in a small, fast, statically typed language that reclaims memory without a collector and without reference counting, and that says out loud what it cannot yet do — it is right here, and every claim on this site was checked against the compiler when the page was built.