Skip to content

Status and roadmap

This page summarizes the release history and the future work that is actually stated in the repository. The release by release detail lives in the changelog on GitHub.

The current release is 1.11.0, and Dusk is self-hosting. The compiler runs the whole pipeline: it lexes, parses, resolves names, type checks, monomorphizes, and emits code, backed by a golden and unit test suite that stands at 907 records passing under testrun tests/goldens.manifest. The standard library and the multi module sample both build and run, and Dawn stays byte compatible with 10 of 10 offline checks green. Beyond the compiler, the official language server, dusk-lsp, is written in Dusk and drives the same compiler for its answers, so an editor’s diagnostics are the compiler’s own.

1.0.0 declared the bootstrap done: the compiler written in Dusk became canonical, having built itself to a byte identical result three stages deep at 0.9.4. The language surface froze at 0.5.4 for that rewrite and stayed still through 1.0.0, then reopened and grew across the 1.1 through 1.8 releases. At 1.3.1, “the retirement”, the original Rust compiler was deleted from the repository, so the repo is now pure Dusk: the compiler (root file compiler/dusk.dusk), the Dawn package tool, and the native test runner are all written in Dusk and build with the dusk compiler itself. The Rust implementation is archived at github.com/choice404/dusk-rust, a frozen full-history archive at tag v1.3.0 and the reference for the surface through 1.2.0. See the Unicode guide and the collector reference for parts of that surface, and Concurrency for the substrate the async line rides on.

Development has proceeded line by line, each a minor version series with one theme.

0.1.0 delivered the core language through the whole pipeline: paradigm directives gating procedural, functional, and OOP features per file, structs, methods, enums with exhaustive match, interfaces with vtables, closures, monomorphized generics, functional builtins, do notation, errors as values under the must-handle rule, explicit memory with alloc, free, and defer, a module system with a stdlib seed, and a golden test suite compiling and running every example.

The point releases filled in the planned core:

  • 0.1.1: correctness and diagnostics, char semantics, errors as values lowered end to end, per file source tracking in diagnostics.
  • 0.1.2: pointer receivers for methods and the using Allocator interface working end to end, with Heap, FixedBuffer, Arena, and Debug in the stdlib.
  • 0.1.3: qualified call syntax, std.map (then a string keyed Map<V> written in Dusk, generic over its key as Map<K, V> since 1.5.2), and file I/O with read_file and write_file.
  • 0.1.4: console input and parsing, read_line, read_all, parse_int, parse_int_radix, parse_float, and the std.io compositions.
  • 0.1.5: formatted printing, print and println take a format string whose {} holes expand at compile time into typed prints.

Releases 0.2.0 through 0.2.6 built the memory safety story described in the memory guide and the memory reference:

  • 0.2.0: mutable strings, StringBuilder, concat, and the cstr builtin.
  • 0.2.1: generational references, the runtime foundation. A managed *T is a fat pointer carrying a remembered generation checked at every dereference, so a use after free, a double free, or a stale pointer to a reused block faults instead of corrupting memory. The thin layer, *raw T and *void, landed alongside.
  • 0.2.2: single owner pointers, the static half. The checker tracks owners and borrows, move transfers ownership and invalidates the source, and ref makes a non owning alias.
  • 0.2.3: escaping value lifetimes. Returning a slice viewing a frame local array or a closure capturing a frame local is a compile error.
  • 0.2.4: the minimal foreign function interface, foreign "C" blocks calling libc across the raw pointer boundary.
  • 0.2.5: closed the gaps a specification review found, generation checked free, bounds checked indexing, the first enforcement of the must-handle rule, interface conformance at call sites, and more.
  • 0.2.6: hardened the whole line one level deeper, type sized alloc(), integer and float widths tracked so int32 + int64 is a compile error, immutability covering element and field stores, the binding level must-handle rule, module private name isolation, and Display gated printing.

Releases 0.3.0 through 0.3.3 built concurrency in four phases, each covered in the concurrency guide:

  • 0.3.0: threads. spawn starts an OS thread running a lambda whose captures copy into a private heap environment, join waits and retires the handle, the generational heap is thread safe, and std.concurrent.atomic carries the sequentially consistent counter.
  • 0.3.1: channels. std.concurrent.channel carries a bounded, thread safe queue, with ownership moved across threads through chan_send(c, move(p)) and the sender’s name dead at compile time.
  • 0.3.2: mutexes and condition variables. std.concurrent.sync ships Mutex and Condvar, with every classic pthread misuse turned into a named fault.
  • 0.3.3: the thread pool and the async substrate, the non blocking and timed channel operations, the submit builtin over a global worker pool, and pool_start, pool_shutdown, and ncpu in std.concurrent.pool.

Releases 0.4.0 through 0.4.4 built the async line on top of the 0.3.x substrate, covered in the async guide:

  • 0.4.0: futures and the event loop, the first phase. std.async.future ships a one shot Future<T> completed from any thread, std.async.loop runs the single threaded event loop, and std.async.time adds sleep_async. An await that provably cannot finish aborts by name instead of hanging.
  • 0.4.1: the epoll reactor, the second phase. std.async.io turns file descriptor readiness into a one shot future through one epoll thread, with readable, writable, a non blocking pipe and byte surface, and the reactor lifecycle wired into the deadlock gate.
  • 0.4.2: the async func, await, and async_run keywords, which compile async code to a state machine over a heap frame. The same release lands the complete operator set, the bitwise family, compound assignment, ++ and --, **, |>, ..=, and a thirteen level precedence ladder (see the operator reference), and hardens the escape check and interface boxing.
  • 0.4.3: networking and async sugar. std.async.net puts TCP on the reactor’s readiness futures with IPv4 dotted quads and no name resolution, chan_recv_async turns a channel receive into an awaitable, and generic do composes over any monad instead of only fully ground binds.
  • 0.4.4: a second reactor platform and hardening. The reactor moves behind a six function poller seam with a kqueue backend beside the epoll one, written but unverified until a BSD or macOS runner exercises it, SIGPIPE is ignored process wide, every blocking syscall retries on EINTR, and file descriptor exhaustion surfaces as a named error instead of a leak.

Releases 0.5.0 through 0.5.4 hardened the language and grew the standard library ahead of the bootstrap:

  • 0.5.0: interprocedural escape analysis, the ledger. Escape checking became summary based across calls, with every function carrying a summary over what it returns, reads through, flows into, and sinks, so a frame view laundered out through a call, a store, a channel send, a closure, or a pointer alias is caught instead of slipping past.
  • 0.5.1: the collector. collector<T> opts a value into a second managed heap, a conservative mark and sweep collector beside the generational one, sharing the same block header and dereference check. Nothing is collected unless you mint it, and the collector is confined to the main thread. See the collector reference.
  • 0.5.2: Unicode strings. The rune primitive carries one Unicode scalar value, r'...' and \u{...} literals spell codepoints, a string literal must be valid UTF-8, and std.unicode decodes and encodes UTF-8 in pure Dusk. See the Unicode guide.
  • 0.5.3: the standard library. IO<T> became a true lazy monad over a collected thunk, std.functional.result added Result<T, E> with a monad block, and std.logging added leveled logging to stderr.
  • 0.5.4: the audit and the freeze. A hardening pass reserved the unsigned integer widths, gated impl behind @paradigm oop, extended the must-handle rule to error parameters, added caret diagnostics, and froze the language surface for the bootstrap.

Releases 0.6.0 through 0.9.4 rewrote the compiler in Dusk itself, one pipeline stage at a time, with the language surface held still under the freeze. A parity gate, tools/differential.sh, held each stage to matching the Rust compiler byte for byte, and tools/pyramid.sh climbed the stage ladder toward a fixpoint:

  • 0.6.0 opens the line with the front end scaffold, the lexer and the diagnostic renderer in procedural Dusk, with dusk1’s lex and scan dumps matching the seed’s across every file in examples/ and lib/std. 0.6.1 records the else if chain in the spec, a shape the parser always accepted.
  • 0.7.0 gives dusk1 the parser, so it builds the same AST the seed does.
  • 0.8.0 through 0.8.3 port the judgment: name resolution and type checking, then the interprocedural escape summary, then monomorphization and the ground type pass, until dusk1’s verdict agrees with the seed’s across the whole sema corpus with no exclusion left.
  • 0.9.0 through 0.9.4 port code generation: the scalar spine first, then aggregates, then closures and the collector, then the async state machine, until every construct the surface carries lowers under dusk1. 0.9.4 climbs the last rungs of the ladder to the fixpoint, stage1, stage2, and stage3 landing on the identical binary and the identical compiler IR.

1.0.0 declares the bootstrap done. No language surface change and no compiler behavior change: the compiler written in Dusk becomes the canonical dusk compiler. The Rust compiler stays on for now as the seed whose one remaining job is rebuilding the first stage from dusk source, a job it keeps until the 1.3.1 retirement. The fixpoint reproduces at the release tag, with the golden suite passing in full against both the first and second self-built stages, and the 0.5.4 surface is the 1.0.0 surface, unchanged start to finish.

With the compiler self-hosting, the frozen surface reopened and grew, and the Rust seed was retired. Each release carries one theme:

  • 1.0.1: the installed compiler. The canonical compiler gains the same asset search the seed had, a five step probe for lib/ and runtime/: DUSK_HOME checked against the specific asset, the directory the running executable sits in, a share/dusk-lang directory one level above that, the directory argv[0] names, and the working directory as a source checkout fallback. A compiler installed at prefix/bin beside prefix/share/dusk-lang finds its assets with no DUSK_HOME set.
  • 1.1.0: the byte behind the glyph. print, println, and printerr write a char, a char[N], and a char[] as their text bytes rather than as numbers, so println(s[0]) on "hi" prints h. A rune still prints its codepoint number, by design. for c in s iterates a string’s bytes front to back, a string range slice s[lo..hi] is bounds checked against the scanned length, and std.string gains str_from_chars to copy a char slice into a fresh heap string.
  • 1.2.0: the daily driver. && and || short-circuit, string == and != compare content and + concatenates, the width casts int8 through int64 and char convert explicitly, break and continue become statement keywords gated to @paradigm procedural, comparison closes to the scalars and string with named rejections for the rest, and the common runtime faults name the file and line that raised them.
  • 1.3.0: the native harness. The golden test runner (testrun) and Dawn (compiler/dawn.dusk) are ported to Dusk, so the whole toolchain is now written in Dusk. std.string gains str_find and str_contains. No language surface change.
  • 1.3.1: the retirement. The Rust implementation is deleted from the repository, which is now pure Dusk. The old compiler is archived at github.com/choice404/dusk-rust, a frozen full-history archive at tag v1.3.0 and the reference for the surface through 1.2.0. The canonical compiler root file is compiler/dusk.dusk.
  • 1.4.0: the open boundary. A foreign block may end its parameter list in ... to bind a variadic C function such as printf, the @link and @csource directives pull libraries and C files into the link line, std.math binds 22 of libm’s float64 functions with pure Dusk pi, e, is_nan, and is_inf, std.rand adds an xoshiro256** generator over a heap Rng, and float != is corrected to IEEE 754’s unordered comparison so NaN != x answers true.
  • 1.4.1: structs across. A C plain struct crosses a foreign boundary by value, classified and coerced the way clang’s own System V x86_64 ABI places it, eightbyte by eightbyte, register or memory, and checked byte for byte against a clang compiled object on either side of the call. A field the boundary cannot carry is rejected by name rather than quietly mislaid. Two modules land on the reopened boundary: std.fs, files and directories with pure Dusk path arithmetic, and std.time, UTC clock reads paired with a pure Dusk proleptic Gregorian calendar.
  • 1.4.2: the callback. A foreign parameter may be declared with a function type, so a capture free lambda or the name of a top level function crosses as a bare C function pointer, one word with no environment and no trampoline in between, which is the shape qsort and most C registration APIs actually ask for. A callback that captures a local is refused, since C has no environment to put it in. std.process runs a command and reads its output back, and std.vector gains vec_sort, a stable and deterministic merge sort behind a comparator, alongside vec_contains and vec_index_of.
  • 1.4.3: Dusk as a library. The three releases before it carried C into Dusk; this one carries Dusk out. An export "C" func is a function a C caller reaches by its own bare symbol, and dusk build --lib compiles a module into a static archive and a generated C header that any C ABI language links against, the module free to omit main entirely. std.json lands on top, a parser and emitter over a recursive enum. See the CLI page for the flag and C libraries for the boundary.
  • 1.4.4: the boundary hardened. No new surface. The line closes by testing the whole boundary against adversarial input and fixing what that surfaced: fault goldens across every boundary feature, proving a Dusk fault crosses an export as a clean abort rather than corruption; library packaging fixes, so a private helper named for a libc entry can no longer interpose the host’s own call at the static link; and std.json hardening, a nesting depth bound and a number range check turning a pathological document into a named error instead of a crash. Two limitations are recorded honestly rather than papered over: a parsed Json tree has no json_free and is reclaimed at process exit, which 1.8.1 later closes, and the archive is static only, not position independent, so a dlopen based FFI cannot load it.

1.5.x: casts, hashing, the generic map, and the string toolkit

Section titled “1.5.x: casts, hashing, the generic map, and the string toolkit”

The 1.5 line opens on the scalar surface and continues into a standard library overhaul:

  • 1.5.0: the numeric cast. 1.2.0 added an integer width cast and stopped at the integer family. This release opens the whole scalar set: rune, float32, and float64 join the cast builtins, and a cast now crosses the integer and float boundary in both directions. A float to an integer saturates, clamping a magnitude beyond the target’s range to its nearest bound and casting a NaN to zero, rather than leaving an out of range input undefined the way C does. A misused cast is therefore deterministic, which is the safety posture the language takes everywhere else.
  • 1.5.1: the hash builtin. hash(v) returns a deterministic 64-bit hash over a hashable value, an integer of any width, a char, a rune, or a string. A float is refused, since a NaN breaks the coherence between a hash and equality, and a struct or pointer is refused too. It shipped one release ahead of the map that needs it, on purpose, so the previous release’s compiler could still build the standard library that would use it.
  • 1.5.2: the generic map. std.map had been keyed by strings alone since it first shipped; it is now Map<K, V>, generic over its key as well as its value, with K drawn from the hashable set hash defined a release earlier. The open addressing, the linear probe, the half full grow, and the insertion order iteration are all unchanged, and a string key hashes and compares by content exactly as before. The compiler itself, the heaviest map user in the tree at more than seven hundred annotation sites, migrated with it and emits byte identical IR afterward, so the map going generic changed nothing the compiler produces.
  • 1.5.3: the string toolkit. std.string grew up serving the compiler, so it had the parsers, the builder, and the foreign bridge, but not the everyday manipulation set a program reaches for first. This release adds it, twelve functions in pure Dusk over the existing builder with no compiler change and no runtime change: ends_with and str_rfind search from the tail the way starts_with and str_find search from the front, str_cmp orders by unsigned byte value and returns the shape vec_sort’s comparator already takes, so a vector of strings sorts with no glue, trim_start, trim_end, and trim strip ASCII whitespace, repeat and replace_all build a fresh string, str_split and str_join cross between a string and a *Vector<string>, and to_upper and to_lower fold ASCII letters alone, leaving a byte at 128 or above untouched so a multibyte scalar survives intact. The split and join pair is why std.string now imports std.vector, which is the one way this release breaks code that compiled before: nearly every module imports std.string, so a program that defined its own vec_len or its own Vector now sees the standard library’s too and fails loudly on the duplicate definition. Rename the private copy.

Two spellings moved in this line: every existing map is Map<string, V> now, and a program carrying its own vec_* names renames them. See the standard library overview for the surface as it stands.

1.6.x: comments, the second target, and doc comments

Section titled “1.6.x: comments, the second target, and doc comments”

The 1.6 line writes down two things the language never had and opens a path to the browser:

  • 1.6.0: the block comment and the second target. Dusk carried only the // line comment since 0.1.0, and the spec never wrote even that down. This release adds /* */, nesting the way commented out code needs, and gives the spec a Comments section that records both forms. A tools/comment-differential.sh proves the feature invisible, rewriting every line comment in the example corpus into block form and asserting byte identical IR. Alongside it, dusk ir --target=wasm32 cross-emits the module for wasm32-unknown-wasip1, the shape a wasi toolchain links and the form the browser playground is built from, while every other command keeps the native triple. A runtime/wasm_shim.c carries the wasm side of the runtime, and std.os’s errno read is renamed os_errno so its old bare name stops colliding with the C symbol errno on a target whose libc owns it.
  • 1.6.1: the doc comment. A block comment that opens with /** binds to the declaration it precedes, and the new dusk doc command renders a module’s documentation as markdown or, with --json, as a stable JSON model for tooling. Every fact comes from the declaration itself, so the documentation cannot drift from the code, and where the prose contradicts the signature the command refuses to emit and says why. See doc comments and the CLI.

1.7.x: the debt release and the machine face

Section titled “1.7.x: the debt release and the machine face”
  • 1.7.0: the debt release. Every entry in the known defect ledger was re-verified against the current compiler rather than trusted from memory, and the verdicts drove the release. The one live soundness hole is closed: a function that wrapped a pointer argument into a returned struct could let a frame view egress unseen, and the built program read a dead frame; the fix raises the escape summary and the alias linker so the escape is caught at the return. The rest of the ledger is pinned by goldens or documented honestly, including the boundary that makes a hand rolled deep free of a node tree inexpressible until an owning take exists.
  • 1.7.1: the machine face. The compiler’s diagnostics and its doc model become data a tool consumes rather than text a person scrapes. dusk check --json emits one deterministic JSON document with every diagnostic’s message and precise source span, and the doc model gains a span on every item. These are the two contracts the language server builds on. No language surface changed. See check —json.

1.8.x: the owning take and the stdlib five

Section titled “1.8.x: the owning take and the stdlib five”
  • 1.8.0: the owning take. vec_take and map_take remove an element from a container and hand the caller the owner, and the checker’s borrow net around containers settles into one model across every spelling, so freeing a container read directly is rejected and the take is the sanctioned path. A program can now hand write the deep free of a heap tree, with examples/jsonfree.dusk the acceptance. No parser, codegen, or runtime change: the removal is two standard library generics plus a checker that blesses their results as owners. See owning removal from a container.
  • 1.8.1: the stdlib five. Five standard library items and no new surface: std.flags for command line parsing, std.set over the generic map, vec_map and vec_filter beside the vector sort, std.time’s weekday and a strict parse_iso8601, and std.json’s json_free, the first in-tree caller of the owning take 1.8.0 introduced. Every item is plain Dusk in lib/std/ with its examples and goldens; no token, no AST node, no checker rule, no lowering, and no runtime change.

1.9.x through 1.11.0: the marker, the frame, and the container loop

Section titled “1.9.x through 1.11.0: the marker, the frame, and the container loop”
  • 1.9.0: the owning func and the debt sweep. owning func is the declared generalization of the take blessing: a head marked owning declares its call result the caller’s own value, so the checker blesses it exactly as it blessed vec_take by name. The same release finishes the match expression, typing its result and judging its arms on type and ownership with one voice, and closes a batch of checker gaps that reached clang or the generation backstop. dusk doc also learns to attach a doc comment to a foreign function head. See owning functions.
  • 1.9.1: the adoption. vec_take and map_take now carry the owning marker themselves, and the compiler’s by-name recognition of them is deleted, so the blessing rides one declared mechanism. std.vector gains vec_fold, the reduce that completes map, filter, and fold over a vector. The one behavioral flip is raise-only: a user’s own unmarked function named vec_take or map_take is no longer blessed by its name and marks itself owning func to restore it.
  • 1.10.0: the scoped frame. Block scoping and shadowing become sound the whole way down to code generation, which gains a per-block scope chain in place of its flat per-function table, so a binding that shadows an outer name in a nested block stops clobbering the outer name’s resolution; the defer replay and lambda capture that resolved through the same table are repaired with it. The type checker already resolved these correctly, so no check verdict moves; every repair is a program that checked clean and then printed a wrong value or died in clang. A binding that would shadow a using allocator’s name is now rejected.
  • 1.10.1: the match tail. An arm body that ends in a bare match value-izes into the enclosing match, so a nested match is the arm’s value with no name to bind it, and three adjacent judgment holes the feature exposed close with it. A form the checker rejected before now lowers.
  • 1.11.0: the container loop. for iterates the two standard containers: for x in v over a *Vector<T>, and for k in m and for k, v in m over a *Map<K, V>. A pass between the surface type pass and monomorphization lowers each loop into the exact index loop a program would write by hand against vec_len and vec_get, or map_len and the two new positional accessors map_key_at and map_val_at, so container layout stays in the standard library and code generation never learns it. The length re-reads every iteration, so a container freed inside the body faults at the next iteration’s generation check rather than reading a stale pointer, and the for binder is now typed, catching a narrowing bind that would have truncated silently. See iterating with for.

The split between 1.5.1 and 1.5.2 was deliberate sequencing rather than tidiness. The map uses hash, so hash had to be one release old before the map could reach for it, which keeps the previous release’s binary able to build the current source. That property is the ratchet, and every release in these lines holds it: the v1.5.1 release binary builds the 1.5.2 source, and the compiler it produces passes the full suite.

The stage ladder re-fixes at every release beside it. Seeded with the previous release’s binary, stage1, stage2, and stage3 land on one shared binary hash and one shared compiler IR hash, with the collapse, fixpoint, and determinism checks green and the golden suite passing under stage1 and stage2 alike. A self-hosting compiler has no outside authority to check it against, so these two habits, the ratchet and the ladder, are how it stays honest about itself.

Only the work the sources state is listed here.

The bootstrap is done and the surface, reopened after 1.0.0, has been actively growing. 1.1.0 through 1.11.0 each shipped real language and library surface, from char as text and the daily driver operators, through the foreign boundary in both directions, to the numeric cast, the hash builtin, the generic map, and the string toolkit, then on to block and doc comments, the wasm cross-emit target, the machine readable diagnostics and doc model that editor tooling reads, the owning take that lets a program reclaim a heap tree, and the standard library growth that spent it, and most recently the owning func marker, a sound scoped frame in code generation, the finished match expression, and the for loop over the two standard containers. The recent lines lean toward the library and the tooling around it, which is where the sources point next. Growth continues by proposal rather than on a fixed schedule, alongside the three constants that never paused, sharper diagnostics, standard library growth, and soundness fixes. The concrete work the sources still name is the package tool and the standard library below.

The dawn package tool is a minimal working seed today: an import resolves against the latest clone in the cache, and there is no version selection, no lock file, no fetch past the root file’s direct imports, and no integrity check. Its stated roadmap, in order:

  1. Version selection: pin a git tag or commit per package, chosen by a minimal version selection rule like Go’s and recorded so builds repeat.
  2. A lock file: a checked in manifest at the project root listing every package and its resolved version, so a fresh machine builds the same bytes.
  3. Graph fetch: walk the imports of fetched packages, not the root file alone, and resolve the whole graph before a build.
  4. Integrity: a hash per fetched module, verified on use, to catch a moved or rewritten tag.
  5. Quality of life: a vendor mode that copies dependencies into the tree, offline builds from the cache, and private repositories with authentication.

The repository sketches the shape the standard library grows into. This is a direction, not a schedule. Much of the original sketch has since landed: a string keyed std.map in 0.1.3, made generic over its key as Map<K, V> in 1.5.2, mutable strings in 0.2.0, a conservative collector<T> heap in 0.5.1, Unicode aware string operations in 0.5.2, and Result<T, E>, a lazy IO<T>, and leveled std.logging in 0.5.3. The 1.4 line added std.fs, std.time, std.process, and std.json on top of the foreign boundary, and the 1.8 line added std.flags and std.set beside the map. What the sources still leave open:

  • A precise collector to replace the conservative one, which the spec names as much later work.
  • A List<T> monad; the 0.5.3 helper additions across Maybe, Either, and Result filled in most of the rest.

The full history, newest first, is in the changelog, and the source lives at github.com/choice404/dusk.