Timm Behner
- MSc in Informatik Uni Bonn 2016
- Fraunhofer FKIE (Cyber Analysis & Defense) bis 2022
- Datenbanken- & Netzwerk-Forensik
- Infrastruktur-Projekte
- Lehre
- Deutsche Telekom Security
- Kryptographische Software-Systeme
- Teig-Monitor mit Arduino, Thermometer, Abstandssensor und Rust
- Vorkenntnisse
- Erwartungen
- Ziele
| Tag | Slot | Thema |
| — | ------------- | -------------- |
| Sa. | 09:00 - 10:35 | Rust Einführung und Speicherverwaltung |
| Sa. | 10:45 - 12:15 | Rust Generics, Traits und Error-Handling |
| Sa. | 13:00 - 17:00 | Übungen |
| So. | 09:00 - 10:35 | Rust OOP und Funktionale Programmierung |
| So. | 10:45 - 12:15 | Rust Fearless Concurrency und Interoperatibilität |
| So. | 13:00 - 17:00 | Übungen |
- dieser Workshop
- Rust Buch (auch auf deutsch)
- Pakete finden auf crates.io
- Edition Guide
- Cargo Buch
- Rust Cookbook
- Dokumentation der Standard-Bibliothek
- clippy, language-server und rustc
- makerspace-rust-workshop auf github
Belohnung für:
- gefundene Fehler
- beantwortete Fragen
- Reliable: If it compiles, it works
- Performant: Idiomatic code runs efficiently
- Suuportive: the language, tools, and community are here to help
- Productive: a little effort does a lot of work
- Transparent: you can predict and control low-level details
- Versatile: You can do anything with Rust
- Firefox
- Amazon (AWS Firecracker und Bottlerocket)
- Microsoft (Azure – IoT Edge)
- Cloudflares QUIC und Pingora
- Linux
- npm
Rust ist die meist geliebte Programmiersprache in der StackOverflow Nutzerstudie
- seit 7 Jahren
- seit Rust 1.0
- mit fast 20%-Punkten Abstand
- Pingora braucht 70% weniger CPU und 67% weniger Speicher
- https://discord.com/blog/why-discord-is-switching-from-go-to-rust
- Backend Anwendungen (Server-Anwendungen, Microservices, …)
- System-Werkzeuge
- Cryptocurrencies, Blockchains, Blockchain-Anwendungen
- Frontend (Web Assembly)
- Embedded
- Programmiersprachen
- Rustup: Installationsprogramm für Rust und Rust-Komponenten
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shInstallation von src_shell{rustc} und src_shell{cargo}
- Rust aktuell halten
rustup update- Installation von alternativen Toolchains
rustup install nightly
rustup default nightly- IDEs
- IntelliJ Idea (via Plugin)
- Eclipse (via Plugin)
- VSCode (with Plugin)
- Text-Editor und Kommandozeile
- Emacs
- Vim
- VSCode
- Sublime
- … jeder mit LSP-Support?
- Rust Tools Resourcen
- Erstellen einer neuen Anwendung
cargo new hello_rust- Erstellen einer neuen Bibliothek
cargo new --lib greeting// src/main.rs
fn main() {
println!("Hello, world!");
}# Cargo.toml
[package]
name = "hello_rust"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]// src/lib.rs
pub fn add(left: usize, right: usize) -> usize {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}[package]
name = "greeter"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]- Deklaration und Initialisierung
let x: usize = 5; println!("x has value {x}");
- Deklaration mit späterer Initialisierung
let x: usize; x = 5; println!("x has value {x}");
- Array
let scores: [u8; 3] = [1, 2, 3]; println!("Score: {score[0]}"); println!("Score: {score[1]}"); println!("Score: {score[2]}");
- gleicher Datentyp
- feste Kapazität
- Index aus Ausdruck
- alle Variablen haben einen konstanten (unveränderbaren) Wert
let x = 5; x = 6; // cannot assign twice to immutable variable `x` println!("x has value {x}");
- es sei denn, man macht sie veränderbar
let mut x = 5; x = 6; println!("x has value {x}");
- bei Zuweisung
let x = 5; println!("x has value {x}");
- Kontextabhängig
let answer = "42".parse(); // Error: type annotations needed
const PI_FOR_PHYSICS: f64 = 3;- zur Kompilierzeit zugewiesen
- immer src_rust{static}
- müssen explizit typisiert sein
let values: Vec<usize> = Vec::from([1, 2, 3, 4]);
let also_values: Vec<usize> = vec![1, 2, 3, 4];- variable Anzahl an Elementen
- gleicher Datentyp
- Kapazität ≠ Länge
values[0]; // \rightarrow 1
values.len(); // \rightarrow 4
values.capacity(); // \rightarrow 8
values.is_sorted(); // \rightarrow trueModifizierende Operationen nur auf mutable Vec:
let mut values = vec![1, 2, 3];
values.push(4); // \rightarrow [1, 2, 3, 4]
values.extend(vec![2, 1]); // \rightarrow [1, 2, 3, 4, 2, 1]
values.pop(); // \rightarrow [1, 2, 3, 4, 2]
values.sort(); // \rightarrow [1, 2, 2, 3, 4]
dbg!(values);- Erweitern
- Löschen
- Sortieren
let greeting = String::from("Hello, World!");
dbg!(greeting.len()); // \rightarrow 13
dbg!(greeting.to_lowercase()); // \rightarrow "hello, world!"
dbg!(greeting.as_bytes()); // \rightarrow [72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33]
- UTF-8 String
- variable Länge und Inhalt
Modifizierende Operationen auf Strings
let mut greeting = String::from("Hello, ");
greeting.push('W');
greeting.push_str("orld!");
dbg!(greeting);fn say_hello() {
println!("Hello, World!");
}
fn main() {
say_hello();
}fn greet(greeting: String, subject: String) {
println!("{greeting}, {subject}!")
}
dbg!(greet("Hello".into(), "World".into()));fn add_one(x: usize) -> usize {
return x + 1;
}
dbg!(add_one(41));- Anweisungen: Instruktionen die einen Effekt haben, und keinen Wert zurückgeben:
let x = 5;
- Ausdrücke: werden zu einem Ergebniss ausgewertet und können einen Effekt haben:
x + 5; x;
Der letzte Ausdruck einer Funktion ist ihr Rückgabewert
fn add_one(x: usize) -> usize {
x + 1
}aber auf Semikolon achten:
fn add_one(x: usize) -> usize {
x + 1;
// Error - implicitly returns `()` as its body has no tail or `return` expression
}if /*condition*/ {
/*true case*/
} else {
/*false case*/
}- src_rust{if} sind Ausdrücke
let y = 10; let x = if y == 5 { y } else { y + 1 }; dbg!(x);
let number = 49;
if number % 2 == 0 {
println!("{number} is not a prime");
} else if number % 3 == 0 {
println!("{number} is not a prime");
} else if number % 5 == 0 {
println!("{number} is not a prime");
} else {
println!("{number} is probably a prime");
}- Endlos-Schleifen
loop {
println!("Sind wir bald da?");
}- Schleifen sind Ausdrücke
let arr = [1, 3, 4]; let mut counter = 0; let final_value = loop { if arr[counter] % 2 != 0 { break arr[counter] * 2 } counter += 1; }; dbg!(final_value);
let mut counter = 5;
let mut factorial = 1;
while counter > 1 {
factorial *= counter;
counter -= 1;
}
println!("5! = {factorial}")whileist kein Ausdruck, sondern eine Anweisung
Iteration über eine Kollektion zu Iterieren
let a = [1, 2, 3, 4];
for x in a {
println!("{x}")
}- exklusives Intervall
for i in 1..4 {
/* tue etwas mit i*/
}- inklusives Intervall
for i in 1..=4 { /* tue etwas mit i*/ }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_name() {
assert!(true);
}
}- Tests in eigenem Modul
- Test-Funktionen werden annotiert
- src_rust{assert} Macros zum Prüfen von Werten
- src_rust{assert!(false, “This is false!”)} Fehlermeldungen anpassen
- src_shell{cargo test} – alle Tests ausführen
- src_shell{cargo test – –show-output} – Anzeigen der Standard-Ausgabe
- src_shell{cargo test test_name} – Ausführen einzelner Tests
- Ignorieren von einzelnen Tests
#[test] #[ignore] fn ignored_test() { assert!(false); }
- Als Sub-Modul (in der gleichen Datei)
- Als eigenes Modul (gesonderte Datei)
- Als Integration-Tests in gesondertem Verzeichniss
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid- Each value has exactly one owner
- When the owner goes out of scope, the value will be dropped
{
let s = "hello";
let f = s;
println!("{}, {}!", s, f);
}fn greet(name: String) -> String {
let mut greeting = String::from("Hello, ");
greeting.push_str(&name);
greeting.push('!');
greeting
}
fn main() {
let name = "Thorsten".to_string();
let greeting = greet(name);
dbg!(greeting);
}- src_rust[]{String} auf dem Heap
{
let s = String::from("hello");
let f = s; // value moved here
println!("{}, {}!", s, f); // Error -- value borrowed here after move
}Konstante Pointer:
{
let s = String::from("hello");
let f = &s; // -> &String
println!("{}, {}!", s, f);
}Dereferenzieren mit src_rust{*}
let v: usize = 5; // type: usize
let r = &v; // type: &usize
assert_eq!(v, *r);
dbg!(*r);Referenzen mit Schreibe-Zugriff
{
let mut s = String::from("hello");
let f = &mut s;
f.push_str(" there, General Kenobi!");
println!("{f}");
}Alles hat Grenzen!
{
let mut s = String::from("hello");
let f = &mut s;
f.push_str(" there, General Kenobi!");
println!("{}, {}!", s, f); // Error
// error[E0502]: cannot borrow `x` as immutable because it is also borrowed as mutable
}Zu jeder Zeit kann es entweder:
- eine veränderbare (mutable) Referenz
- oder eine beliebige Anzahl von nicht-veränderbaren Referenzen geben
Der Compiler überprüft gültigkeit von Referenzen
fn make_dangling_reference() -> &String {
let o = String::from("foo");
&o // return reference to o
} // o goes out of scope
// Error -- expect lifetime parameterfn make_reference(name: &str) -> &str {
&name
}und explizite Lifetimes
fn make_reference<'a>(name: &'a str) -> &'a str {
&name
}Slices sind eine Referenz auf einen Teil einer Kollektion
- String Slices src_rust{&str}
let s = String::from("Hello, World!"); dbg!(&s[0..5]); dbg!(&s[7..12]);
- General Slices src_rust{&[T]}
let v = vec![1, 2, 3]; dbg!(&v[1..2]);
- alle bisherigen Refernezen hatten eine implizite Lebenszeit
fn get_first(input: &str) -> &str { &input[0..1] }
- explizite Lebenszeit
fn get_first<'a>(input: &'a str) -> &'a str { &input[0..1] }
- manchmal notwendig
fn multiple_lifetimes(first: &str, second: &str) -> &str { first } let v = "I'm on the stack"; { let a = String::from("look, I'm on the heap!"); let result = multiple_lifetimes(v, &a); assert_eq!(result, v); }
- Statisch in der erzeugten Binärdatei
- Gleiche Lebenszeit wie src_rust{main}
const VERSIONNUMBER: &'static str = "v0.0.1"
const DBCONNECTION: &'static Connection = /* ... */- Gruppieren von zusammenhängenden Daten und Kapselung von Daten
struct Point { x: f64, y: f64, desc: String, }
- Instanzen (Objekte) eines src_rust{struct}
let origin = Point{x: 0, y: 0, desc: "origin".to_string()}
- Kopieren
let shifted = Point{x: 1, ..origin}
- Auf Teile (Member) zugreifen mit Punkt-Notation src_rust{origin.desc}
- Structs können Besitzer all ihrer Daten sein
- können aber auch Referenzen haben
struct ExtDescPoint<'a> { x: f64, y: f64, desc: &'a str, }
struct Circle {
radius: f64
}Methoden für src_rust{struct} definieren
- Struct-Methoden
impl Circle { fn get_pi() -> f64 {3.141592} }
- Objekt-Methoden
impl Circle { fn area(&self) -> f64 {2 * 3.1415 * self.radius * self.radius} }
- veränderbare Objekt-Methoden
impl Circle { fn resize(&mut self, new_radius: f64) {self.radius = new_radius} }
- Gruppierung alternativer, fester Werte
enum DataSource { Network, FileSystem, UserInput, }
- jeder Wert kann wiederum Variablen beinhalten
enum DataSource {
Network(IpAddr),
FileSystem(std::path::PathBuf),
UserInput,
}- src_rust{String}
- src_rust{Vec}
- src_rust{Box<T>}
- src_rust{Rc<T>}
- src_rust{RefCell<T>} (und src_rust{Ref} / src_rust{RefMut})
Speicher auf dem Heap
- Rekursive Datentype haben keine bekannte Größe
enum BinTree { Node(BinTree, BinTree), Nil, }
- Performanz bei Funktionsaufrufen
- Trait-Objekte (s. OOP)
Mehrere Owner des gleichen Objektes:
let a = Rc::new(BinTree(Nil, Nil)); // Anzahl der Referenzen 1
let b = BinTree(Rc::clone(a), Nil); // Anzahl der Referenzen 2
{
let c = BinTree(Rc::clone(a), Nil); // Anzahl der Referenzen 3
}
println!("{Rc::string_count(&a)}")// Anzahl der Referenzen 2- src_rust{Rc::clone} erzeugt eine neue Referenz (Pointer)
- src_rust{a} wird erst gelöscht, wenn es keine Referenzen mehr gibt
- src_rust{Rc} ist immer eine src_rust{Box}
- kaum overhead
- Problem: Zirkuläre Referenzen:
- doppelt verkettete Listen
- Referenzen auf Eltern-Knoten in Graphen
- Erzeugung erhöht nicht den Referenz-Zähler
let par = Rc::new(5); let weak_ref = Rc::downgrade(&par);
- Zugriff kann ins Leere laufen!
weak_ref.upgrade() match { Some(r) => {/* */}, None => {/* */} }
- Borrow-Checking-Regeln zur Laufzeit
- neue RefCell erstellen
let msg: RefCell<String> = RefCell::new(String::from("hello"));
- Versuche den Wert zu leihen (borrow) → src_rust{Ref}
{ if let Ok(m) = msg.try_borrow() { // use m here } } // msg not borrowed anymore
- Versuche den Wert veränderbar zu leihen (mutable borrow) → src_rust{RefMut}
if let Ok(m) = msg.try_borrow_mut() { // change m here } // msg not borrowed anymore
let value = Rc::new(RefCell::new("Shared mutable string"));- mehrere Owner von veränderbarem Objekte src_rust{Rc<RefCell<T>>}
- src_rust{Rc::clone} zum Kopieren der Referenz
- src_rust{RefMut} zum verändern der Werte
- Zuerst muss es kompilieren, kümmere dich um Effizienz später
- Verschiebe Werte auf den Heap (String, Vec, Box)
- Kopiere Werte
- Klone Objekte
- Verschiebe Objekte in eine src_rust{Rc}
- Verschiebe Objekte in eine src_rust{RefCell}
In dynamisch typisierten Programmiersprache ist jeder Wert eine src_rust{Rc<RefCell<T>>}!
- Gemeinsame Funktionalität die mehrere Structs teilen
trait Shape { fn size(&self) -> f64; }
- Structs können Traits implementieren
struct Circle { r: f64 } impl Shape for Circle { fn size(&self) -> f64 {2 * 3.141529 * r * r} }
- src_rust{impl} Schlüsselwort
fn is_smaller(first: &impl Shape, second: &impl Shape) -> bool { first.size() < second.size }
- als Trait-Bound (s. Generics)
- Rückgabewerte mit eindeutigem Typen
fn get_shape(r: f64) -> impl Shape { return Circle{r} }
Implementierung von Standardverhalten
impl Named {
fn get_name(&self) -> String
fn say_hello(&self) -> String {
format!("Hello, my name is {}", self.get_name())
}
}- Implementierung wird durch Macro erzeugt
#[derive(Debug)]
struct Person {
name: String
}
let p = Person{name: "Linus"};
println!("{p:?}");
dbg!(p);PartialEq: partielle VergleichbarkeitPartialOrd: partielle OrdnungClone: Tiefe Kopie des Objekts (auch auf dem Heap)Copy: ausschließlich auf dem Stack- https://doc.rust-lang.org/book/appendix-03-derivable-traits.html
fn find(needle: usize, haystack: Vec<usize>) -> usize {
//
}wird zu
fn find<T>(needle: T, haystack: Vec<T>) -> usize {
//
}struct Matrix<T> {
values: Vec<Vec<T>>
}
impl<T> Matrix<T> {
fn get(&self, x: usize, y: usize) -> &T {
&self.values[x][y]
}
}- Container-Datentypen
- Generische Pattern
- src_rust{enums} können auch generische Datentypen haben
enum Option<T> { Some(T), None }
- Entspricht Algebraischen Datentypen aus funktionalen Programmiersprachen
Anforderungen an generische Typen
struct Matrix<T: Default + Clone + PartialEq> {
values: Vec<Vec<T>>
}Beispiel:
- src_rust{Default}: Standardwert
- src_rust{Clone}: Wert kann geklont werden
- src_rust{PartialEq}: Typ ist partiell vergleichbar
fn is_smaller<T: Shape>(first: &T, second: &T) -> bool {
first.size() < second.size
}- 70% of all security bugs are memory safety issues.
- Sowohl in Chrome als auch in Microsoft Produkten
- Zugriffsfehler: Buffer Overflow, Buffer Over-Read, Use After Free, Race Conditions
- Uninitialisierte Werte: Null pointer dereference, Wild Pointers
- Speicher Lecks: Double Free, Invalid Free, Unwanted Aliasing
- Keine Null-Pointer
- Kein uninitialisierter Speicher
- Kein uninitialisierter Pointer
- Keine unchecked Memory-Boundaries
Vermiedene Probleme:
- Buffer Overflows
- Race Conditions
- Invalid Page faults
- Use after free
- Memory Leaks
- Dangling-Pointers/Wild-Pointser
- DDoS
- Injections
- …
enum Color {Red, Green, Blue}
let v = Color::Red;
match v {
Color::Red => println!("this is red")
Color::Green => println!("this is green")
_ => println!("this is some other color")
}- default-Fall
- Kompiler überprüft auf Vollständigkeit
enum Color {RGB(u8, u8, u8), HEX(String)}
let white = Color::RGB(255, 255, 255);
match white {
Color::HEX(value) => println!("hex value with #{value}"),
Color::RGB(r, g, _) => println!("This color is {r} red, {g} green, and some blue"),
}- Bindung an Variablen
- Ownership-Regeln gelten
- src_rust{ref} alternative Schreibweise für Referenzen
let v = 5; let r1 = &v; let ref r2 = v;
- Referenzen in Matching
match white { HEX(ref value) => println!("{value} is a &str now!"), RGB(r, g, _) => println!("This color is {r} red, {g} green, and some blue"), }
- Überprüfung von gebundenen Variablen
let k = Ok(5); match k { Ok(v) if v % 2 == 0 => println!("even result"), Ok(v) => println!("odd result"), Err(_) => println!("some error"), }
- Ranges
let v = 5; match v { 1..=5 => println!("a number between 1 and 5"), _ => println!("some different number"), }
- Alternativen
let c = 'q'; match c { 'q' | 'Q' => println!("quit"), _ => println!("some action"), }
Pattern matchting kann auch in if-Verzweigungen genutzt werden.
let k = Ok(5)
if let Ok(n) == k {
println!("{n}");
}enum Result<T, E> {
Ok(T),
Err(E)
}Fehler behandeln:
use std::fs;
let open_result = fs::File::open("some_file.txt");
let f = match open_result {
Ok(some_file) => some_file,
Err(e) => panic!("Could not open file!"),
};let r = Ok(0);
r.is_ok(); // -> true
r.is_err(); // -> false
r.unwrap(); // -> 0
r.unwrap_or_default(5); // -> 0
r.unwrap_or_else(get_some_value()); // -> 0- Ergebniss überprüfen
- Wert extrahieren
- mehr in Funktionale Programmierung
- Frühzeitiges terminieren der Funktion mit src_rust{match}
let file = match std::fs::File::open("some_file.txt") { Ok(f) => f, Err(e) => return Err(e) };
- viel Boilerplate-Code
- Fragezeichen-Postfix-Operator
fn read_file(filename: &str) -> Result<String, std::io::Error> { let mut file = std::fs::File::open(filename)?; let mut out: String = String::new(); file.read_to_string(&mut out)?; Ok(out) }
- Rückgabewert muss passen
- src_rust{panic}-Macro zum Beenden des Prozesses
panic!("that was not okay!");
- src_rust{Result} in src_rust{panic} umwandeln
let v: Result<String, String> = Ok("Ok...".into()); let r: Result<String, String> = Err("This is wrong!".into()); let correct_string: String = v.expect("a valid string"); r.expect("That this blows up!"); // KABOOM!
- src_rust{lhs.or(rhs)}
method lhs rhs output ------ ------ --------- ------ or Ok(x) (ignored) Ok(x) or Err(d) Ok(y) Ok(y) or Err(d) Err(e) Err(e) - src_rust{lhs.and(rhs)}
method lhs rhs output ------ ------ --------- ------ and Ok(x) Ok(y) Ok(y) and Ok(x) Err(d) Err(d) and Err(d) (ignored) Err(d)
enum Option<T> {
Some(T),
None
}mit Methoden analog / kompatibel zu src_rust{Result}
- Verschachtelte Ergebnisse
let r = Ok(Some("yay!"));
r.transpose(); // -> Some(Ok("yay!"))- src_rust{Result} in src_rust{Option} umwandeln
let maybe = Ok(1); maybe.ok() // -> Some(1) maybe.err() // -> None
- src_rust{Option} in src_rust{Result} umwandeln
let maybe = Some(1); maybe.ok_or("this should not happen!")
- Package: Ein oder mehrere Crates
- Crate: Kleinste Einheit von Code, die der Rust Compiler (und cargo) verarbeitet
- Library Crates und Binary Crates
- Beliebig viele Binary Crates
- Maximal ein Library Crate
Module:
- inline Modul in src_shell{lib.rs}
- Modul in eigener Datei in src_shell{src}: src_shell{src/submodule.rs}
- Modul in eigenem Verzeichniss: src_shell{src/submodule/mod.rs}
Sub-Module:
- inline Sub-Modul in src_shell{module.rs}
- Sub-Modul in eigener Datei in src_shell{src/module}: src_shell{src/module/submodule.rs}
- Sub-Modul in eigenem Verzeichniss: src_shell{src/module/submodule/mod.rs}
- Jedes Modul ist zunächst privat
- src_rust{pub mod} für öffentliche Module
- Sub-Module können auf alle Funktionen, Structs, etc. von Eltern-Modulen zugreifen
- externe Crates src_rust{use regex;}
- absoluter Pfad src_rust{use crate::module::submodule;}
- relativer Pfad
- src_rust{use self::module::submodule;}
- src_rust{use super::othermodule::submodule;}
- src_rust{use submodule::subsubmodule;}
- Aliase erzeugen src_rust{use regex as re; }
- Re-Exportieren src_rust{pub use crate::module::submodule;}
- Von der Kommandozeile src_shell{cargo add regex}
- Abhängigkeiten von Crates auf crates.io
# Cargo.toml [dependencies] regex = "1.7.0"
- Abhängigkeiten von GitHub
[dependencies] regex = { git = "https://github.com/rust-lang/regex" }
- optionale Features
[dependencies] # Enables the `derive` feature of serde. serde = { version = "1.0.118", features = ["derive"] }
- src_shell{mypkg/src/main.rs} ausführen: src_shell{cargo run –bin mypkg}
- src_shell{mypkg/src/bin/other.rs} ausführen: src_shell{cargo run –bin other}
- Taget-Konfiguration
# Cargo.toml [[bin]] name = "newname" path = "bin/other.rs" # ...
- Typen, Kapselung von Daten und Methoden: YES!
- Polymorphismus (Trait-Bounds, Generics): YES!
- statisches und dynamisches Dispatching: YES!
- Subtyping (auf src_rust{struct}s) und Vererbung: NOPE…
- Rust Book zu OOP
- generische Objekte die Trait implementieren
- dynamical Dispatch: konkrete Implementierung wird zur Laufzeit ermittelt
- Unbekannte Größe zur Compile-Time
let v: Box<dyn MyTrait> = Box::new(MyStruct{})Implementierung von Standardverhalten
impl Named {
fn get_name(&self) -> String
fn say_hello(&self) -> String {
format!("Hello, my name is {}", self.get_name())
}
}- Traits die auf Funktionalität anderer Traits beruhen
trait Shouted: fmt::Display { fn shouted(&self) { let out = format!("{self}"); // self needs fmt::Display println!("{}!!!!", out.to_ascii_uppercase()); } }
- Traits kombinieren
trait ShoutedInfo: fmt::Display + fmt::Debug { fn shouted(&self) { let out = format!("{self} ({self:?})"); // self needs fmt::Display println!("{}!!!!", out.to_ascii_uppercase()); } }
- Erzeugung in mehreren Schritten
- mutable während der Erzeugung
- imutable finales Objekt
let mut builder = GreeterBuilder::new();
let greeting = builder.set_name("Rust")
.set_greeting("Hello")
.build()- nicht veränderbare Werte
- pure Funktionen
- Komposition von Funktionen (First Class Functions)
- Funktionen übergeben
fn say_something() { println("something"); } fn do_twice(f: fn() -> ()) -> () { f(); f(); } do_twice(say_something);
- Funktionen mit Argumenten und Rückgabewert
fn twice_the_size(f: fn(&str) -> i32, s: &str) -> i32 { f(s) + f(s) }
Funktionen können Rückgabewert einer Funktion sein
pub fn get_operator(desc: &str) -> fn(usize, usize) -> usize {
match desc {
"+" => add,
"*" => mult,
_ => panic!("unkown"),
}
}- Closures sind anonyme Funktionen die ihre Umgebung “einfangen”.
- Closures sind repräsentiert durch Trait src_rust{Fn} oder src_rust{FnMut}
- keine bekannte Größe zur Compile-Time
- Closures sind keine Funktionen (aber funktionen implementieren src_rust{Fn})
- Typen können deduziert werden
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;
dbg!(add_one_v4(1 as u32)); // x: u32 und return-Wert u32- als nicht veränderbarer Wert
{ let y: usize = 5; // add_some: Fn(usize) -> usize let add_some = |x: usize| -> usize {x + y}; add_some(5); // -> 10 }
- als veränderbarer Wert
{ let mut y: usize = 0; // add_nothing: FnMut(usize) -> usize let add_nothing = |x: usize| -> usize {y += 1; x}; add_some(5); // -> 5 und y == 1 }
- Referenz auf Wert aus Umgebung
let x = vec![1, 2, 3]; let first = || {x.len()}; dbg!(first());
- Mutable Referenz auf Wert aus Umgebung
let mut x = vec![1, 2, 3]; let mut second = || {x.push(4)}; // second: FnMut, x: &mut Vec second(); x.len(); // 4
- Besitz übernehmen
let mut x = vec![1, 2, 3]; let third = move || {x.push(4); x.len()}; third(); // 4 x.len(); // Error
- Iterieren über Collections
(1..3).map(|i| i*2).for_each(|x| println!("{x}"));
- und einsammeln
let even_numbers: Vec<usize> = (1..3).map(|x| x * 2) .collect(); // [2, 4, 6] dbg!(even_numbers);
- auf src_rust{HashMap}
use std::collections::HashMap; let even_numbers: HashMap<usize, usize> = (1..3).map(|x| (x - 1, x * 2)) .collect(); // {0: 2, 1: 4, 2: 6} dbg!(even_numbers);
- Werte auf Eigenschaften prüfen
(1..100).filter(|x| x % 2 == 0)
- Filter mit Umgebung
(1..100).filter(|x| cum += x; cum < 25)
- Akkumulieren von Ergebnissen
(1..100).fold(0, |acc, x| acc + x)
$(\ldots((0 + 1) + 2) + 3) \ldots + 100)$ - src_rust{rfold} Akkumulieren von Rechts
(1..100).rfold(0, |acc, x| acc + x)
- src_rust{Results} und src_rust{Options} haben auch src_rust{map}
Ok(2).map(|x| x * 2); // -> Ok(4) Err("foo").map(|x| x * 2); // -> Err("foo") Some("foo").map(|s| s.len() ) // -> Some(3)
- boolsche Operatoren mit Closures src_rust{Fn(T) -> Result<U,E>}
Ok(5).and_then(|x| Ok(x * 2)) // -> Ok(10) Err("some error").or_else(|error| error_recovery(error))
- Paralelität: zeitgleiche Ausführung von mehreren Vorgängen (OS-Threads und Prozesse)
- Nebenläufigkeit: gemeinsame Ausführung von mehreren Vorgängen
- Präemptive Nebenläufigkeit: ausführender Vorgang wird von Management-Prozess unterbrochen (greenlets, goroutines)
- Kooperative Nebenläufigkeit: ausführender Vorgang pausiert selbständig und lässt anderen Vorgang weiterarbeiten (JavaScript async/await, Python asyncio, Rust async-await)
- Deadlocks: simultanes Warten auf Resourcen
- Race-Conditions: simultaner ändernder Zugriff auf gleichen Speicher
- starten eins Threads
thread::spawn(|| { println("Hello, Threads!"); });
- warten auf Ende eines Threads
let handle = thread::spawn(|| { println("Give me a minute"); thread::sleep(Duration::from_secs(60)); println("Ok, done!"); }); handle.join.unwrap(); // wartet auf thread
- src_rust{handle.is_finished()} prüft ob der Thread fertig ist
fn main() {
let name = String::from("Rust");
thread::spawn(move || {println("Hello, {name}");})
.join().unwrap();
}- Laufzeit von Threads kann nicht geprüft werden
- src_rust{move}, src_rust{Rc} oder src_rust{Clone}
- src_rust{Rc} → src_rust{Arc}
- src_rust{RefCell} → src_rust{Mutex}
- src_rust{RwLock}: multiple readers, single writer
let cnt = Arc::new(Mutex::new(0));
let cnt_clone = Arc::clone(&cnt);
thread::spawn(move || {
let mut local_cnt = cnt_clone.lock().unwrap();
local_cnt += 1;
});Do not communicate by sharing memory; instead, share memory by communicating.
- Mechanismus zum Übertragen von Werten und Objekten
- src_rust{mspc::channel}
let (transmitter, receiver) = mscp::channel();
thread::spawn(|| { transmitter.send(String::from("foo")).unwrap()} );
let msg = receiver.recv().unwrap();- mit src_rust{transmitter} beliebig viele Nachrichten senden
- src_rust{recv} ist ein src_rust{Err} sobald src_rust{transmitter} gelöscht ist
let (transmitter, receiver) = mscp::channel();
let t1 = transmitter.clone();
thread::spawn(|| { t1.send(String::from("foo")).unwrap()} );
let t2 = transmitter.clone();
thread::spawn(|| { t2.send(String::from("bar")).unwrap()} );
drop(transmitter);
for msg in receiver {
println!("{msg}");
}- Send-Trait: Objekte können zwischen Threads transferiert werden
- Sync-Trait: src_rust{T} implementiert src_rust{Sync}, wenn src_rust{&T} src_rust{Send} implementiert
- alle primitiven Typen
- nicht src_rust{Rc}
- alle Typen die aus src_rust{Send} / src_rust{Sync} zusammengesetzt sind
- (alte) C++-Bibliothek von Rust aus benutzen
- legacy Bibliotheken weiter benutzen
- legacy Entwicklung in Rust fortführen
- Rust-Bibliothek implementieren und von C++ aus benutzen
- C++-Anwendung in Rust weiterentwickeln
- Rewrite in Rust schrittweise durchführen
- https://cxx.rs/index.html Vollständige Brücke zwischen Rust und C++, keine Anpassung des C++-Codes oder des Rust-Codes notwendig
- https://github.com/mystor/rust-cpp Inline C++-Code in Rust-Code Falls eine C++-Lib häufig in Rust genutzt werden soll
- “Rust ← C++”-Build-System mit cargo
- Boilerplate-Code wird automatisch generiert
- Übersetzen von Typen
- Teilen von Daten-Strukturen
- C++ Funktionen deklarieren
#[cxx::bridge] mod ffi { unsafe extern "C++" { include!("package/include/header.h"); fn get_string_len(input: &str) -> usize; } }
- Aufruf in Rust
let str_size = ffi::get_string_len("Hello, World!");
- C++ Implementierung
// strlen.h uint64_t get_string_len(rust::Str);
uint64_t get_string_len(rust::Str input){ return input.size(); }
- Rust-Funktionen deklarieren
#[cxx::bridge(namespace = "org::strlib")] mod ffi { extern "Rust" { fn get_size(input: &CxxString) -> usize; } }
- Rust-Funktion in C++ benutzen
auto len = org::strlib::get_size("Hello, World!")<< std::endl;
- Rust-Funktion implementieren
fn get_size(input: &CxxString) -> usize { input.len() }
- rudimentäre Typen (src_cpp{uint64_t} → src_rust{u64})
- Rust-Typen in C++ übersetzt
Rust C++ ------------ ------------------ String rust::String &str rust::Str Vec<T> rust::Vec<T> Result<T> throw/catch - C++-Typen in Rust übersetzen
Rust C++ ------------ ------------------ CxxString std::string UniquePtr<T> std::unique_ptr<T> CxxVector<T> std::Vec<T>
- Bottlenecks in Python-Anwendungen in Rust implementieren Beispiele:
- Rust-Anwendungen mit Python automatisieren
- PyO3
- Rust ↔ Python
- Type-Konvertierung
- Maturin
- Initialisieren
- Bauen
- Verteilen (außer manylinux)
- Initialisieren von neuen Projekten
maturin init - Bauen von Python-Modulen/Paketen
maturin build - Installieren von Python-Paket in Virtualenv
maturin develop
#[pymodule]
fn pyrsdemo(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(get_file_size, m)?)?;
Ok(())
}#[pyfunction]
fn get_file_size(a: &str) -> PyResult<u64> {
let path = PathBuf::from(a);
if !path.exists() {
return Err(PyValueError::new_err("{a} does not exist!"));
}
Ok(path.metadata()?.len())
}- PyResult für Python-Errors
- Transparente Type-Konvertierung
- Struct → Python-Class
#[pyclass] struct MyGreeter{ greeting: String, }
- Methoden und Dunder-Methoden definieren
#[pymethods] impl MyGreeter { #[new] fn __new__(greeting: &str) -> Self { MyGreeter{ greeting: greeting.to_string() } } fn greet(&self, subj: &str) -> String { format!("{self.greeting}, {subj}!") } }
- Vererbung
- Initialisieren von Python und des GILs
pyo3::prepare_freethreaded_python();
- GIL reservieren
Python::with_gil(|py| { })
- Python-Code ausführen
let result: Result<Option<Vec<u64>>, PyErr> = py .eval("[i * 10 for i in range(5)]", None, None)? .extract()?;