Softwareentwickler kommen nie zur Ruhe: Sobald ein zuvor unerreichbar scheinendes Ziel abgehakt ist, beginnt bereits die Arbeit an einem neuen. Die systemorientierte Entwicklung ist dafür ein gutes Beispiel: In diesem Bereich gibt es bereits eine Reihe starker Programmmiersprachen - etwa C, C++, Rust oder Go. Mit der neuen Language Zig kommt nun eine hinzu, die die Vorzüge der eben genannten Sprachen vereinen will - bei vergleichbarer Performance und einer besseren Developer Experience.
Zig wurde im Jahr 2015 von Andrew Kelley als Projekt gestartet und scheint inzwischen eine kritische Masse zu erreichen. Das Ziel ist dabei hochgesteckt: Zig soll die langjährige Herrschaft von C beenden und es sowohl als die maßgebliche Low-Level-Sprache als auch als Standard, an dem andere Sprachen gemessen werden, ablösen.
Zig - ein Ersatz für C?
Zig wird als "Low-Level-Systemsprache" beschrieben, aber was ist das eigentlich genau? Wir haben Loris Cro, VP of Community bei der Zig Software Foundation gefragt, wie er die Programmiersprache definieren würde: "Zig ist eine Allzweck-Programmiersprache, denn obwohl sie gut für die Systemprogrammierung geeignet ist, eignet sie sich auch für Entwicklungsarbeit auf Embedded Devices, für WebAssembly, Games und die meisten anderen Tasks, die normalerweise High-Level-Sprachen vorbehalten bleiben."
Zig ist am einfachsten zu verstehen, wenn man es in Relation zu C setzt - als eine universelle, portable Sprache mit Pointern und ohne Garbage Collection. Heute basiert quasi die gesamte Entwicklungsinfrastruktur auf C - die Sprache bildet unter anderem die Grundlage für andere Programmiersprachen wie Java, JavaScript und Python.
Stellen Sie sich vor, welchen Impact es hätte, eine Programmiersprache zu entwickeln, die wie C ist - nur sicherer, weniger fehleranfällig und leichter zu pflegen. Wenn Zig auf breiter Basis als C-Ersatz angenommen würde, könnte das enorme systemische Vorteile mit sich bringen. Laut Cro konkurriere Zig zwar mit C - dennoch sei es nicht sehr wahrscheinlich, C einfach so zu verdrängen. Stattdessen sei zu erwarten, dass beide Programmiersprachen über einen sehr langen Zeitraum nebeneinander existierten.
Zig - Syntax & Design
Zig ist eine "Close to the Metal"-Programmiersprache, die es Developern ermöglicht, direkt mit dem Systemspeicher zu arbeiten - eine Voraussetzung dafür, Code zu schreiben, der so gut wie möglich auf seinen jeweiligen Task optimiert ist. Sowohl die C-Familie als auch Rust und andere Low-Level-Systemsprachen setzen auf eine direkte Speicherzuweisung. Zig bietet ähnliche Fähigkeiten, zielt aber darauf ab, diese in mehrfacher Hinsicht zu optimieren.
Zig will eine simplere, systemorientierte Sprache sein und es einfacher machen, sicheren und korrekten Code zu schreiben. Außerdem soll die Programmiersprache die Developer Experience verbessern, indem sie die "scharfen Kanten", die bei der Programmierarbeit mit C-ähnlicher Software auftreten, reduziert. Die Funktionen von Zig mögen auf den ersten flüchtigen Blick nicht weltbewegend erscheinen - zusammengenommen ergeben sie jedoch eine Plattform, die für Developer einfacher zu beherrschen und verwenden ist.
Derzeit wird Zig genutzt, um die JavaScript-Runtime Bun.js als Alternative zu Node.js zu implementieren. Jarred Sumner, der Schöpfer von Bun, weiß um die Vorteile der Programmiersprache: "Zig ähnelt C, weist aber bessere Memory-Safety-Funktionen auf und bringt zudem moderne Features wie defer
(ähnlich wie bei Go) und Arbitrary Code mit, der zur Kompilierzeit über comptime
ausgeführt wird. Weil es in Zig zudem sehr wenige Keywords gibt, ist es viel einfacher zu erlernen als beispielsweise C++ oder Rust."
Zig unterscheidet sich von den meisten anderen Sprachen durch einen vergleichsweise geringen Feature-Footprint, der das Ergebnis eines expliziten Designziels ist: Es soll möglichst nur eine offensichtliche Möglichkeit geben, Dinge zu tun. Das haben sich die Verantwortlichen so sehr zu Herzen genommen, dass Zig keinen for
-Loop enthielt. Dieser wurde als unnötige Erweiterung des adäquaten while
-Loop angesehen.
Rust-Experte Kevin Lynagh beschreibt in einem Blogbeitrag, warum er eine Keyboard-Firmware in Zig neu geschrieben hat: "Die Sprache ist so klein und konsistent, dass ich bereits nach ein paar Stunden des Studiums in der Lage war, einfach meine Arbeit zu erledigen." Eine Einschätzung, die C-Entwickler Nathan Craddock in seinem Blogbeitrag nur bestätigen kann. Programmierer wissen die fokussierte Qualität von Zigs Syntax also offensichtlich zu schätzen.
Friendly reminder to all new people interested in trying out Zig: learning a new language is even better when you have access to someone that can help you when you get stuck!
— Zig (@ziglang) March 7, 2021
Join one of the Zig communities and enjoy a comfy on-boarding experiencehttps://t.co/1gFIcf5ktD
Zig - Memory Management
Eine Besonderheit von Zig ist, dass sie die Speicherzuweisung nicht direkt händelt. Es gibt kein malloc
-Keyword wie in C/C++. Stattdessen wird der Zugriff auf den Heap explizit über die Standardbibliothek abgewickelt. Wenn Sie eine solche Funktion benötigen, übergeben Sie ein Allocator
-Objekt. Das hat den Effekt, dass eindeutig angegeben wird, wann die Bibliotheken auf den Speicher zugreifen, während gleichzeitig abstrahiert wird, wie er adressiert werden soll. Welche Art von Allokator angemessen ist, bestimmt Ihr Client-Code.
Weil Speicherzugriff ein Bibliotheksmerkmal wird, werden versteckte Zuweisungen vermieden. Das ist für Echtzeit-Umgebungen und solche mit limitierten Ressourcen ein Segen. Die Memory wird aus der Sprachsyntax herausgelöst und das Handling wird expliziter.
Wenn der Client-Code spezifizieren darf, welche Art von Allokator er an eine API weitergibt, wählt er diese entsprechend der Umgebung, die er targetiert. Das sorgt dafür, dass Library Code offensichtlicher zu verstehen und wiederverwendbar wird. Eine Anwendung kann exakt bestimmen, wann eine von ihr verwendete Bibliothek auf den Speicher zugreift und ihr den für die Laufzeit am besten geeigneten Allokator-Typ (embedded, Server, WASM) übergeben.
Die Zig-Standardbibliothek wird beispielsweise mit einem Basis-Allokator (Page Allocator) ausgeliefert. Dieser fordert folgendermaßen Memory vom Betriebssystem an: const allocator = std.heap.page_allocator
; Zig enthält darüber hinaus auch Sicherheitsfunktionen, um Buffer Overflows zu verhindern und wird mit einem Debug-Allokator ausgeliefert, der Memory Leaks aufspüren soll. Weitere Informationen über verfügbare Allokatoren finden Sie in der offiziellen Zig-Dokumentation.
Zig - Konditionale Kompilierung
Zig verwendet Conditional Compilation, was einen Preprocessor wie in C überflüssig macht. Entsprechend gibt es in Zig auch keine Makros wie in C/C++. Vom Design-Standpunkt aus betrachtet das Entwicklungsteam von Zig die Notwendigkeit eines Preprocessor als Anzeichen für eine Sprachlimitierung, die lediglich grob geflickt wurde.
Anstelle von Makros bestimmt der Compiler von Zig, welche Code-Teile zum Zeitpunkt der Kompilierung evaluiert werden können. Ein if
-Statement wird zum Beispiel (wenn möglich) seinen toten Branch zur Kompilierungszeit eliminieren. Anstatt über #define
eine Konstante zur Kompilierungszeit zu erstellen, ermittelt Zig, ob der const
-Wert auf diese Weise behandelt werden kann - und tut es einfach. Das macht den Code nicht nur einfacher zu lesen, zu schreiben und zu durchdenken, sondern eröffnet auch Optimierungsmöglichkeiten.
Wie Softwareentwickler Erik Engheim in einem Blogeintrag treffend anmerkt, macht Zig Compile-time Computing zu einem zentralen Feature und enthebt es dem Status eines nachgelagerten Gedankens: "Das ermöglicht Entwicklern, generischen Code zu schreiben und sich der Meta-Programmierarbeit zu widmen - ganz ohne Expliziten Support für Generics oder Templates."
Ein charakteristisches Zig-Feature ist das Keyword . Es ermöglicht, Code zur Kompilierzeit auszuführen. So können Entwickler (unter anderem) Types gegen Generics durchsetzen.
Zig - Interoperabilität mit C/C++
Zig bietet ein hohes Maß an Interoperabilität mit C und C++. Aus gutem Grund, wie in der Dokumentation nachzulesen ist: "Derzeit ist es es Fakt, dass C die vielseitigste und portabelste Programmiersprache ist. Jede Sprache, die nicht mit C-Code interagieren kann, riskiert es, bedeutungslos zu werden."
Zig kann C und C++ kompilieren. Außerdem wird es mit libc-Bibliotheken für diverse Plattformen ausgeliefert, die ohne Verknüpfung mit externen Bibliotheken erstellt werden können. Eine ausführliche Diskussion über die Beziehung zwischen Zig und libc finden Sie in diesem Reddit-Thread. Zig-Schöpfer Andrew Kelley legt in einem eigenen Blogbeitrag nahe, dass Zig C nicht nur mit seiner eigenen Syntax ersetzen, sondern C so weit wie möglich "absorbieren" will.
"Zig ist besser als andere C/C++-Compiler, weil er unter anderem von Haus aus Cross-Compilation unterstützt", ergänzt Cro. "Zig kann auch trivial mit C interagieren - man kann C-Header-Dateien direkt importieren - und es ist insgesamt besser als C bei der Verwendung von C-Bibliotheken, eines stärkeren Typsystems und Sprachfunktionen wie Defer sei Dank."
Das Type-System von Zig
Zig ist stark typisiert und nutzt struct
für objektähnliche Semantik, aber keine Klassen und Objekte. Die Sprache unterstützt Generics, wie C++ oder Java und nutzt das anytype
-Keyword, um generische Parameter zu ermöglichen (arg: anytype
). Zig bietet zudem Support für Unions.
Nice summary of the Zig language: pic.twitter.com/vwC3nkmttG
— Michal Ziulek???? (@MichalZiulek) March 13, 2022
Zig - Error Handling
Zig weist ein einzigartiges Error-Handling-System auf. Als Teil einer "avoid hidden control flow"-Designphilosophie greift die Programmiersprache nicht auf throw
zurück, um Ausnahmen zu erzeugen. Stattdessen können Statements und Funktionen bei Bedarf einen Error Type zurückgeben. Der Code kann das Error Object verwenden, um entsprechend zu reagieren - oder den Fehler mit dem Keyword try
übergehen.
Ein Error Union Type weist folgende Syntax auf: <error set type> ! <primitive type>
. Wie das in Aktion aussieht, zeigt folgendes "Hello, world"-Beispiel:
const std = @import("std");
pub fn main() !void {
const stdout = std.io.getStdOut().writer();
try stdout.print("Hello, {s}!\n", .{"world"});
}
Der Großteil dieses Listings ist selbsterklärend. Interessant ist dabei vor allem die !void
-Syntax. Sie besagt, dass die Funktion entweder void oder einen Fehler zurückgeben kann. Das bedeutet: Läuft die Funktion main()
ohne Fehler, gibt sie nichts zurück. Wenn nicht, gibt sie ein Error Object zurück, das den Fehlerzustand beschreibt.
Zig - Toolchain & Testing
Zig beinhaltet auch ein Build-Tool. Als Beispiel könnten wir das eben gesehene "Hello, world"-Beispiel erstellen und mit folgenden Befehlen ausführen:
$ zig build-exe hello.zig
$ ./hello
Hello, world!
Das Build-Tool von Zig arbeitet plattformübergreifend und ersetzt Werkzeuge wie make
und cmake
. Ein Package Manager ist derzeit in Arbeit - Support für Testing ist direkt in die Sprache und den Runner integriert.
LLVM als Basis
Zig wurde ursprünglich auf dem LLVM-Toolset aufgebaut - inzwischen ist dieses aber eine optionale Komponente. Das trägt weiter dazu bei, die Portabilität von Zig zu erhöhen, und sorgt dafür, dass sie sich besser für eigenständige Builds eignet.
Zig - Status Quo
Zu Zig gehört auch eine aktive Discord-Community und ein ausgedehntes GitHub-Ökosystem. Zig nähert sich laut Cro der Produktionsreife: "Zig ist noch nicht in der Version 1.0, deswegen stecken Dinge wie Webdev noch in den Kinderschuhen. Dennoch ist Datenverarbeitung der einzige Zweck, für den ich Zig nicht empfehlen würde. Dafür ist eine dynamische Sprache wie Python oder Julia meiner Meinung nach praktischer."
Folgende Beiträge und Artikel halten mehr Informationen über Zig bereit - und darüber, wie es die Welt der systemorientierten Programmierung aufrüttelt:
(fm)
Dieser Beitrag basiert auf einem Artikel unserer US-Schwesterpublikation Infoworld.