Vergleich von Ada und Rust anhand der Lösung von AoC-Problemen
(github.com/johnperry-math)- Verglichen werden die Unterschiede und Eigenschaften von Ada und Rust, die beim Lösen von Advent-of-Code-Problemen sichtbar wurden
- Analysiert werden die Unterschiede im Sprachdesign und in der praktischen Programmierweise beider Sprachen mit Fokus auf Sicherheit und Zuverlässigkeit
- Unterschiede zeigen sich aus vielen Blickwinkeln, etwa bei den Standardbibliotheken, eingebauten Funktionen, Leistungsunterschieden und dem Stil der Fehlerbehandlung
- Anhand praktischer Codebeispiele zu Modularität, Generics, Schleifen und Error Handling werden konkrete Erfahrungen aus der realen Entwicklung und Nutzung erläutert
- Besonders deutlich unterscheiden sich die Entwicklungserfahrungen durch die Art der statischen Typisierung, die Array-Verarbeitung und die Schnittstellen zur Fehlerbehandlung
Einführung und Zielsetzung
- Beim Lösen von Advent of Code (im Folgenden AoC) wurde zunächst nur Ada verwendet; seit 2023 werden Lösungen auch in Rust und Modula-2 geschrieben, was einen direkten Vergleich ermöglichte
- Beim Übertragen bestehender Ada-zentrierter Lösungen nach Rust wurden die strukturellen Unterschiede und die eigenständigen Ansätze beider Sprachen deutlich spürbar
- Ziel ist es, die praktischen Unterschiede im Hinblick auf Code-Sicherheit, Zuverlässigkeit und Sprachdesign klar herauszuarbeiten
Für den Vergleich verwendete Sprachversionen
- Ada 2022 (bei Bedarf unter Bezug auf einige Regeln aus SPARK 2014)
- Rust 2021 (für die zentralen Vergleiche auf Basis von Rust 1.81.0)
Ausgeklammerte Funktionen und Vergleichskriterien
- Die typischen Funktionen jeder Sprache (= Killer-Features) werden im Text nur kurz in Kommentaren erwähnt
- Einige Funktionen wurden je nach persönlicher Erfahrung und praktischem Bedarf der jeweiligen Lösung nicht behandelt
- Es wird versucht, persönliche Meinungen möglichst auszuklammern und sich auf die wesentlichen Merkmale zu konzentrieren
Hintergrund und Perspektive des Autors
- Sowohl bei Ada als auch bei Rust ist der Autor kein Muttersprachler; die Grundlage bilden Erfahrungen mit Sprachen der 1980er Jahre wie C/C++, Pascal und Modula-2
- Daher kann sich der Code-Stil von modernen oder idiomatischen Stilformen unterscheiden
- Die Implementierungen sind möglicherweise nicht optimal, und je nach Problemsituation wurden teils intuitive oder unübliche Lösungsansätze gewählt
Positionierung von Ada und Rust
- Ada ist weiterhin eine sehr sichere und hochzuverlässige Sprache für System- und Embedded-Entwicklung und legt großen Wert auf Code-Lesbarkeit
- Rust bringt Stärken bei Speichersicherheit und Systemprogrammierung mit und wurde in der Stack-Overflow-Entwicklerumfrage über mehrere Jahre als „beliebteste Sprache“ genannt
- Ada ist eine allgemein einsetzbare Hochsprache und bietet ein Spektrum, das besonders auf Lesen und Wartung ausgerichtet ist
- Rust zielt auf die Entwicklung von niedrigstufigen Systemprogrammen und etabliert eine Kultur sicherer Programmierung mit explizitem Speichermanagement sowie Fehler- und Option-Typen
Vergleich von Sicherheit und strukturellen Eigenschaften
-
Ada
- ISO-Standard (strenge Spezifikation)
- Es ist einfach, problemgerechte Typen zu definieren, etwa für Bereiche oder Stellenzahlen
- Array-Indizes dürfen auch nicht numerisch sein
- Mit SPARK existiert eine noch strengere Spezifikation
-
Rust
- Die Spezifikation stützt sich auf offizielle Dokumentation (Reference) und den Compiler
- Typdeklarationen hängen von Maschinentypen ab (z. B.
f64,u32) - Array-Indexierung ist im Wesentlichen nur mit numerischen Typen natürlich
Wichtige Zusammenfassung der Tabelle zu Funktionen/Eingebautem
- Unterschiede gibt es unter anderem bei der Unterstützung von Array-Bereichsprüfungen, generischen Containern, Nebenläufigkeit, gelabelten Schleifen und Pattern Matching
- Ada verwendet für Fehlerbehandlung Exceptions, Rust dagegen eine rückgabebasierte Behandlung über Result/Option-Typen
- Rust hebt sich durch Unterstützung für Makros, Pattern Matching und funktionale Reinheit hervor
- Ada unterstützt vertragsbasiertes Design und die Compile-Time-Prüfung von DBC (Design By Contract) in SPARK
- Bei der Speichersicherheit erzwingen Rust und SPARK Sicherheit, während Ada die Nutzung von Null-Pointern zulässt
Vergleich von Performance und Laufzeit
- Rust gilt allgemein als schnell zur Laufzeit, aber langsam beim Kompilieren, während Ada umgekehrt den Ruf hat, schnell zu kompilieren und zur Laufzeit wegen Prüfungen etwas langsamer zu sein
- In Benchmarks kam es in der day24-Aufgabe bei Rust aufgrund der Grenzen des f64-Typs zu einem Overflow; Ada konnte dagegen mit einer Hochsprachen-Typangabe wie digits 18 automatisch einen geeigneten Maschinentyp wählen, Overflows vermeiden und sehr gute Performance zeigen
- Rust muss dafür ein nicht stabiles
f128oder externe Bibliotheken verwenden, während Ada schon durch eine zum Compiler passende Typangabe im Vorteil sein kann
Dateiverarbeitung und Error Handling (Case Study 1)
Dateiverarbeitung in Ada
- Standardmäßig wird Ada.Text_IO verwendet
- Dateien lassen sich explizit öffnen, zeilenweise lesen und mit gewünschten Bereichen oder positionsbasierten Zeilen relativ intuitiv verarbeiten
- Bei Fehlern erfolgt die Behandlung eher über Exceptions als über klare Fehlermeldungen, und in der Funktionssignatur ist die Möglichkeit eines Fehlers nicht sichtbar
Dateiverarbeitung in Rust
- Verwendet werden std::fs::File und BufReader
- Beim Öffnen einer Datei wird ein Result-Typ zurückgegeben, sodass die Möglichkeit eines Fehlers klar sichtbar ist
- Direkter Zugriff per Zeichenindex wird nicht unterstützt; die Verarbeitung muss über Iteratoren erfolgen
- Im Zentrum stehen funktionale und iterative Werkzeuge wie map, filter, collect, sum sowie verschiedene Makros (z. B.
include_str!) - Durch explizite Fehlerangaben im Rückgabetyp wird die Fehlerweitergabe auf Funktionsebene klar nachvollziehbar
Modularität und Generics (Case Study 2)
Modularität in Ada
- Klare Trennung von Spezifikation (Interface) und Implementierung auf Basis von Packages
- Zur stärkeren Modularisierung werden Unterpakete sowie die Kombination von use/rename-Syntax zur Anpassung der Lesbarkeit eingesetzt
- Generics in Packages unterstützen die Verallgemeinerung von Typen, Konstanten und ganzen Unterpaketen
Modularität in Rust
- Module werden über das mod/crate-System organisiert; die Trennung von Spezifikation und Implementierung wird durch den Dokumentationsgenerator automatisiert
- Deklarative Zugriffskontrolle über pub/private
- Kombination aus Importen und Umbenennungen über use/as
- Eingebaute Testunterstützung erlaubt es, Testmodule direkt im Code zu deklarieren, zu bauen und automatisch auszuführen
Generics
- Ada unterstützt Generics nur auf Package-/Prozedur-Ebene (nicht für einzelne Typen allein)
- Rust kann Generics direkt auf Typen anwenden (template-basiert)
- Ada kann zusätzliche Typeigenschaften wie Wertebereiche über Range-Typen und Subtypen klar ausdrücken, während Rust dafür Instanzkonstanten nutzt
Vergleich von Enum-Typen (Case Study 3)
- Ada bietet kompakte Deklarationen und unterstützt dabei automatisch diskrete und geordnete Typen sowie deren Nutzung in Schleifen und als Indizes
- Rust-Enums sind in der Deklaration ähnlich, erfordern aber für Pattern Matching und Iteration einen expliziteren Umgang
Fazit
- Bei hochsprachlichen Spezifikationstypen, Verifizierbarkeit und Laufzeitprüfungen bietet Ada eine strengere Kontrolle
- Bei funktionalem Programmierstil, Makroprogrammierung und compilerunterstützter Fehlerbehandlung liegt Rust sowohl bei Entwicklerkomfort als auch bei Sicherheit vorn
- In der praktischen Problemlösung punktet Ada mit Kompatibilität zu älterem Code und Wartungsstärken, Rust dagegen mit einem modernen Entwicklungsökosystem sowie Vorteilen bei Sicherheit und Parallelität
1 Kommentare
Hacker-News-Kommentare
Erklärung zu Nim Subranges
Dokumentation dazu
Rust-Spezifikation
char) ist, weshalb man ihn leicht wie ein Byte-Array indizieren kann. Rust wurde dagegen mit einem stärkeren Bewusstsein für Unicode entworfen, daher sind Rust-Strings UTF-8-kodiert und damit wirklich „Text“. Deshalb erlaubt Ada Random Indexing wie bei einem Array, während man in Rust bei einem anderen String-Konzept notfalls auch einfach auf ein Byte-Array umsteigen kann[u8]oder die Methodestr::as_byteszu verwendenPrunt-Website
Prunt GitHub
Zugehöriger HN-Kommentar
pub const SIDE_LENGTH: usize = ROW_LENGTH;zu deklariereneggs[Robin]undeggs[Seagull]sinnvoll,eggs[5]aber nicht erlaubt. In Rust kann man ebenfalls eine gewünschte Datenstruktur bauen, zum Beispiel mit einerIndex<BirdSpecies>-Implementierung, und dann funktionierteggs[Robin], währendeggs[5]ein Fehler ist. Rust unterstützt das aber nicht direkt in der Sprache als „Array“. Gerade wenn man wie in Ada benutzerdefinierte Typen als Teilmengen ganzzahliger Typen deklarieren kann, kommt diese Form der Indexierung besonders gut zur Geltung. In Rust kann man reine benutzerdefinierte Typen für bereichsbeschränkte Integer bislang noch nicht bauen; intern gibt es nur Dinge wieNonZeroI16. Es wäre wirklich großartig, wenn Rust so etwas irgendwann unterstützen würde