Having recently come back to Nim after not using it much since pre-2.0, I was very confused about the state of things. I used to make use of async patterns, and now I need threads for a particular project. Nim is great fit for every other reason I can come up with, but the ambiguity and lack of direction relating to threads has set me back several days. Finally, I turned to AI for the answers. I will relay it to the community so that others in my position may have a clearer picture than I did, written by Claude's deep research...
Nim's concurrency features have undergone dramatic transformations from 2008 to August 2025, shifting from an ambitious compile-time safety model to deprecated standard library modules, with the creator now recommending his new Malebolgia library while the community fragments across competing solutions. The most significant current development is that the standard threadpool is officially deprecated, async/await has memory leaks under the default ORC memory manager, and developers face a 40% performance regression when using threads in Nim 2.0—creating what many describe as a concurrency crisis.
This research reveals a programming language caught between revolutionary ambitions and pragmatic constraints. The journey from Nimrod's 2013 vision of statically-verified thread safety to today's ecosystem of external libraries represents both technical evolution and philosophical shifts about where concurrency should live in a modern systems language. The continuous changes stem from fundamental conflicts between thread-local garbage collection, shared-heap memory models, and the challenge of composing different concurrency paradigms.
The concurrency story begins with Andreas Rumpf's 2008 launch of Nimrod, initially featuring basic threading support typical of systems languages. By June 2013, Rumpf published an extraordinarily ambitious vision: a shared-memory concurrency system using compile-time verification to prevent data races through sophisticated type qualifiers like shared ptr and guarded ptr, combined with static lock hierarchy analysis to prevent deadlocks. The system would track lock levels at compile-time, ensuring locks were always acquired in order to mathematically prevent circular wait conditions.
This theoretical elegance proved too complex for practical implementation. The type system changes were overwhelming, the static analysis couldn't prevent all race conditions, and developers found the abstractions too abstract for real-world concurrent programming. Rumpf himself acknowledged the trade-offs, noting that while the system couldn't prevent every possible data race, it "surely looks like a sweet trade-off"—but the community disagreed.
The pragmatic pivot came in 2015 when Dominik Picheta created the asyncdispatch module, implementing async/await through Nim's macro system rather than core language features. This library-first approach succeeded where the ambitious type system failed, providing immediate utility for web developers building HTTP servers and network applications. The async/await model became Nim's primary concurrency story through version 1.0 (released September 2019), coexisting uneasily with the older spawn/threadpool system for CPU-bound parallelism. This dual model—async for I/O, threadpool for computation—established a pattern of fragmentation that would only intensify.
The transition from Nim 1.x to 2.0 brought fundamental changes driven by memory management evolution. The original refc garbage collector used thread-local heaps, making it impossible to share GC-managed objects (strings, sequences, references) between threads without deep copies. This architectural constraint meant async/await and spawn/parallel couldn't compose—you literally couldn't await a spawned task because they operated in different memory universes.
Nim 2.0 introduced ORC (Optimized Reference Counting with Cycle collection) as the default memory manager in 2023, enabling shared heaps across threads. But this seemingly positive change triggered cascading problems. The reference counting operations weren't atomic, creating race conditions. Performance benchmarks showed programs compiled with --threads:on (now default in Nim 2.0) ran 40% slower than their single-threaded counterparts, even when using only one thread. Memory leaks appeared in async code under ORC that didn't exist under the old GC.
The threadpool module, already suffering from global queue contention and lack of work-stealing, became officially deprecated with an explicit message: "use the nimble packages malebolgia, taskpools or weave instead". The async/await implementation showed its age with documented memory leaks, poor cancellation support, and the inability to integrate with parallel constructs. One developer reported their production HTTP server experienced a 3x memory usage increase when upgrading from Nim 1.6 to 2.0, requiring the -d:useMalloc workaround to restore normal behavior.
By 2024, Andreas Rumpf had developed clear opinions about Nim's concurrency future, crystallized in his Malebolgia library—a sub-300-line structured concurrency solution emphasizing predictability over flexibility. Malebolgia deliberately omits FlowVars, instead using awaitAll barriers for synchronization. It focuses on bounded memory consumption, built-in cancellation, and what Rumpf calls "the 'backpressure' problem as a side effect" of its design constraints.
Rumpf's current recommendations are unambiguous: use Malebolgia for general concurrency, or alternatives like taskpools (Status-im's lightweight solution) and weave (mratsim's high-performance computing runtime supporting trillions of tasks). He explicitly warns against the deprecated standard threadpool and acknowledges "open secret" criticisms of async/await, particularly its incompatibility with gc:orc and the complexity of its macro-based implementation generating 3x more code than equivalent threaded solutions.
The philosophical shift is striking. The 2013 vision sought to encode all concurrency safety in the type system; Malebolgia achieves safety through structural constraints—all tasks must complete within defined scopes. Where the original model offered maximum flexibility with compile-time verification, the new approach trades flexibility for predictability. Rumpf's warning on the old concurrency documentation captures this evolution: "The information presented here is severely outdated. Nim's concurrency is now based on different mechanisms (scope based memory management and destructors)."
The most sophisticated development in Nim's concurrency landscape is CPS (Continuation Passing Style), which represents a fundamental reimagining of how concurrency composition works. Unlike other approaches, CPS has actually achieved the holy grail: a working async/threading runtime that can compose different concurrency models seamlessly.
The nim-works/cps project transforms procedures into continuation-passing style using compile-time macros, creating extremely lightweight continuations as small as 32-40 bytes each. This enables patterns impossible with traditional threading or async approaches—continuations can migrate between threads with zero-copy operations, run lock-free within threads, and compose arbitrary dispatch patterns. The experimental insideout runtime built on CPS demonstrates "millions of continuations per second" with features like detached threads, channels with backpressure, and signal handling.
Most impressively, CPS solves the composition problem that plagues other approaches. You can build async I/O, parallel computation, and structured concurrency as libraries on top of the same CPS foundation, with full interoperability. The threadpool example in the CPS repository shows CPS continuations running across multiple OS threads with work-stealing, while the coroutine examples demonstrate CSP-style channel communication.
The critical limitation: these working implementations don't work with Nim >2.0. CPS requires specific compiler conditions (--gc:arc, --panics:on) and primarily develops against Nimskull rather than mainline Nim. The technology exists and works brilliantly, but version compatibility issues prevent adoption by developers who need current Nim versions. This creates the frustrating situation where Nim's most advanced concurrency solution is effectively unavailable to most users.
Developer reactions to Nim's concurrency changes range from confusion to anger, with performance regressions and breaking changes creating production headaches. The 40% performance penalty from default threading in Nim 2.0 forced many to disable threads entirely or use memory allocator workarounds. Multiple developers reported that multi-threaded async code became 1.7x slower than single-threaded execution—the opposite of expected behavior.
The ecosystem fragmentation between asyncdispatch (standard library) and chronos (Status's alternative) forces library authors to choose sides or attempt supporting both incompatible APIs. Documentation gaps mean basic operations require searching forums, IRC logs, or reading source code. One frustrated developer compared async/await to threading implementations, finding threading required less code, generated smaller binaries, and was actually simpler despite its reputation for complexity. Their analysis showed async generated 125KB binaries versus 52KB for threads, with significantly more complex codegen.
Success stories exist but remain limited. Nim Forum runs successfully on async/await through the httpbeast server, demonstrating stability when properly implemented. Status's Ethereum client uses chronos for P2P networking without major issues. Some developers achieved OpenBLAS-level performance for matrix multiplication using careful threading. But these successes require deep expertise and careful navigation of undocumented pitfalls.
The most telling community sentiment comes from RFC #295: "This is not the Nim concurrency story I want to tell to newcomers, and I think if you're honest, it's not the one you want to share, either." Developers consistently mention envying Go's simple goroutines, Rust's clear async semantics, and even Python's better-documented asyncio despite its complexity.
The engineering reasons for Nim's concurrency churn stem from fundamental incompatibilities between memory management strategies and concurrency models. Thread-local garbage collection makes thread communication expensive, requiring deep copies for safety. Shared-heap models enable communication but introduce race conditions in reference counting without atomic operations or complex synchronization.
Performance measurements reveal each model's limitations. Async/await generates 3x more C code than equivalent threading due to closure iterator transformations and macro complexity. The standard threadpool's global queue creates contention without work-stealing for load balancing, making it unsuitable for dynamic parallelism. The new ORC memory manager conflicts with existing async implementations, causing memory leaks in exception handlers and steady memory growth in long-running servers.
Different concurrency models optimize for incompatible goals—async/await for high-throughput I/O, ARC for deterministic real-time performance, spawn/parallel for CPU-intensive computation, and CPS for maximum composability. The most advanced solution, CPS, has actually solved many of these problems—achieving async/threading runtime composition, zero-copy thread migration, and running millions of continuations per second. However, these implementations don't work with Nim >2.0, creating a situation where the best technology is stuck on older compiler versions.
Platform differences compound the complexity. Windows shows different memory leak patterns than Linux under ARC/ORC. NUMA architectures require memory locality awareness that current implementations lack. The threading infrastructure includes platform-specific resource management like closeHandle on Windows that doesn't exist on Unix systems.
As of August 2025, Nim's concurrency exists in managed chaos. The standard library threadpool is deprecated but still present. Async/await remains the default for I/O but leaks memory under the default ORC manager. The community has fragmented across Malebolgia, taskpools, weave, chronos, and experimental CPS implementations. The nim-lang/threading repository offers new multi-producer multi-consumer channels for ARC/ORC, but adoption remains limited.
Breaking changes continue accumulating. Code using spawn must migrate to external libraries with different APIs. Async code may leak memory without -d:useMalloc workarounds. The performance regression from default threading means many applications run slower after upgrading. Documentation hasn't kept pace, leaving developers to discover solutions through trial and error or community forums.
Migration paths exist but require significant effort. Moving from standard threadpool to taskpools means rewriting spawn calls with different semantics. Choosing between asyncdispatch and chronos affects entire library ecosystems. Developers must understand memory management implications to choose between refc (stable but limited), ARC (fast but sharp edges), and ORC (default but problematic).
Active development continues on multiple fronts, with CPS (Continuation Passing Style) representing the most sophisticated advancement. The CPS implementation has already achieved an async/threading runtime through the nim-works/cps project and experimental libraries like disruptek's insideout concurrency runtime. CPS offers zero-copy migration between threads, lock-free operation, and extremely lightweight continuations (as small as 32-40 bytes each). The insideout runtime demonstrates running "millions of continuations per second on modern desktop hardware" with features like detached threads, channels with backpressure, and lock-free queues.
However, the major limitation is Nim version compatibility: the working CPS async/threading implementations don't work with Nim >2.0. This creates a paradox where the most advanced concurrency solution is incompatible with current Nim versions. CPS requires specific compiler conditions (--gc:arc, --panics:on) and primarily works with Nimskull rather than mainline Nim. The technology exists and works, but version compatibility issues prevent broader adoption.
RFC proposals suggest making FlowVars and channels awaitable to bridge parallelism and concurrency. The threading library evolves toward better channel abstractions. But without unified direction and version compatibility resolution, these efforts risk creating more fragmentation rather than convergence.
Nim's concurrency evolution from 2008 to 2025 represents a case study in the challenges of evolving fundamental language features while maintaining compatibility and performance. The journey from ambitious compile-time verification through pragmatic async/await to external library solutions reflects both technical constraints and philosophical shifts about where complexity should live in a programming language.
The current prescription from Andreas Rumpf—use Malebolgia or third-party solutions rather than standard library features—acknowledges that concurrency may be too complex and evolving for standard library stability. This represents a fundamental shift from languages like Go or Java that provide canonical concurrency models, toward a more fragmented but flexible ecosystem where different use cases choose different solutions.
The most striking aspect of this story is that the technical problems have actually been solved. CPS has demonstrated working async/threading composition, zero-copy thread migration, and performance measured in millions of operations per second. But these solutions are trapped by version compatibility issues, creating a gap between what's possible and what's practical for most developers.
The continuous changes aren't arbitrary but driven by genuine engineering challenges: incompatible memory models, performance regressions, safety requirements, and the fundamental difficulty of composing different concurrency paradigms. Until compiler compatibility issues are resolved, ORC stabilizes, and performance regressions are addressed, Nim's concurrency story will remain one of evolution rather than stability. For developers, this means carefully evaluating requirements, understanding that cutting-edge solutions may require older Nim versions, and being prepared for future migrations as the ecosystem continues its search for the optimal balance between power, safety, and simplicity.