In the process of developing the Nim 3.0 programming language, a new Nimony compiler is being developed, the fundamental design principle of which is to achieve predictability of the worst-case execution time (WCET). This requirement is dictated by the focus on hard real-time systems, where non-deterministic behavior is unacceptable. As a result, the Nimony architecture excludes the use of JIT compilers and tracing garbage collectors, since their operations can introduce unpredictable delays.
To achieve predictability, primitive data types (integers, characters) are directly mapped to the machine words and bytes of the corresponding architecture. Composite types (structures, objects) are formed without using indirection, being placed directly on the stack or inside other data structures. This approach minimizes overhead and provides a more transparent correspondence between the source code and the generated machine code.
In the area of automatic memory management (MM), Nimony departs from the variety of options available in Nim 2.0 by offering a single standardized mode: "mm:atomicArc". This mode is based on reference counting using atomic operations, supplemented by move semantics and calling destructors on object destruction, which brings the approach closer to practices adopted in Rust and modern C++.
The key innovation is the explicit division of objects into acyclic and potentially cyclic. By default, objects are considered acyclic (.acyclic), which is a new behavior. For data types whose instances can form cyclic references, an explicit annotation with the .cyclic pragma is required. It is noted that a new algorithm for assembling cyclic references is being developed, but its readiness for industrial use is not guaranteed at the moment. The advantage of MM based on destructors is its composability: management of resources requiring release (e.g. file descriptors, network sockets, channels) is integrated naturally through the destructors of the corresponding types.
The approach to error handling in Nimony has undergone significant changes. The author of Nim expresses dissatisfaction with traditional exception mechanisms and their emulation via algebraic data types (sum types). Instead, the concept of integrating the error state directly into the data object itself is proposed. Examples include representing the error in I/O streams via a special state, using NaN for floating-point numbers, or low(int) for invalid integer values. In cases where the object cannot encapsulate the error state, it is proposed to use a thread-local variable for signaling.
However, Nim's traditional exception mechanism is retained, but with one important clarification: any procedure capable of raising an exception must now be annotated with the {.raises.} pragma. This requirement is intended to explicitly indicate potential non-local control transitions.
As an alternative or complement, a new enumerated type, ErrorCode, is introduced. This type is type-safe and requires exhaustive processing of all possible cases (similar to a case statement for an enum). ErrorCode is designed to be mapped to standard error codes of various systems and protocols, such as POSIX errno and error codes. Windows HTTP API and Status Codes. The goal is to unify error handling across different libraries and enable direct translation of system errors (e.g., "disk full") into corresponding status codes (e.g., HTTP 507) without additional conversion. Using ErrorCode also allows for error handling and propagation without allocating memory on the heap, which is critical for handling out-of-memory (OOM) situations.
Nimony's out of memory (OOM) handling is implemented by moving away from the common practice of crashing the program ("die on OOM"). Instead, it provides a mechanism to allow the application to continue running. Containers and memory allocations that cannot fulfill a request call an overridable oomHandler handler. The default implementation writes the size of the failed request to a thread-local variable and allows execution to continue. The out of memory condition for the current thread can be checked by calling threadOutOfMem().
The developer can provide his own implementation of oomHandler, for example for logging or crashing the application, if such behavior is preferred. An important aspect is the handling of ref object construction operations that may fail due to OOM. In Nimony, the result of such operations (e.g. via new or similar constructors) may be nil, and the compiler forces handling of this case similarly to working with Option types, thus preventing null pointer dereference errors. In the context of procedures annotated with {.raises.}, a nil return value may be automatically converted to ErrorCode.OutOfMemError.
Nimony's generics engine has been improved over Nim 2.0. The key improvement is that generics are now fully type-checked at definition time, rather than just when instantiated with concrete types. This is expected to catch errors earlier in the compilation process, provide more informative error messages, and improve IDE support, such as code completion.
Concepts, already present in Nim, retain their role as a mechanism for statically describing requirements on the type parameters of generic functions and types. They allow one to formally specify what operations or properties a type must satisfy in order to be used in a given generic context.
Nimony aims to unify asynchronous and multithreaded programming models under a single spawn construct. The decision whether a task launched via spawn will be executed in the same thread (asynchronously) or in a separate thread from the pool (multithreaded) is made by the scheduler at runtime. This imposes certain requirements on the arguments passed to spawn: they must be thread-safe.
The internal implementation of the concurrency model will be based on continuations, and the compiler will perform the program transformation into the Continuation-Passing Style (CPS). It is noted that the spawn construct itself is implemented not as a built-in language feature, but as a compiler plugin.
Parallelism is seen as a simpler problem compared to concurrency. For writing purely parallel code oriented to computations (e.g., processing arrays of data), Nimony will offer special constructs such as parallel for loops, denoted by the operator "||". This will allow parallel algorithms to be implemented without the need for flow vars, which can be convenient for scientific computing or GPU programming.
Nim's metaprogramming system, known for its macros, evolves in Nimony toward compiler plugins. Plugins are code that is compiled into native instructions and executed late in the compiler's execution, after the type-checking step. This gives plugins access to full type information and semantics of the code being analyzed.
Improved and more convenient APIs for plugin development are promised. The use of the NIM Intermediate Format (NIF) should also simplify the implementation of various code transformations. Plugins can be executed incrementally and in parallel, which helps improve compilation performance.
Plugin types:
- Template Plugins: Attach to specific templates and process code associated with their calls.
- Module Plugins: Take as input the AST (Abstract Syntax Tree) of the entire module and must return the transformed tree. The spawn construct is an example of implementation via a module plugin.
- Iterator Plugins: Similar to template plugins, but applied to iterators.
- Nominal Type Plugins: Can be tied to specific data types, replacing Nim's "term rewriting macros" mechanism. This allows optimizations such as eliminating temporaries when performing matrix operations.
Nimony's documentation is provided on a website that is entirely AI-generated and verified by Nim's author for accuracy.
Source: opennet.ru
