C3 lernen
(alloc.dev)- C3 basiert auf der Sprache C und bietet fortgeschrittene Funktionen wie Module, Operator Overloading, Generics und Compile-Time-Ausführung
- Die vertraute C-Syntax bleibt erhalten, ergänzt um Syntax zur Steigerung von Produktivität und Stabilität wie Fehlerbehandlung, defer und foreach
- Durch die Einführung von deklarativen Contracts sowie optionalen Typen und einer Fehlerbehandlungsweise werden Sicherheit und Klarheit verbessert
- Unterstützt werden eine Standardbibliothek, integriertes Build-System und praktische Entwicklungsumgebung mit Features wie temporärer Speicherallokation
- Bei Build, Projekterstellung und Codestruktur gibt es Ähnlichkeiten zur Sprache Zig, wodurch sich neue Experimente im Sprachdesign erkennen lassen
Überblick und Merkmale von C3
Was ist C3?
- C3 ist eine auf der bestehenden Sprache C aufbauende Sprache, die die vertraute Syntax beibehält und zugleich Funktionen bietet, die in C schwer umzusetzen sind, etwa Modulsystem, Operator Overloading, Generics, Compile-Time-Ausführung, Fehlerbehandlung, defer, Value Methods, schrittweise Contracts, Slices, foreach und Unterstützung für dynamische Typen
- Eine Modulstruktur mit Namespaces verhindert Namenskonflikte (imperative Namespaces wie
abc::Context) - Das Hauptziel ist es, Produktivität zu steigern und moderne Systemprogrammierfunktionen sicher bereitzustellen
Sprachmerkmale
Hello-World-Beispiel
- Syntaktisch C sehr ähnlich
- Bei Funktionsdeklarationen muss das Schlüsselwort
fnexplizit verwendet werden - Standardbibliotheksfunktionen wie Ein-/Ausgabe sind leistungsfähig, und auch verschiedene Typen lassen sich direkt ausgeben
foreach-Schleife
- Anders als C wird die foreach-Syntax standardmäßig unterstützt
- Für Iteration per Referenz wird vor dem Variablennamen
&geschrieben (fortgeschrittene Funktion) - break und continue werden unterstützt, ähnlich wie foreach in anderen Sprachen
while-Schleife
- Vor C99 konnten Deklarationen nicht innerhalb der while-Bedingung verwendet werden, in C3 ist eine interne Deklaration möglich
enum und switch-Anweisung
- In switch-Anweisungen wird implizites break unterstützt (gemischte implizite/explizite breaks sind Geschmackssache)
- Mit dem Schlüsselwort
nextcasewird ein klarer Fallwechsel unterstützt (praktisch für die einfache Implementierung von Jump Tables) - Dadurch lässt sich der in Sprachen wie Zig oder C oft komplexe switch-case-Ablauf kompakter steuern
Schlüsselwort defer
- Beim Verlassen eines Scopes werden mit defer reservierte Anweisungen in umgekehrter Reihenfolge ausgeführt, was sichere Ressourcenbereinigung gewährleistet
- Nutzung von defer in Kombination mit
catchundtry(Steuerung des Fehlerbehandlungsflusses)
struct und union
- Innerhalb von struct sind benannte oder anonyme Sub-structs/unions erlaubt, wodurch sich tagged-union-Muster leicht entwerfen lassen
- Die Unterscheidung zwischen Anonymität (doppelte gleichnamige Felder) und Namenskonflikten ist streng definiert
Fehlerbehandlung
- Mit dem Symbol
?werden optionale Typen unterstützt; Fehler und Werteoptionen werden für bessere Nutzbarkeit zusammengeführt - Mit dem Schlüsselwort
catchkann auf leere Zustände (ohne Optional) bzw. Fehler verzweigt werden - Im Gegensatz zu Rust oder Zig ist die Trennung zwischen Fehlern und optionalen Werten schwächer (Vorteil: einfach, Nachteil: geringere Klarheit der Intention)
- Mit dem Operator
!(rethrow) können Ausnahmen weitergereicht werden
Contracts
- Vor- und Nachbedingungen von Funktionen (Require/Ensure) werden zwischen
<* .. *>geschrieben (Bedingungen werden beim Kompilieren geprüft) - Unterstützt sogar Fold-Analyse zur Compile-Time (statische Analyse ist noch nicht implementiert)
struct-Methoden
- Mit Typangabe plus Punktnotation (
Foo.next) werden zugeordnete Methoden aufgebaut; es gibt einen Namespace (auch für Primitive) - Methoden sind für alle Typen erlaubt, darunter struct, union und enum
Makros
- Makros auf Basis von Compile-Time-Evaluierung (Schlüsselwort
macro) - Mit
$für Compile-Time-Parameter, mit#für die Übergabe vor der Auswertung - C-Stil (minimiert verflochtene Makroprobleme, betont AST-Stabilität, Prüfungen mit
@-Präfix usw.) - Typ-Reflection und Compile-Time-Ausführung werden über Makros verarbeitet
Type Properties
alignof, kindof, extnameof, sizeof, typeid, methodsof, has_tagof, tagof, is_eq, is_ordered, is_substructusw.- Geeignet für Metaprogrammierung und Reflection
Base64-/Hex-Literale
- Byte-Sequenzen lassen sich direkt in der Form
b64"..."undx"..."deklarieren - Können durch das eingebaute Makro
$embedersetzt werden (in der Praxis aber selten genutzt)
Primitive Typen
- Verschiedene Basistypen wie int, uint, char (immer unsigned), bool, float, int128/uint128
- Zusätzliche Pointer-/Größentypen wie iptr, uptr, isz, usz (etwas weniger intuitiv)
- Anders als in C ist die Bitgröße garantiert
Sonstiges
- Breiter Funktionsumfang mit Operator Overloading, Struct-Subtyping, Generics, Runtime Dispatch, Typ
anyund Bitfeld-Structs (bitstructs)
Praxisteil: Erfahrungen mit C3
C3 installieren
- Unterstützt werden sowohl Prebuilt-Binaries von der offiziellen Website als auch das direkte Bauen aus dem Quellcode
- LLVM und LLD müssen installiert sein (bei Link-Problemen können die CMake-Flags
-DLLVM_DIRund-DLLD_DIRverwendet werden) - Da in einigen Distributionen LLD-Bibliotheken fehlen, wird empfohlen, die Binaries direkt herunterzuladen
- Der C3-Compiler benötigt die Abhängigkeit libtinfo
Projekt erstellen
- Mit dem Befehl
c3c initwird eine Standard-Ordnerstruktur erzeugt (LICENSE/README.md/project.json/srcusw.) - Grundlegende Projektkonfiguration für Build, Build-Targets und Source-Setup (ähnlich wie bei Zig oder Cargo)
- Die Standarddatei main.c3 ist sehr knapp gehalten (Meinung: gut für neue Nutzer)
Einen Rechner bauen
Design und Ziel
- Zur Übung verschiedener Sprachmerkmale von C3 wie Funktionen, Ein-/Ausgabe, Speicherverwaltung und Schleifen wird ein Recursive-Descent-Parser und die Kernlogik eines Rechners implementiert
- Ziel ist es, Stärken und Unbequemlichkeiten der Syntax, die Intuitivität und die Praxistauglichkeit für produktive Entwicklung direkt zu erfassen
Eingabeverarbeitung
- Mit
@poolwird ein temporärer Allokator (tmem) verwendet; beim Verlassen des Scopes wird Speicher automatisch freigegeben (Arena Allocator) - Unterstützt werden die Standardarten der Speicherverwaltung tmem (temporär) und mem (allgemein), einschließlich des Musters, Allokatoren auf Funktionsebene zu übergeben (Mischung aus Vorteilen von Zig und C)
- In der main-Funktion muss ein Rückgabewert immer explizit angegeben werden (vom Compiler erzwungen)
- Funktionen, deren Rückgabewert ignoriert werden darf, werden mit dem Attribut
@maydiscardmarkiert (verhindert böswilliges Ignorieren)
Implementierung des Tokenizers
- Zerlegt Benutzereingaben in eine Token-Liste
- Nutzt verschiedene Kontrollstrukturen wie List aus der C3-Standardbibliothek, foreach-Syntax sowie switch-case (
nextcase, Kombination aus implizitem/explizitem break) - Bei der Slice-Syntax (beide Endindizes inklusive) und Slices der Länge 0 gibt es Verwirrung; dafür existiert eine separate Syntax zur Längenangabe
- Die Transparenz und Flexibilität der Speicherverwaltung, etwa bei gemischter Nutzung temporärer/allgemeiner Allokatoren, sind stark und anderen Sprachen wie Rust überlegen
Implementierung des Parsers
- Erfahrungsbericht zum direkten Schreiben eines Parsers (ausgelassen)
Fazit und Gesamteindruck
- C3 strebt den Schnittpunkt traditioneller Systemsprachen und modernen Designs an
- Die Sprache wurde unter Studium von Zig, Rust und C entworfen, mit dem Ziel, Leistung und Code-Stabilität zu vereinen
- Besonders hervorstechend sind Modularität, sichere Behandlung von Speicher/Fehlern/Contracts, starke Metaprogrammierung und ein intuitives Build-System
- Die Lernkurve ist für Entwickler mit C-Erfahrung schrittweise gut zugänglich
- Die noch unreife Ökosystem-Unterstützung wie Sprachserver und IDE sowie manche Syntaxentscheidungen mit geteilten Meinungen brauchen Verbesserung
- Für professionelle Low-Level- und Systementwicklung ist C3 als Alternative der nächsten Generation bemerkenswert
Noch keine Kommentare.