struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
println!("{}", user1.email);
user1.email = String::from("anotheremail@example.com");
}
Structs
Was sind Structs
Structs kennt man ja aus C/C++. Man kann es (denke ich) auch mit JavaScript Objekten vergleichen.
In Structs gruppiert man zusammengehöriges Zeug und hat so eine Art Pseudo-OOP. Man kann damit neue Datentypen machen.
How to
"Normale" Structs
Hinweis: Es können nicht einzelne Felder mutable sein, sondern wenn dann immer das ganze Struct.
Dinge wie in Javascript
Wenn die Variable heißt wie das Feld, kann man auch statt email: email
einfach nur email
schreiben.
Wenn man ein neues Struct aus einem alten mit Updates erstellen will, geht das auch mit einer Art Spread-Parameter:
let user2 = User {
email: String::from("another@example.com"),
..user1
};
..user1
muss als letztes kommen und füllt dann alle bisher nicht gesetzten Felder.
Außerdem ist das etwas tricky:
Wenn die Daten, die von user1
zu user2
übertragen werden, gemoved werden (sprich: keine primitiven Datentypen sind), dann ist user1
danach ungültig.
Hätten wir jetzt auch noch einen neuen username
gesetzt (auch ein String) und nur active
und sign_in_count
übertragen, wäre user1
noch gültig.
Tupel Structs
struct RGBColor(u8, u8, u8);
fn main() {
let black = Color(0, 0, 0)
}
Sind nutzbar wie Tupel (destrucuture und .index
zum Zugriff auf Werte), allerdings eben ein eigener Typ.
Unit-Like Structs
struct AlwaysEqual;
Ein Struct muss keine Felder haben. Das Buch meint, man könnte für diesen Datentypen jetzt noch Traits implementieren, aber davon habe ich noch keine Ahnung. Nur dann macht diese Art von Struct irgendwie Sinn.
Ownership der Felder
Im ersten Beispiel wird String
satt &str
genutzt.
Wir wollen am besten im Struct keine Referenzen, oder es müssen "named lifetime parameter" sein, etwas das wir erst später lernen.
Der Compiler wird sonst streiken.
Das erste Mal Traits
Im Buch folgt ein Beispielprogramm für ein Struct, das ein Rechteck abbildet.
Wir wollten das ganze printen (mit {}
als Platzhalter), allerdings implementiert Das Rechteck nicht std::fmt::Display
.
Das scheint eine Art toString()
für Rust zu sein.
Es gibt aber noch eine andere Möglichkeit und das haben wir schonmal für Tupel genutzt:
{:?}
als Platzhalter (bzw. {:#?}
für pretty print).
Dafür brauchen wir aber das Trait Debug
.
Zum Glück scheint das aber einfach zu implementieren sein, es muss nur implementiert werden.
Der Compiler schlägt uns zwei Varianten vor:
-
#[derive(Debug)]
über der Definition des Structs -
impl Debug for Rectangle
manuell
Jetzt können wir Variablen dieses Typs printen und es zeigt uns Datentyp und Felder an.
Alternativ kann man auch das Makro dbg!(…)
nutzen.
Das wird dann auf stderr
geprintet.
Man kann sogar ein Statement da rein packen (also zum Beispiel 30 * x
) und bekommt das Statement mit dem Ergebnis geprintet, wobei das Ergebnis (als Wert, nicht Referenz) auch zurückgegeben wird.
Funktionen in Structs
Unser Struct soll jetzt auch eine Funktion auf sich selbst aufrufen können. Tatsächlich ist der sehr einfach und sehr OOPig.
Die folgenden Beispiele sollten relativ viel erklären:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Das ist eine Methode/"method"
// Erster Parameter ist &self (/&mut self) und wird aufgerufen wie folgt:
// var.area();
fn area(&self) -> u32 {
self.width * self.height
}
// Das ist eine "associated function"
// Kein &self und aufgerufen wie folgt:
// Rectangle::square(5);
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
// Mehrere impl Blöcke sind erlaubt
impl Rectangle {
// var.has_same_area(&other);
fn has_same_area(&self, other: &Rectangle) -> bool {
self.area() == other.area()
}
// Rectangle::same_area(&first, &second);
fn same_area(first: &Rectangle, second: &Rectangle) -> bool {
first.area() == second.area()
}
// Methoden können auch wie Felder heißen
fn width(&self) -> bool {
self.width > 0
}
}
fn main() {
let rect1 = Rectangle {
width: 12,
height: 3,
};
let rect2 = Rectangle::square(6);
println!("{}", rect1.area()); // 36
println!("{}", rect2.area()); // 36
println!("{}", rect1.has_same_area(&rect2)); // true
println!("{}", rect2.has_same_area(&rect1)); // true
println!("{}", Rectangle::same_area(&rect1, &rect2)); // true
}
&mut self
Eine Methode kann auch &mut self
als ersten Parameter haben.
Dann können auch Felder geschrieben werden. In diesem Fall werden Referenzen aber invalidiert!
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn change_width(&mut self, width: u32) {
self.width = width;
}
}
fn main() {
let mut rect1 = Rectangle {
width: 12,
height: 3,
};
let ref1 = &rect1;
rect1.change_width(5);
println!("{}", ref1.width); // <- geht nicht!
}