Luce Base
The language design
Status: design draft, revision 3, 2026-09-02. Not yet implemented. This document is complete on its own: it states every rule of the language and the reason for it, and it does not require reading the Luce 1.0 specification. Where a rule is shared with full Luce, this document says so once and states the rule anyway.
Luce Base is C, reorganised. It keeps what makes C the language that everything else is written in: values with predictable layout, pointers, manual memory, a plain calling convention, and no runtime to speak of. It replaces the parts of C that exist only because C is fifty years old: header files, the preprocessor, null, integer promotion, switch fallthrough, return-code error handling, and void* generics.
A Base program is made of structs with functions and initialisers inside them, modules instead of headers, generics instead of macros, tagged unions with exhaustive matching, interfaces, defer, optionals instead of null, and a fallible result type instead of return codes. Memory is managed by hand through explicit allocators. There is no reference counting, no garbage collector, no hidden allocation, and no runtime beyond a trap reporter and program startup.
Base is a profile of the Luce language, compiled by the same compiler as full Luce. A Base module is a file ending in .lucb. Full Luce, with its reference-counted classes and collections, can import a Base module as an ordinary module; Chapter 18 states that contract. A Base program that never touches full Luce needs nothing from it.
The three sentences that summarise the design:
Base gives up nothing that portable C can do. Where a C capability is unsafe, Base admits it with a restriction that makes it checkable; it never omits it.
Values copy. Pointers point and are never null unless they say so. Spans carry their length. Allocators are explicit. Arithmetic, bounds, and shifts are checked. Failure is visible. Nothing runs that you did not write.
A better C that reads like Python, calls C in both directions with no glue, and is the language Luce's own runtime is written in.
1.1 How to read this document#
Each chapter states its rules first, then the reasons under the heading Why. The rules are normative. The reasons are there so that the next person to change a rule knows what it was protecting. Chapter 21 is the grammar; Chapter 22 is a one-page translation table from C; Chapter 23 lists the handful of places where Base and full Luce differ and why.
Code in this document is Base source unless marked otherwise. A fenced block marked c is C, shown for comparison.
1.2 What it looks like#
struct Cursor:
var data: const u8[]
var offset: usize
mutating func advance(self, count: usize) -> unit!:
if self.offset + count > self.data.length:
error(cursor.past_end, "advance past the end of the input")
self.offset += count
func remaining(self) -> const u8[]:
return self.data[self.offset..]
func first_line(text: str) -> str:
var cursor = Cursor(data = text.bytes(), offset = 0)
while let byte = cursor.remaining().first():
if byte == u8('\n'): break
try cursor.advance(1) catch failure:
recover ()
return str.from_bytes_unchecked(text.bytes()[0..<cursor.offset])
Everything in that example has a C counterpart and costs what the C would cost. The span const u8[] is a pointer and a length. offset + count traps on overflow instead of wrapping. error(...) returns a two-word error value; try checks it. Nothing allocates.