In dem Vortrag werde ich das Programm Sudoku-Solver besprechen. Es gibt zwar Lösungsalgorithmen für Sudoku bis hin zu einfachem Brute-Force mit Backtracking, das trotz der 7⋅10²⁷ schnelle Ergebnisse liefert, aber ich wollte die menschlichen Strategien nachprogrammieren.
Aufzeichnung
Vom ersten Vortrag gibt es leider keine Aufzeichnung, aber beim zweiten Vortrag habe ich bei mir lokal aufzeichnen können:
Code-Struktur
- Hauptdatei bei Programmen (cargo new --bin) immersrc/main.rs, bei Bibliotheken (cargo new --lib) immersrc/lib.rs
- Code kann in eigene Module ausgelagert werden, die eigenen Zugriffsbereich
definieren.
- Nur mit pubveröffentlichte Funktionen, Strukturen, Typen u. s. w. sind von außerhalb erreichbar.
- Innere Module haben auf alle äußeren Vollzugriff.
 
- Nur mit 
- Module können in Dateien ausgelagert werden und müssen dann mit mod xy;eingebunden werden.- Datei muss xy.rsheißen.
- Wenn Module wiederum Module verwenden, müssen diese in Verzeichnissen liegen
mod xy { mod yz { … } }liegt in Dateixy/yz.rs
- xy.rsund- xy/mod.rssind äquivalent, ich bevorzuge- xy/mod.rs
 
- Datei muss 
- eingebettete Module sind für Tests nützlich
- mit use …werden die Symbole in den aktuellen Bereich importiert; kann auch innerhalb von Funktionen oder Blöcken passieren- Bibliotheken und Module definieren solche Bereiche use std::io
- spezieller Pfad use super::bezieht sich auf das übergeordnete Modul
- spezieller Pfad use crate::bezieht sich auf die Wurzel des Programms, wenn man (in Makros) nicht weiß, wo man ist
- spezielles Element use …::*für alles
- spezielles Element use …::selffür das Modul selbst, hilfreich beiuse std::io::{self, Read}
- auch Zustände aus Enums können importiert werden
- aus Importen kann wieder importiert werden
 
- Bibliotheken und Module definieren solche Bereiche 
- Funktionen und Datentypen können auch innerhalb von Funktionen definiert werden; Sichtbarkeitsbereich dementsprechend eingeschränkt; manchmal sehr hilfreich für Hilfsfunktionen
- grundsätzlich kann eine Struktur/Funktion/… vor ihrer Definition verwendet werden
Ein-/Ausgabe
- Kommandozeilenargumente mit clap parsen
- Ausgabe:
- auf stdout mit print!()oderprintln!();lnfügt Zeilenumbruch am Ende hinzu
- auf stderr mit eprint!()undeprintln!()
- erstes Argument ist Formatierungsmuster, wobei {}sich auf die Parameter bezieht
- Formatierung innerhalb von {}auch veränderbar, z. B.{:2X}für zweistellig und hexadezimal in Großbuchstaben oder{:-<5}für links ausgerichtet mit 5 Stellen und-als Füllzeichen; Formatdefinition in std::fmt
- typisch ist auch {:?}für Debug-Formatierung,Pathunterstützt z. B. nur diese
- Makros, die beim Compilieren die Formatanweisungen auseinander nehmen
- Ergebnisse der Makros kann man sich mit cargo expand(zuvorcargo install cargo-expand) ansehen
 
- auf stdout mit 
- formatierte Ausgabe in Stringmitformat!("…", …)
- zum Schreiben in Dateien gibt es writeln!(file, "…", …)?
Geltungsbereich von Variablen
- Variablen haben einen Bereich, in dem sie genutzt werden können: Beginn bei
letund Ende am Ende des Blocks oder wenn sie an eine Funktion übergeben werden
- am Ende eines Geltungsbereichs wird der Destruktor aufgerufen, d. h. der Speicher freigegeben
- künstliche Blöcke mit { … }, Vorteil: optisch erkennbar, Nachteil: größere Einrückung
- Ende mit drop(var), Variable wird an drop überführt, die damit nichts macht und am Ende der Funktion wiederum ihr Geltungsbereich endet und somit der Variablenwert vernichtet wird
- Variablenwert lebt weiter, auch wenn er durch Neudeklaration der Variablen nicht mehr erreichbar ⟹ Gefühl von Duck-typing wie in Skriptsprachen - Benutzung des Datentyps Enum
- structsind Datentypen mit mehreren Komponenten
- enumsind Datentypen mit mehreren Zuständen, aber jede Variable hat zu jedem Zeitpunkt nur einen Zustand
- Zustände können auch Werte haben; Größe des Datenobjekts ist die Größe des größten Zustands
- enumäquivalent in C zu einer- struct { zustand; union { … } }
- Beispiele: Option { Some(val), None }undResult { Ok(val), Err(err) }
- in C hat man Magic-Values wie NULLoder-1missbraucht, um zwischenSomeundNonezu unterscheiden
- Fehler werden in C über errnoweitergegeben, was für Parallelität eine Katastrophe ist
- Zustandabfrage mit Pattern matching: match var { Zustand => { … }, _ => () }oderif let Zustand = var { … }
Attribute
- Attribute sind Anweisungen an den Compiler, die die Code-Erstellung beeinflussen
- #[]vor einem Element oder- #![]für das aktuelle Element;- fn …() { #![…] }äquivalent- #[…] fn …() {}; für Module ist Dokumentation sinnvoller innerhalb der (Modul-)Datei, Anweisung für gesamtes Programm in main.rs mit- #!
- typische
Attribute,
Rust-Buch:
- [#[derive(Default,Debug,Clone,PartialEq)]]](https://doc.rust-lang.org/reference/attributes/derive.html): der Compiler erzeugt eine Standardimplementierung für den Datentyp, z. B. Clone, indem jedes Element geclont wird (sofern möglich), oder PartialEq, indem alle Elemente verglichen werden (sofern möglich)
- #[deprecated(since = "5.2", note = "…")]: Kennzeichnung veralteter Funktionen/Datentypen, bei deren Benutzung der Compiler eine Warnung ausgibt
- #[must_use = "…"]: der Compiler warnt über Objekte (z. B. Rückgabewerte), die nicht genutzt werden; wichtig z. B. bei Result oder Iteratoren
- #[allow(unused_variables)],- #[warn(unused_variables)]: Compiler-Warnungen an- und abschalten
- #[macro_use]um alle Makros eines Pakets zu importieren; geht mittlerweile schöner mit- use …::macro;(ohne Ausrufezeichen)
- #[cfg(…)]bedingte Kompilierung des Elements in der Cargo-Konfiguration …
 
- [
- eigene Attribute definierbar
- fast jede Zeile kann ein Attribut haben, aber nicht alle Attribute sind immer möglich/sinnvoll
Datentypen definieren
- type neu = alt
- Enum
- Struct:- mit benannten Attributen { name: type, … }, Zugriff mitobj.name
- unbenannte Attribute (type, …), Zugriff mitobj.0
 
- mit benannten Attributen 
- impl Type { fn …() { … } }: Assoziierte Funktionen- Mod::Type::fn()
Initialisierung
- es gibt keine speziellen Konstruktoren von Objekten, jede Funktion, die alle Attribute eines Objekts beschreiben kann, kann dafür den Speicher reservieren und das Objekt initialisieren.
- Initialisierung mit Type { attr1: val1, … }bzw.Type(val1, …)
- zur Vereinfachung kann man auch überall Selfstatt des Typs verwenden, erleichtert Vorlagen des Editors
- daher Objekterzeugung mal mit new(),with_capacity(),from()
Methoden
- Methoden sind Funktionen eines Objekts; obj.fn()
- Unterschied: erster Parameter ohne Datentyp ist irgendwas mit self, self,&mut self,Box<self>,Rc<self>; self entspricht this in anderen Sprachen
- meist verwendet man &selffür Funktionen, die nicht das Objekt verändern, und&mut selffür Funktionen, die das Objekt verändern,selfkonsumiert das Objekt
- this in anderen Sprachen irgendwie magisch, weil nirgendwo definiert; Python
macht es explizit als ersten Parameter, tatsächliche Assembler-Implementation
ist auch: obj.fn()⇒fn(obj)
- die Methodenaufrufe sind »nur« eine Erleichterung des Compilers (Syntactic
sugar), um nicht jedesmal Typ::fn(obj)schreiben zu müssen und wissen zu müssen, welcher Typ das ist – Überarbeitung des Codes wäre sehr aufwendig
- aber Rust macht's auch möglich, Methoden als assoziierte Funktion aufzurufen:
println!("Results: {} und {}", x.len(), Vec::len(&x))
- es gibt ein paar magische Methoden:
- from()/into(): impl From<Other> for This⟺impl Into<This> for Otherman implementiert nur From und der Compiler erzeugt Into
 
- from()/into(): 
- innerhalb eines Moduls mehrere implfür einen Datentyp möglich
Trait implementieren
TODO
Tests
- Tests müssen mit dem Attribut #[test]gekennzeichnet werden
- es gibt noch die Attribute #[ignore]zum Deaktivieren eines Tests und#[should_panic], wenn ein Test mit einer Panic abbrechen soll
- innerhalb von Tests mit den Makros assert!(…),assert_eq!(…, …)undassert_ne!(…, …)arbeiten, im Fehlerfall lösen diese eine Panic aus und brechen die Methode damit ab
- cargo test -q
- cargo test NAMEAuswahl einiger Test anhand eines Teils (Substrings) des Namens
TODO: sudoku.rs