Structs in Rust
Ein Struct (Struktur) fasst zusammengehörige Werte zu einem eigenen, benannten Typ zusammen. Ein Tupel ist ein schnelles Bündel ohne Namen – ein Struct dagegen gibt jedem Feld einen Namen. Code, der damit arbeitet, wird dadurch viel leichter lesbar.
Ein Struct definieren und erstellen
Abschnitt betitelt „Ein Struct definieren und erstellen“struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{} ist {}", alice.name, alice.age);}Ausgabe:
Alice ist 30Vergleich das mal mit einem Tupel ("Alice", 30): Bei einem Struct erklären sich alice.name und alice.age von selbst. Du musst dir nicht merken, dass Feld 0 der Name und Feld 1 das Alter ist.
Ein Struct veränderbar machen
Abschnitt betitelt „Ein Struct veränderbar machen“Wie Variablen sind auch Struct-Instanzen standardmäßig unveränderbar. Willst du ein Feld nach dem Erstellen ändern, schreib mut davor:
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{} ist jetzt {}", alice.name, alice.age);}Verhalten mit Methoden hinzufügen
Abschnitt betitelt „Verhalten mit Methoden hinzufügen“Mit einem impl-Block hängst du Funktionen an ein Struct. Darin steht &self für genau die Instanz, auf der die Methode aufgerufen wurde:
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("Hallo, ich bin {} und {} Jahre alt", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}Ausgabe:
Hallo, ich bin Alice und 30 Jahre altFunktionen im Stil eines Konstruktors
Abschnitt betitelt „Funktionen im Stil eines Konstruktors“Ein verbreitetes Muster ist eine new-Funktion im impl-Block, die dir eine Instanz baut. So muss niemand beim Aufruf jedes Feld von Hand ausschreiben:
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{} ist {}", bob.name, bob.age);}Achte auf das alleinstehende age in Person { name: name.to_string(), age }. Hat eine Variable genau denselben Namen wie das Feld, kannst du dir age: age sparen und einfach age schreiben. Rust nennt das Field Init Shorthand (Kurzschreibweise für die Feld-Initialisierung).
Ein Struct mit derive ausgeben
Abschnitt betitelt „Ein Struct mit derive ausgeben“Versuchst du println!("{:?}", alice) mit dem Struct von oben, kompiliert das nicht. Rust weiß nicht, wie es einen eigenen Typ zum Debuggen ausgeben soll – außer du sagst es ihm. Schreib #[derive(Debug)] über das Struct, und du bekommst das geschenkt:
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}Ausgabe:
Person { name: "Alice", age: 30 }Mit #[derive(...)] lässt Rust häufig gebrauchte Funktionalität für einen Typ automatisch erzeugen. Debug und später auch Clone und andere wirst du ständig auf diese Weise hinzufügen.
Übungen
Abschnitt betitelt „Übungen“1. Methoden für ein Rechteck
Abschnitt betitelt „1. Methoden für ein Rechteck“Schreib ein Struct Rectangle mit den Feldern width und height (u32). Gib ihm zwei Methoden: area, die die Fläche zurückgibt, und is_square, die true zurückgibt, wenn beide Seiten gleich lang sind. Tipp: Beide Methoden lesen die Felder nur, also nehmen sie &self.
Lösung anzeigen
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("breit: Fläche {}, quadratisch? {}", wide.area(), wide.is_square()); println!("Quadrat: Fläche {}, quadratisch? {}", square.area(), square.is_square());}Ausgabe:
breit: Fläche 24, quadratisch? falseQuadrat: Fläche 25, quadratisch? true2. Ein Bankkonto
Abschnitt betitelt „2. Ein Bankkonto“Schreib ein Struct Account mit einem owner (String) und einem Kontostand balance in Cent (u32). Schreib Account::new("Ana"), das den Kontostand bei 0 beginnen lässt. Zahl dann 50 und 25 Cent ein, indem du das Feld änderst, und gib das Konto auf zwei Arten aus: als normalen Satz und mit {:?}. Tipp: Die Variable muss let mut sein, bevor du account.balance ändern kannst, und {:?} braucht #[derive(Debug)].
Lösung anzeigen
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{} hat {} Cent", account.owner, account.balance); println!("{:?}", account);}Ausgabe:
Ana hat 75 CentAccount { owner: "Ana", balance: 75 }new setzt balance: 0 selbst, sodass niemand, der die Funktion aufruft, es vergessen kann. Genau deshalb sind Konstruktor-Funktionen so verbreitet.
3. Eine Liste von Personen
Abschnitt betitelt „3. Eine Liste von Personen“Leg drei Person-Werte in einen Vektor: Ana (34), Ben (16) und Chen (52). Gib den Namen aller aus, die 18 oder älter sind, und danach, wer am ältesten ist. Tipp: Lauf mit for person in &people durch die Liste und merk dir die bisher älteste Person mit let mut oldest = &people[0];.
Lösung anzeigen
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} ist volljährig", person.name); } if person.age > oldest.age { oldest = person; } }
println!("Am ältesten: {} ({})", oldest.name, oldest.age);}Ausgabe:
Ana ist volljährigChen ist volljährigAm ältesten: Chen (52)oldest ist eine Referenz (&Person) in den Vektor hinein, keine Kopie. Es wird also nichts geklont. Jedes Mal, wenn wir jemand Älteren finden, lassen wir oldest einfach auf diese Person zeigen.
Zusammenfassung
Abschnitt betitelt „Zusammenfassung“- Ein Struct bündelt benannte Felder in einem Typ:
struct Person { name: String, age: u32 }. - Eine Instanz erstellst du mit
Person { name: ..., age: ... }. Markiere sie mitmut, wenn du Felder später ändern willst. - Verhalten fügst du mit einem
impl-Block hinzu.&selfsteht für die Instanz, auf der die Methode aufgerufen wurde. - Mit
#[derive(Debug)]kannst du ein Struct per{:?}ausgeben.
Kurzer Check
Abschnitt betitelt „Kurzer Check“Kurzer Check
1. Was ist der Hauptvorteil eines Structs gegenüber einem Tupel?
2. Was ermöglicht dir #[derive(Debug)] an einem Struct?
3. Was bewirkt das alleinstehende age in Person { name: name.to_string(), age }?
Punkte: 0 / 3
Weiterlesen
Abschnitt betitelt „Weiterlesen“- Strukturen definieren und instanziieren – Das Rust-Buch: behandelt auch die Update-Syntax (
..other), Tupel-Structs und Unit-ähnliche Structs. - Beispielprogramm mit Strukturen – Das Rust-Buch: baut ein Rechteck-Programm von einzelnen Variablen über ein Tupel bis zum Struct um – so siehst du, warum Structs helfen.
- Methoden – Das Rust-Buch:
&selfgegenüber&mut self, Methoden mit weiteren Parametern und assoziierte Funktionen wienew. - Structures – Rust by Example (auf Englisch): ausführbare Beispiele für jede Art von Struct, mit einer kleinen Übung am Ende.
- Derive – Rust by Example (auf Englisch): was
#[derive(...)]für dich erzeugen kann, etwaDebug,CloneundPartialEq.