diff --git a/diaries/rust/05 - Structs.adoc b/diaries/rust/05 - Structs.adoc new file mode 100644 index 0000000..f90b226 --- /dev/null +++ b/diaries/rust/05 - Structs.adoc @@ -0,0 +1,171 @@ +:experimental: +:docdatetime: 2022-07-05T17:31:22+02:00 + += Structs + +https://doc.rust-lang.org/book/ch05-00-structs.html[Link zum Buch] + +== 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 + +[source, Rust] +---- +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"); +} +---- + +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: + +[source, Rust] +---- +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 + +[source, Rust] +---- +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 + +[source, Rust] +---- +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: + +1. `#[derive(Debug)]` über der Definition des Structs +2. `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. + +[source, Rust] +---- +struct Rectangle { + width: u32, + height: u32, +} + +impl Rectangle { + // var.area(); + fn area(&self) -> u32 { + self.width * self.height + } + + // 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() + } +} + +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 +} +---- diff --git a/list.json b/list.json index 2a960f8..8b0b2e9 100644 --- a/list.json +++ b/list.json @@ -40,7 +40,8 @@ { "title": "01 - Cargo", "filename": "01 - Cargo"}, { "title": "02 - Higher-Lower-Game", "filename": "02 - Higher-Lower-Spiel"}, { "title": "03 - Cheatsheet", "filename": "03 - Concepts"}, - { "title": "04 - Ownership", "filename": "04 - Ownership"} + { "title": "04 - Ownership", "filename": "04 - Ownership"}, + { "title": "05 - Structs", "filename": "05 - Structs"} ] }, {