Lyx – Variablen

Eine Variable in Lyx ist immer drei Entscheidungen zugleich: Speicherklasse (darf sie sich ändern?), Typ (was steht drin?) und Ort (lokal oder auf Modulebene?). Diese Seite geht den Lebenszyklus durch — Deklaration, Initialisierung, Gültigkeit, Lebensdauer — und sammelt die Stellen, an denen der Compiler heute anders reagiert, als man erwartet.

Syntax-Referenz · Datentypen · Memory Management · Module & Importe

Alle Beispiele geprüft mit lyxc 1.0.17K.


1. Aufbau einer Deklaration

<Speicherklasse> <Name> [: <Typ>] [:= <Startwert>] ;

var counter: int64 := 0;      // vollständig ausgeschrieben
var counter := 0;             // Typ abgeleitet
var counter: int64;           // ohne Startwert — wird 0

Der Name folgt [A-Za-z_][A-Za-z0-9_]* und unterscheidet Groß- und Kleinschreibung. Das Semikolon ist Pflicht.

Reihenfolge zählt. Eine lokale Variable existiert erst ab ihrer Deklaration:

PrintLn(IntToStr(spaeter));   // sema error: undefined symbol 'spaeter'
var spaeter: int64 := 5;


2. Die vier Speicherklassen

Schlüsselwort Wert änderbar? Wann festgelegt Typischer Einsatz
var ja zur Laufzeit alles, was sich ändert: Zähler, Puffer, Zwischenergebnisse
let nein einmalig bei der Deklaration benannte Zwischenwerte, die nicht mehr wandern sollen
co nein einmalig bei der Deklaration Schwellwerte und Grenzen, die erst zur Laufzeit feststehen
con nein zur Übersetzungszeit Puffergrößen, Grenzwerte, Magic Numbers

unit main;

import std.io;
import std.string;

con BUFFER_SIZE: int64 := 4096;       // Compile-Zeit-Konstante

fn main(): int64 {
    var counter: int64 := 0;          // wird verändert
    let name: pchar := "Lyx";         // einmalig gesetzt
    co  max_retries: int64 := 5;      // zur Laufzeit unveränderlich

    counter := counter + 1;

    PrintLn(name, ": ", IntToStr(counter));
    PrintLn("Puffer: ", IntToStr(BUFFER_SIZE));
    PrintLn("Versuche: ", IntToStr(max_retries));
    return 0;
}

Lyx: 1
Puffer: 4096
Versuche: 5

Der Unterschied ist nicht kosmetisch — der Compiler weist jeden Schreibversuch ab:

Versuch Meldung
Zuweisung an let oder co assignment to let/co binding not allowed 'a'
Zuweisung an con assignment to con constant not allowed 'A'

con verlangt einen Ausdruck, der beim Übersetzen ausrechenbar ist. Andere con-Werte dürfen einfließen, Funktionsaufrufe nicht:

con A: int64 := 10;
con B: int64 := A * 2;      // 20 — erlaubt

fn f(): int64 { return 5; }
con C: int64 := f();        // Parse error: function call not allowed in const expression

Faustregel: var nur, wo sich wirklich etwas ändert. Jede Variable, die als let, co oder con deklariert ist, kann später niemand versehentlich überschreiben — und der Compiler prüft das, statt es nur zu dokumentieren.


3. Startwerte

Ohne := ist eine Variable mit Null vorbelegt — 0 bei Zahlen, false bei bool, Adresse 0 bei pchar. Es gibt keinen „undefinierten„ Zustand wie in C:

var u: int64;
PrintLn(IntToStr(u));       // 0

Für let und co ist das eine Sackgasse: sie lassen sich nur bei der Deklaration belegen. let a: int64; übersetzt zwar, ergibt aber einen Slot, der für immer 0 bleibt — eine spätere Zuweisung wird abgewiesen. Deshalb: let und co immer mit Startwert schreiben.


4. Typangabe oder Ableitung

Fehlt der Typ, leitet der Compiler ihn aus dem Startwert ab:

var a := 42;        // int64
var b := 3.14;      // f64
var c := true;      // bool
var d := "Text";    // pchar

Typsuffixe am Literal (42i8, 255u8) gibt es nicht — das ist ein Parse-Fehler. Wer eine bestimmte Breite braucht, schreibt sie in die Deklaration: var b: uint8 := 255;.

Lyx kennt keine impliziten Konvertierungen; zwischen Typen wird mit as umgewandelt:

var ganzzahl: int64 := 7;
var kommazahl: f64 := ganzzahl as f64;

 
Zwei Lücken in der Prüfung des Startwertes. Die Regel „keine impliziten Konvertierungen“ gilt nicht überall — zwei Fälle rutschen durch und liefern stillschweigend Unsinn:

^ Deklaration ^ Verhalten ^
| var b := 3.14; var i: int64 := b; | angenommeni enthält 4614253070214989086, das Bitmuster von 3.14 |
| var f: f64 := 7; | angenommenf enthält 0.0 |
| var b: f64 := 3.14; var i: int64 := b; | korrekt abgewiesen: Initialisierung: int64 erwartet, f64 gegeben |
| var i: int64 := 7; var f: f64 := i; | korrekt abgewiesen: Initialisierung: f64 erwartet, int64 gegeben |

Die Prüfung greift also bei explizit typisierten Variablen, nicht aber bei einer abgeleiteten f64 und nicht bei einem ganzzahligen Literal in einer f64-Deklaration. Bis das behoben ist: Fließkommazahlen mit Punkt schreiben (var f: f64 := 7.0;) und beim Übergang zwischen Ganzzahl und Fließkomma immer as setzen. Geprüft mit 1.0.17K.

5. Gültigkeitsbereich

Eine Variable gilt vom Punkt ihrer Deklaration bis zum Ende des Blocks, in dem sie steht. Blöcke sind geschweifte Klammern — Funktionsrümpfe, if-Zweige, Schleifenkörper.

fn main(): int64 {
    var x: int64 := 1;
    if (true) {
        var x: int64 := 2;                       // eigener Block: verdeckt das äußere x
        PrintLn("innen: ", IntToStr(x));
    }
    PrintLn("aussen: ", IntToStr(x));
    return 0;
}

innen: 2
aussen: 1

  • Verdecken über Blockgrenzen ist erlaubt — im inneren Block ist das äußere x nicht erreichbar, danach gilt es wieder unverändert.
  • Im selben Block ist es verboten: eine zweite Deklaration desselben Namens meldet Name im selben Block bereits deklariert 'x'.
  • Auch ein Parameter lässt sich im Funktionsrumpf verdecken — fn f(x: int64) { var x: int64 := 99; … } übersetzt und liefert 99. Lesbar ist das nicht; besser einen anderen Namen wählen.
  • Eine lokale Variable verdeckt eine gleichnamige globale.

Die Laufvariable einer for-Schleife gehört zur Schleife und ist danach weg:

for j := 0 to 2 do { }
PrintLn(IntToStr(j));       // sema error: undefined symbol 'j'


6. Variablen auf Modulebene

Außerhalb jeder Funktion deklarierte Variablen leben für die gesamte Programmlaufzeit und sind in der ganzen Unit sichtbar. Mit pub werden sie exportiert und sind nach dem Import auch aus anderen Units erreichbar — lesend und schreibend:

// m/cfg.lyx
unit m.cfg;

pub var Schwelle: int64 := 7;
pub fn Get(): int64 { return Schwelle; }

// main.lyx
unit main;

import std.io;
import std.string;
import m.cfg;

fn main(): int64 {
    Schwelle := 9;                  // direkter Schreibzugriff von außen
    PrintLn(IntToStr(Get()));       // 9
    return 0;
}

Genau deshalb gilt: pub var vermeiden. Wer den Zustand über pub fn Get() und pub fn Set() kapselt, behält die Kontrolle darüber, wer wann schreibt — und kann später Prüfungen einziehen, ohne alle Aufrufer zu ändern.

Aggregate funktionieren auf Modulebene genauso wie lokal:

type S = struct { a: int64; b: int64; };

var feld: [4]int64 := [1,2,3,4];
var s: S;
var text: pchar := "global";

fn main(): int64 {
    feld[2] := 99;
    s.a := 7;
    // feld[2]=99, s.a=7, text="global"
    return 0;
}

(Bis lyxc 1.0.16x waren globale Arrays und Structs unbrauchbar — Schreibzugriffe stürzten ab oder verpufften. Seit 1.0.17C tragen sie, siehe #1256.)

Startwerte globaler Variablen

Der Startwert einer globalen Variablen muss zur Übersetzungszeit feststehen. Der Wert einer anderen globalen Variablen zählt nicht dazu:

var A: int64 := 10;
var B: int64 := A * 2;
// codegen error: globale Variable 'B': Startwert ist zur Uebersetzungszeit nicht bekannt

Abhilfe: con verwenden (con A: int64 := 10; con B: int64 := A * 2; ist erlaubt) oder den Wert in einer Init()-Funktion setzen.

 
Globale f64 rechnen seit lyxc 1.0.17K richtig — außer als con (#1373). Eine globale var- oder co-Variable vom Typ f64 liefert in jedem Ausdruck ihren Wert: var S: f64 := 85.0; vergleicht und rechnet korrekt.

Offen bleibt genau con: Mit con SCHWELLE: f64 := 85.0; ist 86.0 >= SCHWELLE falsch, und SCHWELLE + 0.0 ergibt 4635681760191971328 — die Konstante kommt als rohes Bitmuster in die Rechnung. Die direkte Ausgabe über PrintF64 stimmt dagegen, der Fehler fällt deshalb leicht nicht auf.

Bis zur Behebung: für Fließkomma-Schwellwerte var oder co statt con nehmen, oder die Konstante zuerst in eine lokale Variable kopieren (var schwelle: f64 := SCHWELLE;). Ganzzahlige Globals sind nicht betroffen. (Issue #1373, geprüft mit 1.0.17K)
 
con nach seiner ersten Verwendung ist seit lyxc 1.0.17K in Ordnung (#1338). Eine con-Deklaration, die erst nach der benutzenden Funktion steht, trägt jetzt ihren Wert — fn f(): int64 { return SPAETER; } con SPAETER: int64 := 42; liefert 42. Bis 1.0.17I setzte der Compiler dort kommentarlos 0 ein.

Die Empfehlung bleibt: con-Deklarationen an den Anfang der Unit — das liest sich besser und hängt an keiner Compiler-Fassung.

7. Lebensdauer und Speicherort

Deklaration Liegt Lebt Aufräumen
var / let / co in einer Funktion Stack bis zum Ende des Blocks automatisch
var auf Modulebene Datensegment Programmlaufzeit entfällt
con im Maschinencode entfällt
array<T>, new T[n], new Klasse() Heap bis dispose von Hand
alloc(n) Heap bis free(ptr, size) von Hand

Eine Stack-Variable verschwindet mit ihrem Block. Wer ihre Adresse über das Blockende hinaus weiterreicht, greift auf fremden Speicher zu — Lyx hat keine Lebensdauer-Prüfung, die das verhindert. Details: Memory Management und Rohspeicher.


8. Praxisregeln

Situation Empfehlung
Wert ändert sich var
Zwischenergebnis, das stehen bleiben soll let
Grenze oder Schwelle, erst zur Laufzeit bekannt co
Puffergröße, Magic Number, Protokollkonstante con — ganz oben in der Unit
Fließkomma-Schwellwert lokal halten (siehe Kasten oben)
Zustand, den andere Units brauchen pub fn Get()/Set() statt pub var
Größe steht beim Übersetzen fest, unter ~100 KB Stack: var puffer: [N]T;
Größe erst zur Laufzeit bekannt Heap: array<T> oder alloc — mit passendem Aufräumen

9. Fallstricke auf einen Blick

  • let/co ohne Startwert bleiben für immer 0 — der Compiler meldet das nicht.
  • Ganzzahliges Literal in einer f64-Deklaration (var f: f64 := 7;) ergibt 0.0. Immer 7.0 schreiben.
  • Abgeleitete f64 in einer int64-Deklaration wird angenommen und liefert das Bitmuster.
  • Globale f64-Konstante (con) rechnet mit ihrem Bitmuster; globale var/co sind seit 1.0.17K in Ordnung (#1373).
  • Globaler Startwert aus einer anderen globalen Variablen wird abgewiesen — con nehmen oder in Init() setzen.
  • pub var erlaubt jeder importierenden Unit den Schreibzugriff.
  • Verdecken ist über Blockgrenzen erlaubt und im selben Block verboten — wer denselben Namen zweimal braucht, sollte einen besseren suchen.

Weiterführend: Datentypen · Syntax-Referenz · Memory Management · Module & Importe

Letzte Aktualisierung: 2026-08-13 · alle Beispiele geprüft mit lyxc 1.0.17K; con-Vorwärtsreferenz (#1338) behoben, globale f64 nur noch als con betroffen (#1373)