Nimony ist ein vielversprechendes neues Projekt, das die Programmiersprache Nim in eine neue Ära führen soll. Entwickelt als Compiler für eine Nim-Variante, die letztlich Version 3.0 werden soll, vereint Nimony bewährte Konzepte aus Nim 2 mit neuen Ideen, die gezielt auf die Bedürfnisse von Echtzeitsystemen und eingebetteten Anwendungen ausgerichtet sind. Obwohl Nim als umfassende Sprache sehr komplex ist und seine vollständige Umsetzung noch Zeit braucht, zeichnet sich Nimony bereits jetzt durch eine elegante, schlanke Syntax und innovative Mechanismen aus, die auch eigenständig eine hohe Nutzbarkeit bieten. Die Entwicklung von Nimony ist geprägt von dem Bestreben, eine (weitgehend) speichersichere Sprache mit Fokus auf Vorhersagbarkeit, Effizienz und Zuverlässigkeit zu schaffen, die speziell die Herausforderungen von Embedded- und Echtzeitsystemen adressiert.
Dabei steht die Allgemeingültigkeit im Vordergrund: Funktioniert die Sprache robust und performant auf ressourcenbeschränkten Systemen, so kann sie mühelos in anderen Umgebungen eingesetzt werden. Ein entscheidendes Kriterium in diesem Bereich ist die Worst-Case Execution Time (WCET). Operationen sollen in vorhersehbarer und konstanter Zeit ausführbar sein, wobei der generierte Maschinencode keine Überraschungen bereithält. Daraus resultiert ein Verzicht auf Just-in-Time Compiler oder auf Tracing Garbage Collector mit unvorhersehbaren Pausen. Primitive Datentypen wie Integer oder Zeichen werden eins zu eins auf Maschinenwörter und Bytes abgebildet, komplexe Datentypen werden ohne Zeiger-Indirektionen direkt im Speicher abgelegt.
Ein Objekt mit mehreren Gleitkommafeldelementen wird somit direkt im Stack-Frame oder in eingebetteten Strukturen festgehalten. Diese Speicherorganisation, die auch schon in Nim 2 Verwendung findet, garantiert eine hohe Zugriffsgeschwindigkeit und Kontrolle über den Speicherverbrauch. Ein zentrales Element von Nimonys Design ist das automatische Speichermanagement, das zugleich sicher sein und den Quellcode prägnant halten soll. Das manuelle Freigeben von Speicher birgt immer das Risiko von Fehlern wie das Verwenden freigegebener Ressourcen, was schwerwiegende Fehler verursachen kann. Nimony setzt auf einen Ansatz, der dem von Rust und C++ ähnelt, nämlich ein scopes-basiertes Speichermanagement mit Destruktoren und Move-Semantik.
Dabei wird der Fokus auf Einfachheit gelegt: Wo Nim 2 noch eine Vielzahl von Schaltern für das Speicher-Management anbietet, gibt es bei Nimony nur die Variante mm:atomicArc – eine atomare Referenzzählung mit sicheren Destruktoren. Eine Cycle Collection, also ein Algorithmus zur zyklischen Speicherbereinigung, ist in Entwicklung, aber noch nicht produktiv nutzbar. Objekte, die potenziell Zyklen verursachen, müssen daher explizit mit einer speziellen Annotation (.cyclic) versehen werden, während der Standardfall .acyclic ist und keine zusätzlichen Überprüfungen benötigt.
Ein großer Pluspunkt der Speicherverwaltung mit Destruktoren ist ihre Komponierbarkeit: Sequenzen von Kanälen, die Betriebssystemressourcen verwalten, können automatisch korrekt dealloziert werden, ohne dass der Entwickler eingreifen muss. Diese Eigenschaft wird von Systemen mit Garbage Collection oder Region-Based Memory Management nicht in gleichem Maße erreicht, da deren Ressourcenfreigabe oft unvorhersehbar oder verzögert abläuft. Die Fehlerbehandlung in Nimony verfolgt einen anderen Weg als viele moderne Sprachen, die klassischerweise Ausnahmen vermeiden wollen und stattdessen auf Summentypen und Pattern Matching setzen. Der Autor bevorzugt die Integration des Fehlerzustands direkt in die Objekte selbst. So kann beispielsweise der Status eines Streams explizit seinen Fehlerzustand speichern, Gleitkommazahlen NaN (Not a Number) verwenden und ganze Wertebereiche für ungültige Integer reserviert werden.
Für Fälle, in denen kein geeignetes Objekt für Fehlerzustände existiert, steht ein thread-gebundener Fehlerzustand zur Verfügung, der flexibel als Nebenkanal zur Fehlerübermittlung dient. Einzigartig ist in Nimony, dass Routinen, welche Ausnahmen auslösen können, ausdrücklich mit der Annotation {.raises.} versehen werden müssen. Dadurch wird klar und explizit gemacht, wo versteckter Kontrollfluss auftritt.
Zusätzlich zu den klassischen Ausnahmen können aus dem neuen ErrorCode Enum Ausnahmen ohne Heap-Allocation ausgelöst werden. Dieser Ansatz kombiniert die Vorteile von klassischem Exception-Handling mit der Effizienz von Fehlercodes und verhindert z.B. Schwierigkeiten beim Umgang mit Out-Of-Memory-Situationen. Out-Of-Memory (OOM) wird in Nimony nicht als Todesurteil für die Anwendung betrachtet, sondern als ein mitdenkbarer Fehlerzustand.
Container können eine eigene, überschreibbare OOM-Handler-Funktion implementieren, die standardmäßig die Größe der angefragten Speicheranforderung protokolliert, ohne sofort das Programm zu beenden. Der Zustand ist anschließend abfragbar, wodurch das Programm ggf. weiterlaufen oder alternative Strategien verfolgen kann. Dies bewahrt eine größere Robustheit gegenüber Ressourcenmangel. Eine Besonderheit ist die Art, wie Nimony mit nullbaren Referenzen umgeht.
Konstruktionen von Referenzobjekten können fehlschlagen und nil zurückgeben, doch jeder solcher Fall muss eindeutig behandelt werden. Für Funktionen, die das .raises-Attribut tragen, ist der Rückgabetyp automatisch nicht-nilbar, sodass Fehler wie unkontrolliertes Dereferenzieren quasi statisch ausgeschlossen werden. Dies erhöht die Sicherheit und spart Prüfungen an vielen Stellen. Generische Programmierung ist ein weiterer Eckpfeiler von Nimony.
Eine vollständig typsichere und komplett geprüfte Implementierung generischer Codes ermöglicht es Entwicklern, maßgeschneiderte Datenstrukturen wie sequentielle Listen oder Hashtabellen effizient zu schaffen. Nimony erhebt und erweitert damit die Konzepte, die in Nim 2 etabliert sind, etwa die sogenannten Concepts, mit denen Sprachkonstrukte definiert werden, denen generische Typen folgen müssen. Dies verbessert nicht nur die Fehlermeldungen zur Compilezeit und erhöht die Entwicklerproduktivität, sondern verbessert auch die Möglichkeiten für intelligente Code-Vervollständigung in IDEs. Nebenläufigkeit und Parallelismus werden in Nimony durch ein neuartiges und vereinheitlichtes Modell umgesetzt. Die Programmiersprache kennt nur ein Konstrukt namens spawn, welches sowohl asynchrone als auch multi-threaded Programmierung ermöglicht.
Dabei entscheidet zur Laufzeit ein Scheduler, ob die Ausführung im gleichen oder in einem neuen Thread stattfindet. Argumente für spawn müssen deshalb immer Thread-Sicherheit garantieren. Faszinierend ist, dass unter der Haube das Programm in Continuation Passing Style (CPS) transformiert wird, ohne dass Programmierer dies unmittelbar mitbekommen. Diese Technik ermöglicht eine sehr feine Steuerung von asynchronen Operationen und verschmilzt elegant mit dem Threadpool-Ansatz, der reine Parallelität abbildet. Für klassische Parallelfor-Schleifen bietet Nimony spezielle Iteratoren (z.
B. “||”), mit denen parallele Schleifendurchläufe sauber modelliert werden können, ohne dass der Programmierer händisch Synchronisationsprobleme lösen muss. Dies ist besonders relevant für wissenschaftliches Rechnen oder GPU-Programmierung, wo parallelisierte Matrixoperationen und numerische Berechnungen dominieren. Schließlich ermöglicht Nimony eine mächtige Meta-Programmier-Architektur, die über einfache Makros oder Templates weit hinausgeht. Compiler-Plugins, die als kompiliertes Maschinencode-Modul vorliegen, können vollständig mit allen Sprachfeatures interagieren und erhalten während der Kompilierung vollständige Typsicherheit und Introspektion.
Dies erlaubt sehr flexible und zugleich effiziente Quellcode-Transformationen. Sowohl Modular-Plugins, die ganze Quellcode-Module umstrukturieren, als auch Plugins, die an Typ-Deklarationen angebunden sind, eröffnen tiefgreifende Optimierungen, wie z.B. die Vermeidung temporärer Objekte bei komplexen Operationen. Die Plugin-Architektur stellt somit eine wichtige Grundlage dar, um den Sprachumfang mit High-Level-Konstrukten zu erweitern und Compileroptimierungen menschenlesbar in den Quelltext zu integrieren.