====== Lyx – Arrays, Maps & Datensammlungen ======
Lyx unterscheidet vier Container-Formen. Die Wahl zwischen ihnen ist keine Stilfrage, sondern eine Sicherheits- und Architekturentscheidung: Stack-Arrays sind deterministisch und DO-178C-tauglich, Heap-Arrays sind flexibel aber nicht echtzeitfähig, SIMD-Arrays für numerische Hochleistungsberechnungen, Maps für Schlüssel-Wert-Zugriff.
> **Globale Arrays und Structs tragen seit lyxc 1.0.17C** (#1256, #1299). Auf Modulebene deklarierte Aggregate verhalten sich jetzt wie lokale: ''%%var q: [8]int64;%%'' nimmt einen schreibenden Indexzugriff an, ein Initialisierer (''%%:= [1,2,3,4,5,6,7,8]%%'') bleibt erhalten, und ein Feldzugriff auf einen globalen Struct (''s.x := 3'') wirkt.
>
> Bis 1.0.16x stürzte der schreibende Zugriff ab, der Initialisierer wurde verworfen, und Struct-Felder blieben still auf ''0''.
→ [[lyx_-_programmiersprache:sprache:datentypen|Datentypen]] · [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]] · [[lyx_-_programmiersprache:guides:do-178c|DO-178C]]
----
===== 1. Statische Arrays — [N]T =====
Statische Arrays liegen auf dem **Stack**. Größe und Typ stehen zur Compile-Zeit fest und sind unveränderlich. Das ist die bevorzugte Form für sicherheitskritischen und echtzeitfähigen Code.
==== Syntax ====
// [Größe]Typ — Typ-zuerst-Notation (wie Go/Zig, NICHT wie C)
var buffer: [256]uint8; // ohne Initialisierer
var coords: [3]f64 := [0.0, 0.0, 0.0];
var ids: [16]int64;
// let — unveränderliche Bindung (Werte im Array selbst bleiben änderbar)
let defaults: [4]int64 := [0, 1, 2, 3];
> **Wichtig:** Die Syntax ist ''[N]T'' — Größe in eckigen Klammern **vor** dem Typ. ''T[N]'' (C-Stil) ist in Lyx kein gültiger Typ.
==== Zugriff und Zuweisung ====
var coords: [3]f64 := [0.0, 0.0, 0.0];
coords[0] := 10.5; // Schreiben
coords[1] := coords[0]; // Lesen und Schreiben
var z: f64 := coords[2]; // Lesen
// Bounds-Checking:
// — Konstanter Index außerhalb der Grenzen → Compile-Fehler
// — Variabler Index außerhalb der Grenzen → panic, aber nur mit --runtime-checks
> **Die Laufzeitprüfung hängt am Schalter ''--runtime-checks''.** Ohne ihn liest ein Zugriff außerhalb der Grenzen kommentarlos fremden Speicher: ''a[9]'' auf einem ''[4]int64'' liefert einen beliebigen Wert statt eines Abbruchs. Mit dem Schalter meldet das Programm ''index out of bounds'' und endet mit Exit 1. Für sicherheitskritische Builds gehört er damit zur Pflichtausstattung.
==== Mehrdimensionale statische Arrays ====
> **Mehrdimensionale Arrays tragen seit lyxc 1.0.17C** (#1230). ''%%var m: [3][4]int64;%%'' lässt sich lesen und schreiben (''m[1][2] := 7''). Umgesetzt ist **genau eine** Verschachtelung: die Daten liegen flach (N*M Slots hinter einem ''{cap,len}''-Kopf), ''m[i]'' liefert die Adresse der Zeile, ''m[i][j]'' liest von dort. Eine ganze Zeile zuzuweisen (''m[i] := x'') und eine dritte Dimension werden abgewiesen. Bis 1.0.16x endete **jeder** Zugriff mit Exit 139.
==== Safety-Eigenschaften ====
* Keine Heap-Allokation — kein Fragmentierungsrisiko, kein GC-Druck
* Bounds-Checking bei konstantem Index zur Compile-Zeit
* Bounds-Checking bei variablem Index zur Laufzeit — **nur mit ''--runtime-checks''** (kontrollierter ''panic'')
* Maximaler Stack-Verbrauch statisch berechenbar → kompatibel mit ''@stack_limit''
* Kein ''dispose'' nötig — Stack-Frame wird automatisch freigegeben
// DO-178C DAL-A: Waypoint-Liste als statisches Array
@dal(A)
@flight_crit
@stack_limit(2048)
@wcet(100)
fn FindNearest(wps: [32]Waypoint, n: int64, pos: GeoPoint): int64 {
var best: int64 := 0;
var best_d: f64 := 1.0e18;
for i := 0 to n - 1 do {
var d: f64 := GeoDistance(pos, wps[i].pos);
if (d < best_d) { best_d := d; best := i; }
}
return best;
}
----
===== 2. Dynamische Arrays — array =====
Dynamische Arrays liegen auf dem **Heap** und wachsen automatisch. Intern speichern sie drei Werte (Fat-Pointer): Speicheradresse, aktuelle Länge (''len'') und reservierte Kapazität (''cap'').
==== Syntax und Operationen ====
import std.io;
var list: array := [10, 20, 30];
PrintLn(IntToStr(len(list))); // 3 — len() ist ein Builtin, keine Methode
PrintLn(IntToStr(list[0])); // 10
list[0] := list[0] + 1; // Lesen und Schreiben
list[2] := 999;
list.push(40); // Anhängen
var letzter: int64 := list.pop(); // Entnehmen — liefert 40
dispose list; // Zwingend — sonst Memory Leak
Eine blosse Deklaration ohne ''%%:=%%'' legt seit lyxc 1.0.13 ein leeres Array an — ''push'' darauf funktioniert, und ''len()'' zählt korrekt mit:
var u: array; // ohne Initialisierer
u.push(5);
u.push(7);
PrintLn(IntToStr(len(u))); // 2
PrintLn(IntToStr(u[0])); // 5
Dasselbe gilt für ein Array aus einem Literal: ''len()'' folgt seit lyxc 1.0.15B auch dort dem ''push''.
''len()'' auf einem **leeren** Array-Literal liefert ''0'', ohne dass vorher ein ''push'' nötig wäre. Bis lyxc 1.1.2H legte ''%%:= []%%'' keinen Kopf an und ''len()'' las an Offset 0 eines Nullzeigers — Speicherzugriffsfehler in genau der Zeile, mit der jede einsammelnde Schleife beginnt ([[https://github.com/SEOLizer/LyX-Compiler/issues/1642|#1642]]).
import std.io;
fn main(): int64 {
var a: array := [];
PrintInt(len(a)); // 0
return 0;
}
var l := [1, 2, 3];
push(l, 4);
PrintLn(IntToStr(len(l))); // 4
Die Bereichsprüfung zur Compile-Zeit entfällt für eine solche Variable, sobald sie im selben Rumpf wächst — erkannt an ''push'', ''append'' oder ''pop'', in freier Form (''%%push(a, x)%%'') wie in Methodenform (''%%a.push(x)%%''). Nur bei einer Deklaration mit fester Größe (''%%[N]T%%'') bleibt die vermerkte Größe die Schranke.
> **Dynamische Arrays taugen seit lyxc 1.0.17C als Funktionsparameter** (#1197). ''fn F(a: array<int64>): int64 { return a[0]; }'' liefert den Elementwert; bis 1.0.16x kam dort eine Adresse an, also eine wechselnde Zahl. Dasselbe galt für Feldzugriffe und Verkettungen — auch die liefern jetzt Werte statt Adressen.
==== Felder eines Arrays ====
An einem Array sind genau vier Feldzugriffe gültig:
^ Zugriff ^ Bedeutung ^
| ''a.length'', ''a.len'' | Anzahl der Elemente |
| ''a.cap'' | Kapazität |
| ''a.data'' | Zeiger auf das erste Element (mit ''peek64'' lesbar) |
Jeder andere Feldname wird seit lyxc 1.0.15B mit ''unknown field'' abgewiesen — ein Tippfehler wie ''list.laenge'' ist damit nicht mehr von einem gültigen Feld zu unterscheiden, sondern ein Übersetzungsfehler. Dasselbe gilt an eingebauten Grundtypen: ''%%n.egal%%'' an einem ''int64'' meldet ''unknown field''.
> **''list.len()'', ''list.cap()'' und ''list.clear()'' als //Methoden// gibt es weiterhin nicht** — der Aufruf meldet ''undefined function''. Die Länge liefert das Builtin ''len(list)'' oder das Feld ''list.len''.
==== dispose mit mehreren return-Pfaden ====
Verlässt eine Funktion über mehrere Pfade, muss ''dispose'' **vor jedem** ''return'' stehen:
fn LoadConfig(path: pchar): bool {
var cfg: Map := {};
if (ParseFile(path, cfg) == false) {
dispose cfg; // Auch im Fehlerfall freigeben
return false;
}
ApplyConfig(cfg);
dispose cfg;
return true;
}
> Lyx hat kein automatisches RAII/Destruktor-System für Heap-Typen. Bei mehreren ''return''-Pfaden statische Arrays ''[N]T'' bevorzugen — kein ''dispose'', kein Leak-Risiko.
==== Wann array sinnvoll ist ====
* Anzahl der Elemente zur Compile-Zeit unbekannt
* Server- und Tool-Code, bei dem Flexibilität wichtiger ist als deterministisches Timing
* **Nicht** in DAL-A/B-Regelschleifen — Heap-Allokation hat nicht-deterministische Latenz
> In Funktionen mit ''@flight_crit'' warnt der Compiler bei Heap-Allokation. Ausschließlich ''[N]T'' verwenden.
----
===== 3. Parallele Arrays — parallel Array =====
Parallele Arrays sind Heap-Arrays mit garantiertem **SIMD-Alignment** (16 Byte für SSE/NEON, 32 Byte für AVX). Batch-Operationen auf diesen Arrays übersetzt der Compiler direkt in Vektor-Instruktionen.
var data: parallel Array := parallel Array(1024);
data[0] := 1.0;
data[1] := 2.0;
// Batch-Skalierung — compiler übersetzt in SSE/AVX/NEON
// data := data * 2.0;
dispose data;
==== Einsatzgebiete ====
* Signalverarbeitung (FFT, Filter, Fensterfunktionen)
* Lineare Algebra (Matrix-Vektor-Produkt)
* Machine-Learning-Inferenz (Dot-Produkte, Aktivierungen)
* Physik-Simulationen (Partikelsysteme, Kraftfelder)
* Nur für primitive Typen: ''f32'', ''f64'', ''int32'', ''int64''
* Kein Einsatz in DAL-A/B-Regelschleifen — SIMD-Timing nicht vorhersehbar genug
----
===== 4. Maps — Map =====
Maps erlauben den Zugriff über einen Schlüssel. Lyx bietet native Literal-Syntax seit v0.5.0; Lesen und Schreiben über ganzzahlige Schlüssel sind seit [[https://github.com/SEOLizer/LyX-Compiler/issues/1152|#1152]] umgesetzt und mit ''lyxc 1.1.11B'' nachgemessen (''%%m[3] := 7%%'' → ''%%m[3]%%'' ergibt ''7''). Eine Iteration über Schlüssel-Wert-Paare gibt es weiterhin nicht.
> **Nur ganzzahlige Schlüssel.** Die Laufzeit vergleicht den Schlüssel als Zahl. Bei ''pchar'' wäre das die //Adresse// — zwei gleich geschriebene Literale haben verschiedene Adressen, ''%%m["Alpha"]%%'' fände seinen eigenen Eintrag also nicht wieder. ''Map<pchar, V>'' wird deshalb bereits bei der Deklaration abgewiesen, statt still danebenzugreifen. Für Zeichenketten-Schlüssel den Schlüssel selbst hashen (''[[lyx_-_programmiersprache:units:hash|std.hash]]'') und den Hash als ganzzahligen Schlüssel führen. (Inhaltsvergleich ist als [[https://github.com/SEOLizer/LyX-Compiler/issues/1291|Issue #1291]] aufgesetzt.)
==== Erstellen, Lesen, Schreiben ====
import std.io;
var scores: Map := {1: 100, 2: 250, 3: 175};
// Lesen — ein fehlender Schlüssel liefert 0
var s: int64 := scores[1]; // 100
// Schreiben / Einfügen
scores[4] := 300;
// Existenzprüfung mit in — immer vor Zugriff auf unbekannte Schlüssel
if (4 in scores) {
PrintLn(IntToStr(scores[4])); // 300
}
// Anzahl der Einträge
PrintLn(IntToStr(len(scores))); // 4
dispose scores;
Eine Deklaration ohne Initialisierer legt eine leere, sofort benutzbare Map an:
var m: Map;
m[1] := 5;
PrintLn(IntToStr(len(m))); // 1
> **''delete m[k]'' und ''for key, val in m do'' gibt es nicht.** Beide sind im Parser nicht umgesetzt; ''delete'' meldet ''undefined symbol''. Einen Eintrag „entfernt" man ersatzweise durch Überschreiben mit einem Sentinel-Wert, und iteriert über die Schlüssel, indem man sie zusätzlich in einem ''array<int64>'' mitführt.
> ([[https://github.com/SEOLizer/LyX-Compiler/issues/1152|Issue #1152]])
==== Performance-Modell ====
Die Laufzeit ist eine Hashtabelle mit **offener Adressierung**: Sondiert wird ab ''key & (cap-1)''. Zugriff damit O(1) amortisiert, unabhängig von der Anzahl der Einträge.
----
===== 5. Speicherverwaltung — Heap-Container =====
Alle Heap-Container (''array<T>'', ''parallel Array<T>'', ''Map<K,V>'') müssen explizit freigegeben werden:
fn ProcessData(): void {
var buf: array := [];
var data: parallel Array := parallel Array(256);
var meta: Map := {};
// ... Verarbeitung ...
dispose buf;
dispose data;
dispose meta;
}
Wird ''dispose'' vergessen, entsteht ein Memory Leak — besonders in Langzeit-Systemen (Avionik, Server) kritisch.
----
===== 6. Zusammenfassung & Safety-Matrix =====
^ Typ ^ Syntax ^ Ort ^ Timing ^ DO-178C DAL-A ^ dispose ^
| Statisches Array | ''[N]T'' | Stack | Deterministisch | ✅ | Nein |
| Dynamisches Array | ''array<T>'' | Heap | Nicht-deterministisch | ✗ (nur Init) | Ja |
| Paralleles Array | ''parallel Array<T>'' | Heap (SIMD) | Nicht-deterministisch | ✗ | Ja |
| Map | ''Map<K, V>'' | Heap | Nicht-deterministisch | ✗ | Ja |
^ Eigenschaft ^ ''[N]T'' ^ ''array<T>'' ^ ''parallel Array<T>'' ^ ''Map<K,V>'' ^
| Größe zur Compile-Zeit | Ja | Nein | Nein | Nein |
| Bounds-Checking | Compile + Runtime | Runtime | Runtime | Runtime |
| @stack_limit-kompatibel | Ja | Nein | Nein | Nein |
| SIMD-Vektorisierung | Nein | Nein | Ja | Nein |
| Schlüssel-Zugriff | Nein | Nein | Nein | Ja — nur ganzzahlige Schlüssel |
| for key, val in | Nein | Nein | Nein | Nein — ''for key, val in m'' endet mit ''Parse error: expected :=, got ','' (mit 1.1.11B nachgemessen) |
Eine Iteration über die **Elemente** eines Arrays (''for v in a do'') gibt es nicht — der Versuch meldet ''for ... in erwartet einen Bereich''. Über ein Array läuft man mit Index:
for i := 0; i < len(a); i++ { PrintLn(IntToStr(a[i])); }
Für Zählschleifen stehen daneben ''for i := 0 to N'', ''for i in range(N)'' und seit lyxc 1.0.14 auch ''for i in 0..N'' zur Verfügung — letzteres mit **einschließender** Obergrenze.
----
Letzte Aktualisierung: 2026-08-19 — ''len()'' auf dem leeren Array-Literal ([[https://github.com/SEOLizer/LyX-Compiler/issues/1642|#1642]]) ergänzt; Beispiel mit ''lyxc 1.1.3I'' übersetzt und ausgeführt.
Vorherige Aktualisierung: 2026-08-11
Codebeispiele geprüft: gegen **lyxc 1.2.5C** übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die ''pub fn''-Signaturen in ''aurum/std'' gehalten, 0 Abweichungen).