Reflexionen über die Programmiersprache Rust 21

in #deutsch4 days ago

*komplexe Datentypen
Enums

So, jetzt bin ich an einem Punkt angelangt, wo mir das Erklären schon etwas schwer fällt. Enums sind ein Thema, wo ich sinngemäß verstehe was sie machen, mir aber nicht automatisch Enums einfallen, wenn ich sie brauchen könnte. Diese Reflexionen habe ich auch dazu angefangen, damit ich hoffentlich mich in diesem Bereich etwas erweitere. Also, was sind diese Enums und was machen sie? Als Erstes gelten Enums als Aufzählungen. Daraus ergibt sich schon mal, dass man mehrere Elemente hineinpacken kann. So viel sei schon gesagt, Enums haben da keine Beschränkung, sie sind beliebig erweiterbar. Die Zweite wichtige Sache sind Zustände, oder nennen wir sie Stati.

Aber was macht man damit? Für mein Verständnis, bezeichnen Sie ein Objekt, welches verschiedene Zustände aufweisen kann. In einem klassischen Beispiel, verwendet man gerne eine Ampel zur Veranschaulichung. Die (bekannten) Zustände einer Ampel können sein, "rot", ,"gelb", "grün", "blinkt gelb", "blinkt grün" und vielleicht noch "defekt". Theoretisch könnte rot ebenfalls blinken, aber lassen wir das mal aus. Das Enum selbst ist eigentlich schnell gebaut, aber der Zugriff darauf, wirkt etwas knifflig.

enum Ampel{
    Rot,
    Gelb,
    Grün,
    BlinktGelb,
    BlinktGrün,
    Defekt,
}

fn main(){

}


Schauen wir uns die wichtigen Teile an.

-1.Das Enum befindet sich außerhalb vom main().
-2. Alle Elemente werden mit Beistrichen getrennt, wobei das Letzte einen Beistrich haben kann, aber nicht muss.
-3. Es gibt eine Konvention(also eine Empfehlung, aber keine Pflicht) Enums in CamelCase zu schreiben,dh. Großbuchstaben am Anfang und jedes weitere Wort ebenfalls mit einem Großbuchstaben anzufangen.

Das ist schön und gut, aber derzeit noch nutzlos. Es wäre toll, wenn diese Zustände der Ampel auch irgendetwas tun würden. Ab hier kommen 2 weitere Punkte ins Spiel. Zuerst die Funktionen; Wenn in der Funktion nicht alle Elemente gelistet sind, die im Enum vorkommen, gibt es einen AUFSCHREI!! Das Enum gilt also als eine Art Kontrollinstanz, die vorher definiert, was dann alles dabei sein soll. Da mag sich der freie Geist beschränkt fühlen, es hat aber durchaus auch seine Berechtigung. Mit dieser Form von Vorgehensweise, lässt es sich besser Schritt für Schritt durchdenken. Bisschen wie eine Zutatenliste, noch ohne genauere Angaben. So, bevor ich mich ins Schwafeln vertiefe, vertiefen wir uns genauer mit dem Aufbau von diesen Funktionen, die bei Enums verwendet werden.

fn ampel_status(status : Ampel){
    match status{
        Ampel::Rot => println!("Die Ampel ist rot, stehenbleiben!"),
        Ampel::Gelb => println!("Die Ampel ist gelb, (noch) stehenbleiben!"),
        Ampel::Grün => println!("Die Ampel ist grün. Los gehts!"),
        Ampel::BlinktGelb => println!("Die Ampel blinkt gelb. Vorsicht!"),
        Ampel::BlinktGrün => println!("Die Ampel blinkt grün. Stehenbleiben, oder Kreuzung überqueren!"),
        Ampel::Defekt => println!("Die Ampel ist defekt. Verkehrsregeln beachten!"),

    }
}

Zugegeben, das sieht am Anfang etwas viel aus. Aber gehen wir das Schritt für Schritt durch. Die Funktion ist quasi in ein paar Richtungen verzweigt. Zum einen beschreibt im Parameter eine Variable status die mit einem Doppelpunkt mit dem Enum Ampel verknüpft ist. Die Variable status wird dann weiter als match Anweisung verwendet, wobei der Aufbau immer Bezug zum Enum und der jeweiligen Aufzählung im Enum hat. Also

EnumName::Status

Dann folgt, wie für Matches üblich ein => und dann was passieren soll.
Jede dieser Anweisungen wird auch durch einen Beistrich getrennt.

Ok, jetzt muss man nur noch auf die verschiedenen Elemente des Ampel_Enums zugreifen können. Das gehört in den main() Teil.

fn main(){

    let ampel_blinktgelb=Ampel::BlinktGelb;
    ampel_status(ampel_blinktgelb);
}

Ausgabe

image.png

Was passiert hier? Vorher wird eine Variable erzeugt, die den Inhalt von Ampel::Blinktgelb aus dem Enum speichert. Danach wird die Funktion ampel_status aufgerufen und die Variable ampel_blinktgelb übergeben.
Diese zieht dann die Information aus dem Enum und gibt den vorher gefertigten Text (oder was eben dafür gebaut wurde) aus.

Ein weiterer Punkt, der geradezu auffällig für Rust ist, ist die Möglichkeit bestimmte Datentypen anzuwenden, bzw. diese vorzugeben. Es scheint allerdings hier auf einer freiwilligen Basis zu beruhen, immerhin musste ich es vorher nicht angeben. Das nächste Beispiel habe ich neu aufgebaut, weil es ansonsten ziemlich unübersichtlich wird.

enum Ziffern{
    Eins(u8),
    Zwei(u8),
    Drei(u8),
    Vier(u8),
}

fn ziffern_handling(ausgabe:Ziffern){
    match ausgabe{
        Ziffern::Eins(wert) => println!("Die Zahl ist {}",wert),
        Ziffern::Zwei(wert) => println!("Die Zahl ist {}",wert),
        Ziffern::Drei(wert) => println!("Die Zahl ist {}",wert),
        Ziffern::Vier(wert) => println!("Die Zahl ist {}",wert),
    }
}

fn main(){
    let zahl1 = Ziffern::Eins(10);
    let zahl2 = Ziffern::Zwei(15);
    let zahl3 = Ziffern::Drei(20);
    let zahl4 = Ziffern::Vier(25);
    ziffern_handling(zahl1);
    ziffern_handling(zahl2);
    ziffern_handling(zahl3);
    ziffern_handling(zahl4);
}

Ausgabe
image.png

In dieser Enum-Variante wurden den Elementen des Enums u8 zugewiesen. In weiterer Folge muss dadurch die Match-Anweisung, diesen Wert aufnehmen. In der main-Funktion werden diese Werte vorher deklariert und dann über die Funktion wieder verarbeitet. Soweit mal das Prozedere. Aber ja, ich hätte da wohl auch ein großes Fragezeichen über meiner Stirn. Ich überlege gerade, wie ich das einfacher herunterbrechen kann. Also, es gibt gewisse Verknüpfungen und Bedingungen die man unbedingt beachten muss, sonst funktioniert es nicht.
Sagen wir mal, es gibt 3 Hauptbereiche.

-Das Enum selbst, mit den eingebrachten Elementen.
-Die Funktion die beschreibt, was mit den Elementen im Enum passieren soll.
-Der Zugriff auf die Funktion (nicht das Enum) im main-Bereich, wo die Werte übergeben werden.
*Die Funktion muss alle Elemente im Enum behandeln.
*Im Main muss nicht jeder Teil des Enums(eigentlich der Funktion) behandelt werden, es steht einem quasi die Grundstruktur zur Verfügung.

Ich belasse es jetzt mal damit.
-Eins

enum Ampel{
    Rot,
    Gelb,
    Grün,
    BlinktGelb,
    BlinktGrün,
    Defekt,
}

fn ampel_status(status : Ampel){
    match status{
        Ampel::Rot => println!("Die Ampel ist rot, stehenbleiben!"),
        Ampel::Gelb => println!("Die Ampel ist gelb, (noch) stehenbleiben!"),
        Ampel::Grün => println!("Die Ampel ist grün. Los gehts!"),
        Ampel::BlinktGelb => println!("Die Ampel blinkt gelb. Vorsicht!"),
        Ampel::BlinktGrün => println!("Die Ampel blinkt grün. Stehenbleiben, oder Kreuzung überqueren!"),
        Ampel::Defekt => println!("Die Ampel ist defekt. Verkehrsregeln beachten!"),

    }
}

fn main(){

    let ampel_blinktgelb=Ampel::BlinktGelb;
    ampel_status(ampel_blinktgelb);
    let ampel_grün=Ampel::Grün;
    ampel_status(ampel_grün);
}

Ausgabe

image.png

vom Editor

21_1.png

-Zwei

enum Ziffern{
    Eins(u8),
    Zwei(u8),
    Drei(u8),
    Vier(u8),
}

fn ziffern_handling(ausgabe:Ziffern){
    match ausgabe{
        Ziffern::Eins(wert) => println!("Die Zahl ist {}",wert),
        Ziffern::Zwei(wert) => println!("Die Zahl ist {}",wert),
        Ziffern::Drei(wert) => println!("Die Zahl ist {}",wert),
        Ziffern::Vier(wert) => println!("Die Zahl ist {}",wert),
    }
}


fn main(){

    let zahl1 = Ziffern::Eins(10);
    let zahl2 = Ziffern::Zwei(15);
    let zahl3 = Ziffern::Drei(20);
    let zahl4 = Ziffern::Vier(25);
    ziffern_handling(zahl1);
    ziffern_handling(zahl2);
    ziffern_handling(zahl3);
    ziffern_handling(zahl4);
}

Ausgabe

image.png

vom Editor

21_2.png