Entwurfsentscheidungen

Jede Entwurfsentscheidung zu Hilde wird schriftlich festgehalten, mit Begründung und den Alternativen, die verworfen wurden. Jede davon kann jederzeit geändert werden. Hier die wichtigsten, jeweils in zwei oder drei Sätzen.

„unsafe“ für ganze Module, und nur wo C eingebunden wird

Ein Modul ist als Ganzes unsafe oder sicher und wird mit pragma Unsafe_Module markiert. Nur ein unsafe-Modul darf C einbinden, C einbetten oder rohen Speicher importieren. Arbeit mit Adressen und Zeigern gehört in die Bindings. Das Modul, das ein Programm importiert, ist sicher, und der Compiler prüft das. Die Option --forbid-unsafe-modules nimmt die eingebauten Module des Compilers anhand ihrer Herkunft aus, nicht anhand einer Namensliste, die jemand pflegen müsste.

Strukturierte Nebenläufigkeit auf Fibers

Tasks und ihre Ressourcen gehören zu einem Scope und enden mit ihm. Kanalenden und Task-Handles sind linear, ein Handle kann also nicht vergessen werden. Der erste Entwurf verwendete Threads des Betriebssystems ohne eigenen Scheduler. Daraus wurden Fibers statt Callbacks: so leicht wie Go, aber abgesichert.

Verträge nach dem Vorbild von Ada

requires und ensures werden zur Laufzeit geprüft und lassen sich nicht abschalten. Später kamen Invarianten für Records hinzu, Zufallstests, die aus den Verträgen erzeugt werden, und Beweise für einfache Fälle. Der Vertrag einer öffentlichen Funktion steht genau einmal, in ihrer Schnittstelle, wo jeder Aufrufer ihn liest.

Reguläre Ausdrücke in linearer Zeit

Standard.Regex ist in Hilde geschrieben: ein Parser für die Syntax von RE2, die Thompson-Konstruktion und eine Pike-VM. Kein Muster kann die Suche exponentiell lange dauern lassen. Rückverweise und Lookaround werden abgelehnt. Wer sie braucht, entscheidet sich bewusst für Standard.Pcre, das PCRE2 einbindet.

Vorlagen werden mit dem Programm übersetzt

Vorlagen verwenden die Syntax von Jinja2, aber der Compiler übersetzt sie in Hilde-Anweisungen und prüft sie gegen einen Record-Typ. Zur Laufzeit bleibt von der Vorlage nichts übrig, weder ein Parser noch ein Nachschlagen von Namen. Wo Jinja2 Text stillschweigend weglässt, lehnt Hilde die Vorlage ab.

SQL wird beim Übersetzen geprüft

Jedes @query und @execute wird gegen das Schema geprüft, das die Migrationsdateien aufbauen. SQL, das nicht zu den erlaubten Formen passt, wird mit einer Meldung abgelehnt, statt ungeprüft übersetzt zu werden.

Eine Sandbox muss berichten

Eine Sandbox, die nicht sagen kann, was sie getan hat, ist schlechter als gar keine. Standard.Sandbox gibt deshalb einen Bericht zurück, der genau die Mechanismen nennt, die gegriffen haben. Der Code stammt aus der Sandbox, die für Hiawatha geschrieben wurde. Er wurde Schritt für Schritt in Hilde neu geschrieben, nicht als C übernommen.

Ablehnen statt abstürzen

Schlechte Eingaben ergeben None oder Left mit einer Begründung, aber keinen Programmabbruch. Ein Parser lehnt ab, statt zu raten.

Feste Fehlercodes

Jeder Fehler hat einen Code, der eine bestimmte Stelle im Compiler bezeichnet. Der Wortlaut einer Meldung darf besser werden, der Code bleibt. Verschwindet ein Code aus dem Compiler, wird er nie wieder vergeben.

FastCGI hinter Hiawatha

Ein Hilde-Dienst hängt nicht direkt im Netz. Er spricht FastCGI über einen Unix-Socket, und Hiawatha übernimmt die Verbindungen und TLS. Standard.Fcgi kennt nur das Format der Datensätze und hat keinen eigenen Socket, denn einen Codec, der seine Verbindung selbst besitzt, kann man ohne Verbindung nicht testen.

ML-DSA aus libcrux

ML-DSA kommt aus dem C-Code von libcrux, in der Fassung, die OpenSSH mitbringt, damit OpenBSD kein OpenSSL braucht. Sobald LibreSSL ML-DSA anbietet, wechselt Hilde dorthin. Die zusammengesetzte Signatur aus ML-DSA-44 und Ed25519 gilt nur, wenn beide Teilsignaturen stimmen. Wird eines der Verfahren gebrochen, lässt sich damit allein nichts fälschen.

Raft als Zustandsmaschine

Standard.Raft besitzt weder Netz noch Festplatte noch Uhr, die gehören dem Aufrufer. Dadurch ist jeder Lauf deterministisch und lässt sich mit Netzpartitionen testen, und die Invarianten der Raft-Spezifikation lassen sich als Verträge schreiben.

Datenbanktreiber nur bei Bedarf

Standard.Database ist eine gemeinsame Schnittstelle für SQLite, SQLCipher, PostgreSQL und MariaDB. Ein Treiber landet nur dann im Programm, wenn sein Modul importiert wird.

Kommandozeilen nach clap

Standard.Arguments folgt der Builder-Form von clap 4. Jede Deklaration gibt einen Schlüssel zurück, und ein Fehler in der Kommandozeile beendet das Programm nicht von selbst. Für die Derive-Form von clap gibt es kein Gegenstück.

Ein eigener Paketmanager

Compiler und Paketwerkzeug sind zwei getrennte Programme. Versionen sind git-Tags und werden mit Minimal Version Selection gewählt. Abhängigkeiten werden unter vendor/ eingecheckt, und signify-Schlüssel werden beim ersten Gebrauch übernommen. Nur get und update greifen aufs Netz zu.

Dokumentation in zwei Sprachen

Die wichtigsten Texte der Dokumentation gibt es auf Englisch und Deutsch, und die Prüfläufe sorgen dafür, dass beide Fehlerkataloge übereinstimmen.