Skip to content

Instantly share code, notes, and snippets.

@planetis-m
Last active October 28, 2025 19:12
Show Gist options
  • Select an option

  • Save planetis-m/7a8e4dc26dca95ec9309515c7a0e0786 to your computer and use it in GitHub Desktop.

Select an option

Save planetis-m/7a8e4dc26dca95ec9309515c7a0e0786 to your computer and use it in GitHub Desktop.
Nimony Release notes v0.3

Nimony progress release (October 2025)

What it is Nimony is a new compiler for a Nim variant aimed at becoming Nim 3.0 over time. The current focus is the aufbruch mode: destructor-based memory management (ARC), value-centric data layouts, predictable code generation, and a plugin-based metaprogramming model. A compat mode exists to keep Nim 2 code viable, with known ABI differences.

This release is another incremental step. It adds core language checks, a handful of standard modules, early plugin support, and compiler/tooling pieces that make day-to-day work less fragile. It is not “done.”

What changed since the last release Grouped by area, these are the changes that are confirmed to work in code and examples from the linked reports and docs.

Language and type system

  • Generic code is type-checked at declaration time, not only at instantiation. Concepts are enforced, giving earlier and clearer diagnostics.
  • Flow-sensitive not-nil analysis for refs works. Code that demonstrates nil/not nil refs and control-flow-based checks now compiles and runs. The global mode switch for default ref-nilness is not implemented yet.
  • Exceptions: try/except works with ErrorCode. Routines that may raise must be annotated {.raises.}. Raising other exception types is not supported yet.
  • Bounds and overflow checks:
    • Builtin arrays perform runtime bounds checks.
    • Overflow flag support is exposed via NIFC builtins (keepOverflowFlag / overflowFlag).
  • Iterators and yield compile and run. fields and fieldPairs are available.
  • Converters work (including string → openArray[char]).
  • Operators and overloads for [], []=, ==, $ function as expected.
  • Closures compile and run in basic cases; non-escaping closures avoid heap allocation; generic inner procs are lambda-lifted.
  • Defer and using statements implemented.
  • packed and union pragmas implemented.

Memory management and data structures

  • ARC-based memory management is in place. The reports note no leaks in the exercised examples; recursive ref destructors work.
  • ref objects (including recursive types) are supported.
  • seq works (allocation, indexing, len, assignment), including synthesized hooks/destructors.
  • openArray is a library-defined view type (not compiler magic) with .requires/.ensures-style preconditions to eliminate bounds checks when proven.

Metaprogramming and plugins

  • Templates work. Template expansion can delegate to compiled plugins via {.plugin: "..."}; plugin output is sem-checked and cached.
  • Beginnings of a plugin API exist. Source code filters are supported.
  • A rewrite rule (pragma-to-section) is implemented: proc p {.t.} → t: proc p ...
  • astToStr, system.compiles, do notation are implemented.

Standard library

  • Newly working/ported modules: std/[tables, hashes], std/intsets, std/parseopt, std/dirs, std/unicode, std/encodings, std/monotimes, std/locks, std/rawthreads, std/syncio.
  • std/math is partially ported (contains many consts and procs). strutils is listed as an active target; not done.

Compiler and backend

  • Precompiled modules reimplemented.
  • Whole-program dead-code elimination; merges generic instances.
  • “Find usages” and “go to definition” (—usages/—def) work.
  • Control-flow graph construction was rewritten (“split streams” algorithm).
  • Code generation via NIFC is in place.

Concurrency

  • Raw threads are supported via std/rawthreads.
  • .passive (CPS) procs exist in basic form, but integration with try/finally/raise requires performing CPS later in the pipeline. Early releases will likely not include .passive features.

Two small examples that reflect what’s actually implemented

  • Error handling via ErrorCode:
import std/errorcodes

proc usePositive(x: int) {.raises.} =
  if x < 0:
    raise ErrorCode.RangeError
  # use x
  • openArray as a library view (converter from string):
type
  openArray*[T] {.view.} = object
    a: ptr T
    len: int

converter toOpenArray*(s: string): openArray[char] =
  openArray[char](a: rawData(s), len: s.len)

Why this matters in practice

  • Earlier, stricter generic checking reduces iteration time and improves IDE tooling (you get real errors and completions before instantiation).
  • Destructor-based ARC composes with resources. You can build containers of things that hold OS handles and have them clean up deterministically.
  • ErrorCode gives a single, allocation-free error path and aligns with OS and protocol error domains (errno, Win32 error codes, HTTP status).
  • Making openArray a plain view type, plus contract annotations, removes “special case” magic and makes it easier to write zero-overhead, safe APIs.
  • The nascent plugin system shifts metaprogramming to compiled transformations that run after type checking, which is simpler to reason about than untyped macros.
  • Whole-program DCE and generic merging keep binaries and compile times in check as libraries grow.
  • With unicode, encodings, locks, monotimes, and raw threads, small real programs (CLI tools, file walkers, table-driven code) are practical today.

Compatibility and modes

  • Two language modes:
    • aufbruch (current focus): only mm:atomicArc, not nil by default (design), small stdlib, plugin-based metaprogramming, APIs prefer ErrorCode. The runtime aims to handle OOM gracefully; some parts are design-stage or early-stage, not all are implemented.
    • compat: intended to be close to Nim 2 semantics but with limited implementation complexity. ABI differences are expected.
  • Known interoperability differences even in compat:
    • string, seq, and ref layouts are not ABI-compatible with Nim 2.
    • Due to string COW, passing s[i] as var char is disallowed.
    • seq <-> string casts are disallowed.
    • string → cstring conversions may be more expensive (no guaranteed trailing zero).

Not in this release (planned or acknowledged gaps)

  • Full exception hierarchy beyond ErrorCode.
  • The effect system (no tags, no raises list, no sideEffect tags).
  • Range subtype, user-definable pragmas, dirty templates, backtick identifier construction, overloading ().
  • Closure iterators.
  • Term-rewriting macros (superseded by plugins; module/type-attached plugins are the intended path).
  • Circular dependency solution for modules is not implemented.
  • Mode switch for default ref nilness is not implemented.
  • CPS/async integration with try/finally/raise and the unified spawn/await story (design described; implementation partial).
  • std/math and std/strutils are incomplete.

What’s next (no promises; this is the working queue)

  • Finish std/strutils and round out std/math.
  • Stabilize and document the plugin API; expand module/type-attached plugins.
  • Integrate CPS later in the pipeline so .passive plays well with finally/raise; then expose structured parallelism (e.g., parallel for) via plugins.
  • Implement the mode switch for ref nilness defaults.
  • Continue compat-mode work and document ABI/FFI trade-offs.
  • Investigate cycle collection for ARC (.cyclic pragma, if/when it’s viable).

If you have time, try things out, report bugs, ask questions, and send small, focused PRs—every bit of feedback helps shape the direction.

Links

If your time is scarce but you want this direction to continue: Contributions are welcome via our Open Collective at https://opencollective.com/nim.

Nimony 0.3 Release Announcement

Nimony is a compiler for a streamlined variant of Nim, focused on supporting hard real-time and embedded systems through predictable machine code, scope-based memory management, and explicit error handling. It implements a subset of Nim's features, with plans to expand toward compatibility with Nim 2 as Nim 3.0. This is our first release, version 0.3, which provides a working core for basic programs. It's an early preview, not a complete language, and targets developers interested in testing the fundamentals.

Installation Instructions

To build and install Nimony from source:

Clone the repository:

git clone https://github.com/nim-lang/nimony.git
cd nimony
git checkout v0.3.0

Build using the hastur tool (requires an existing Nim installation):

nim c -r src/hastur build all

What's Implemented in This Release

Based on our progress to date, this release includes the following features, all of which compile and run as demonstrated in our tests:

  • Core Language Constructs: Module imports, basic I/O via echo, for loops with inlining, case statements on strings (using dispatch trees), iterators with yield, operators and overloads (including ==, [], []=, and $), the fields iterator for objects, defer statements, packed and union pragmas, the using statement, do notation, astToStr, system.compiles, push/pop pragmas, passL and dynlib pragmas, and proc pragma-to-section rewrite rule.

  • Generics and Concepts: Generics are type-checked at declaration time, requiring concepts for constraints where needed. For example:

    type
      Fibable = concept
        proc `<=`(a, b: Self): bool
        proc `+`(x, y: Self): Self
        proc `-`(x, y: Self): Self
    
    proc fib[T: Fibable](a: T): T =
      if a <= 2: result = 1
      else: result = fib(a-1) + fib(a-2)
    

    This catches errors early and supports IDE features like completions.

  • Templates and Plugins: Templates for code generation, with support for delegating to compiled plugins via the {.plugin.} pragma. Plugins run as native binaries after type-checking, enabling incremental and parallel processing. Basic API for plugins (nimonyplugins) are in place, though incomplete.

  • Memory Management: Scope-based destruction using atomic ARC (the only mode currently). Supports ref objects, including recursive ones, with destructors that handle cycles via a .cyclic pragma (acyclic by default). Sequences (seq) and strings are library-based, not built-in magic.

  • Safety Checks: Runtime bounds checking for arrays, overflow checking via builtins, and initial support for nil/not nil annotations on refs with flow-based analysis. Array indexes can be proven correct at compile-time using .requires and .ensures.

  • Error Handling: Exceptions limited to raising ErrorCode enums in {.raises.}-annotated procs. Try/except works for these. OOM is handled by keeping containers unchanged and calling an overridable oomHandler, allowing graceful continuation where possible.

  • Concurrency Basics: Raw threads via std/rawthreads, with locks and monotimes. Closures capture variables, optimizing away heap allocations when they don't escape. Initial .passive procs for CPS transformation, though integration with try/finally/raise is pending.

  • Standard Library Modules: Ported and working: syncio (basic I/O), parseopt (option parsing), hashes, tables (hash tables), intsets, assertions, dirs (file walking), unicode, encodings, monotimes, locks, rawthreads. Partial ports: math (some constants and procs), strutils.

  • Other: Open arrays as view types with polymorphic accessors and converters (e.g., string to openArray[char]). Inheritance with methods and dynamic dispatch, optimized to static calls where possible. Compile-time evaluation for consts from procs. Precompiled modules and semantic checking in the compiler.

Why These Changes Matter

These features provide a solid base for writing predictable code without manual memory management or implicit overhead. Type-checked generics reduce runtime surprises and improve error messages compared to instantiation-only checking. The plugin system allows extending the language without built-in macros, keeping the core simple while enabling custom transformations (e.g., for parallelism). Error handling via ErrorCode unifies propagation across libraries, mapping cleanly to system errors like POSIX errno, and avoids heap allocations for OOM cases. In practice, this means you can build small, self-contained programs for embedded targets with fewer footguns, though you'll need to work around missing parts like full exceptions or operator overloading for now.

What's Next

We're continuing development toward broader Nim compatibility in "compat" mode and more features in "aufbruch" mode, including a fuller standard library, complete not-nil checking, mode switches, circular dependency handling, and better CPS integration. Plugins will get a more mature API, and we'll add missing constructs like operator overloading for parentheses and term-rewriting. No firm dates—we'll release updates as they stabilize. Contributions are welcome via our Open Collective at https://opencollective.com/nim.

For more details:

  1. Working Features Table
Construct Description Constraints / Notes Example (from reports)
Module import + echo Importing standard modules and printing works. Uses std/syncio. import std / syncio; echo "hi", "abc"
Concepts Nim’s concept keyword works; used to constrain generics. “Generics are type-checked… when they are declared” and require concepts to compile in many cases. type Fibable = concept … proc fib[T: Fibable](a: T): T = … discard fib(8)
Type-checked generics Generics are type-checked at declaration time. “It does type-check generics when they are declared… without the concept annotation, the code wouldn't compile.” (same fib example as above)
Templates Template expansion works; templates can be used in user code. Also supports plugins via {.plugin: ...}. template copyInto(x: var X; body: untyped) = …
Template plugins Templates can delegate expansion to a compiled plugin. Output is cached; plugin compiled to native binary; expansion is sem-checked. template generateEcho(s: string) {.plugin: "deps/mplugin1".}
For loops For loops and inlining are implemented. for kind, key, val in getopt(): echo "##", key, "##", val
ARC memory management ARC-based memory management is implemented. “It does not leak memory either, recursive ref destructors seem to work well.” (BinaryTree example below)
Ref objects ref object types, including recursive structures. Destructors for refs work, including recursive refs. type BinaryTree = ref object … proc newNode*(data: sink string): BinaryTree = BinaryTree(data: data)
Inheritance + methods (dynamic dispatch) Methods with inheritance and dynamic dispatch work. “Methods and inheritance begin to work.” Virtual destructors possible; optimized to static calls when applicable. type RootObj {.inheritable.} = object; type Obj = object of RootObj; method m(o: RootObj) = …; method m(o: Obj) = …; m Obj(a:1,b:2,c:"3")
Case on string case-of on string works and emits dispatch trees. Implemented via dispatch trees (no hashing). case x of "foo": echo "foo" … else: echo "unknown"
Seq Sequences work: allocation, indexing, len, assignment. “Update: This program works now.” (hook generation/destructor now synthesized.) var s = newSeqstring; s[i] = data; echo s.len, " ", s[40]
Tables Hash table operations from std/tables work. Uses std/[hashes, tables]. var t = initTableint, int; t[123] = 321; inc t[123]; assert t.getOrDefault(123) == 322
IntSet std/intsets works (incl, excl, contains, containsOrIncl). IntSet builds on working hash tables and seq. var s = initIntSet(); s.incl i; assert s.contains i; assert not containsOrIncl(s, 7); assert containsOrIncl(s, 7)
Parseopt getopt iteration works. import std/[parseopt, syncio]; for kind, key, val in getopt(): echo "##", key, "##", val
Bounds checking for arrays Builtin arrays have runtime index checking. Out-of-bounds causes a runtime error message. type TA = array[0..3, int]; for i in 0..4: echo a[i]
Overflow checking Overflow flag support via NIFC builtins. Syntax: {.keepOverflowFlag.}: …; overflowFlag(); {.keepOverflowFlag.}: let x = a + b; echo overflowFlag()
openArray (library-based) openArray implemented as a view type; not magic. .view pragma; .requires for bound-check elimination; polymorphic accessors. type openArray*[T] {.view.} = object … proc []*(…){.requires: idx >= 0 and idx < x.len.} = …
Converters Converters (incl. string→openArray[char]) implemented. converter toOpenArray*(s: string): openArray[char] = openArray[char](a: rawData(s), len: s.len)
Iterators + yield User-defined iterators with yield work. iterator items*[T](a: openArray[T]): var T = … yield a[i]
Operators and overloads Operator procs (==, [], []=, $) work. Equality, indexing, and stringify demonstrated. proc ==[T: Equatable](a, b: openArray[T]): bool = …; proc $(n: BinaryTree): string = …
fields iterator fields and fieldPairs implemented; fields works. “fields and fieldPairs iterators have been implemented.” for f in fields(o): echo f
Exceptions (ErrorCode-based) try/except works; raise supported via ErrorCode enum. “For now only raising the new ErrorCode enum is supported.” proc willFail() {.raises.} = raise Failure; try: willFail() except: echo "failed"
nil/not nil refs nil ref and not nil ref modes usable in code; flow-based checks demonstrated. Three modes described; default still “unchecked ref”. proc p(): Myref not nil {.raises.} = Myref(x: 5); var r: nil Myref; r = p(); echo r[].x
Closures (basic) Capturing closures compile and run; some heap allocs optimized away. “Closures begin to work.” Optimization avoids heap if closure doesn’t escape. proc testClosure() = var x = 40; proc inner = echo x; inner()
Defer defer statement implemented. (Mentioned as implemented; no code given in text.)
packed/union pragmas packed and union pragmas implemented. (Mentioned as implemented; no code given in text.)
using statement using implemented. (Mentioned as implemented; no code given in text.)
.passive (CPS) procs .passive procs transform to CPS; basic cases work. Integration with try/finally/raise needs later CPS phase; likely not in early release. proc passiveProc(x: string) {.passive.} = echo x
astToStr astToStr exists. (Mentioned as implemented; no code given in text.)
do notation do notation implemented. (Mentioned as implemented; no code given in text.)
system.compiles system.compiles implemented. (Mentioned as implemented; no code given in text.)
push/pop pragmas push and pop pragmas implemented. (Mentioned as implemented; no code given in text.)
passL pragma passL pragma implemented. (Mentioned as implemented; no code given in text.)
dynlib pragma dynlib pragma implemented. (Mentioned as implemented; no code given in text.)
Rewrite rule (pragma to section) Rewrite rule “proc p {.t.}… → t: proc p…” implemented. (Mentioned as implemented; no code given in text.)
Raw threads Threads via std/rawthreads supported. (Mentioned: “supports threads via its rawthreads module.”)
Compile-time evaluation staticExec-like CT engine; const from procs works. Enables potential macro support. proc myop(a,b:string):string= a & ";" & b; const MyConst = myop("Hello","World"); echo MyConst
Inner generic procs (lambda lifting) Generic inner procs are moved so lambda lifting can handle them; code works. Heap avoided when closure doesn’t escape. proc outer = var x = 120; proc inner[T] = echo x; innerint
File system dirs std/dirs works; walkDir compiles and runs. Used with try/except. for k, p in walkDir(path"nimcache"): echo $k, " ", $p
  1. In-Progress / Not Yet Working Features
  • “But for now only raising the new ErrorCode enum is supported.”

    • Status: Exceptions supported only for ErrorCode; broader exception types not yet.
  • “Mini progress. The following program parses: … But not-nil checking based on a control flow graph is not implemented yet as I debug our control flow graph.”

    • Status: At that point not-nil checking missing; later: “This program works:” (not-nil refs demonstrated).
  • “There are 3 modes: … The default is still ‘unchecked ref’ as we haven't implemented the mode switch.”

    • Status: Mode switch not implemented.
  • “The compiler was built with solving this problem in mind but it's not been implemented yet.” (re: circular dependency problem)

    • Status: Circular dependency solution not implemented.
  • “Closures begin to work:”

    • Status: Closures are early-stage; basic cases work.
  • “case object is beginning to work.”

    • Status: Case objects partial.
  • “However, to make it play nice with try finally and raise we likely need to do the CPS transformation much later in the pipeline than we currently do.”

    • Status: CPS/.passive not fully integrated with try/finally/raise yet.
  • “Most probably this will still lack the .passive features though. (September 2025)”

    • Status: Early releases will likely not include .passive features.
  • “math, strutils, unicode, encodings. Once we have these we'll have a first release, version 0.4 or something.”

    • Status: stdlib work-in-progress (later: unicode/encodings ported; math partially).
  • “math.nim contains many consts & procs.”

    • Status: math is partial (not claimed complete).
  • “There are now beginnings of an API for plugins.”

    • Status: Plugin API exists only in early form.
  • “Progress: Source code filters are now supported.” (supported) contrasted with “As there is currently no API for plugins, we have to import parts of the Nimony compiler…” (earlier)

    • Status: Earlier plugin API missing; later: beginnings exist.
  1. Modules / Subsystems Status

Already implemented/ported:

  • std/syncio: echo and basic I/O. Used across examples.
  • std/parseopt: getopt iteration. Example prints options.
  • std/tables (+ std/hashes): Table[K,V] operations (initTable, contains, hasKey, getOrDefault, mgetOrPut, indexing, len).
  • std/intsets: IntSet operations (incl, excl, contains, containsOrIncl).
  • std/dirs: walkDir with path literals (path"..."). Example compiles and runs.
  • std/assertions: assert in tests (used in tables/intset code).
  • std/unicode: “unicode.nim has been ported.”
  • std/encodings: “std/encodings has been ported.”
  • std/monotimes: “std/monotimes has been ported.”
  • std/locks: “We now have std/locks.”
  • std/rawthreads: “supports threads via its rawthreads module.”
  • Compiler subsystems:
    • Precompiled modules (reimplemented).
    • Semantic checking (symbol lookup, type checking).
    • Generic instantiation + concepts.
    • Template expansion.
    • For loop inlining.
    • ARC-based memory management.
    • Code generation via NIFC.
    • Source code filters supported.
    • Backend: whole-program dead-code elimination; merges generic instances.
    • Frontend: check + --usages/--def (find usages, goto definition).
    • Control flow graph construction rewritten (“split streams” algorithm).

Partially ported / in progress:

  • std/math: “contains many consts & procs.” (partial)
  • std/strutils: listed as a target under active work.
  • Plugin API: “There are now beginnings of an API for plugins.”

Explicitly not implemented yet:

  • Circular dependency solution: “not been implemented yet.”
  • Mode switch for ref-nilness defaults: “haven't implemented the mode switch.”
  1. Demonstration Code Snippet

Note: Uses only constructs shown to compile and run in the reports: import std modules, echo, arrays with bounds checks, case on string, openArray-based sort proc, and tables.

import std/[syncio, hashes, tables]

# Sorting over openArray[int] (works per report)
proc sort(a: var openArray[int]) =
  var n = a.len
  while true:
    var swapped = false
    var i = 0
    while i < n-1:
      if a[i] > a[i+1]:
        swap a[i], a[i+1]
        swapped = true
      inc i
    dec n
    if not swapped: break

# Case on string (works per report)
proc dispatch(x: string) =
  case x
  of "foo": echo "foo"
  of "bar": echo "bar"
  else:     echo "unknown"

# Tables (works per report)
proc demoTable() =
  var t = initTable[int, int]()
  t[123] = 321
  inc t[123]
  echo "t[123] = ", t[123]          # expected 322
  echo "len = ", t.len

# Main
var x = [3, 2, 1, 6, 7, 4, 5]
sort x
for i in 0..<x.len:
  echo x[i]

dispatch("foo")
dispatch("other")

demoTable()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment