Rust #
Basics #
fn main() {
println!("Hello, world!");
}
Funktionen #
Funktionen werden mit dem fn
-Schlüsselwort (ausgeprochen: “fun”) eingeführt. Funktionen-Statements werden mit {}
abgegrenzt. Der Deklaration des Rückgabetyps wird ->
vorangestellt, der Rückgabewert ist standardmässig die letzte Zeile in der Funktion, die ohne Semikolon abgeschlossen werden muss. Bsp. euklidischer Algorithmus zum Auffinden des grössten gemeinsamen Divisors:
fn gcd(mut n: u64, mut m: u64) -> u64 {
assert!(n != 0 && m != 0);
while m != 0 {
if m < n {
let t = m;
m = n;
n = t;
}
m = m % n;
}
n
}
Skalare Typen #
Integers #
Länge | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 (Standard) | u32 |
64-bit | i64 | u64 |
arch | isize | usize |
Bereich:
- Signed: -(2^n-1^) bis 2^n-1 ^- 1
- Unsigned 0 bis 2^n^ - 1
isize
und usize
hängen von der Architketur ab, auf der das Programm läuft. Bei einer 32-bit Architektur ist isize
bzw. usize
identisch mit 32-bit
, bei einer 64-bit Architektur mit 64-bit
.
Integer-Literale #
Alle numerischen Literale ausser Byte
erlauben Typsuffixe und _
als Trenner.
Literal | Beispiel |
---|---|
Dezimal | 98_222 |
Hexadezimal | 0xff |
Oktal | 0o77 |
Binär | 0b1111_0000 |
Byte (nur u8) | b’A' |
Fliesskoma-Typen
f32
f64
(Standard)
Boolean #
true
false
Character #
char
. Bereich: Skalara Unicode-Werte
Zusammengesetzte Typen #
Zusammengesetzte Typen (compound types) können mehrere Werte von anderen Typen zu einem Typ vereinigen. Rust kennt zwei primitive zusammengesetzte Typen: tuples und arrays.
Tuples #
Beispiel: let tup: (i32, f64, u8) = (500, 6.4, 1);
Werden auf Stack gespeichert
Zugriff auf einzelnen Wert: tup.0;
etc.
Destructuring: let (x, y, z) = tup;
Arrays #
Beispiel: let a = [1, 2, 3, 4, 5];
- Im Unterschied zu Tuples müssen Array immer skalare Werte desselben Typs haben.
- Arrays haben immer eine fixe Länge
- Werden auf Stack gespeichert
Zugriff auf einzelnen Wert: a[0];
etc.
Collections #
Werden im Unterschied zu primitiven zusammengesetzten Typen auf Heap gespeichert
Strings #
String
is the dynamic heap string type, like Vec
: use it when you need to own or modify your string data.
str
is an immutable^1^ sequence of UTF-8 bytes of dynamic length somewhere in memory. Since the size is unknown, one can only handle it behind a pointer. This means that str
most commonly^2^ appears as &str
: a reference to some UTF-8 data, normally called a “string slice” or just a “slice”. A slice is just a view onto some data, and that data can be anywhere, e.g.
-
in static storage: a string literal
"foo"
is a&'static str
. The data is hardcoded into the executable and loaded into memory when the program runs. -
inside a heap allocated
String
:String
dereferences to a&str
view of the String’s data. -
on the stack: e.g. the following creates a stack-allocated byte array, and then gets a view of that data as a
&str
:use std::str;
let x: &[u8] = &[b’a’, b’b’, b’c’]; let stack_str: &str = str::from_utf8(x).unwrap();
In summary, use String
if you need owned string data (like passing strings to other tasks, or building them at runtime), and use &str
if you only need a view of a string.
This is identical to the relationship between a vector Vec<T>
and a slice &[T]
, and is similar to the relationship between by-value T
and by-reference &T
for general types.
^1^A str is fixed length; you cannot write bytes beyond the end, or leave trailing invalid bytes. Since UTF-8 is a variable width encoding, this effectively forces all strs to be immutable. In general, mutation requires writing more or fewer bytes than there were before (e.g. replacing an a (1 byte) with an ä (2+ bytes) would require making more room in the str). ^2^At the moment it can only appear as &str, but dynamically sized types may allow things like Rc for a sequence of reference counted UTF-8 bytes. It also may not, str doesn’t quite fit into the DST scheme perfectly, since there is no fixed size version (yet).
Konzepte #
Ownership #
Etabliert eine definierte Lebenszeit für jeden Wert, der GC im Sprachkern unnötig macht, und Schnittstellen für andere Arten von Ressourcen wie Sockets und File handlers bietet.
Stack #
- LIFO-Prinzip
- Durch dieses Prinzip sind Operationen, die auf den Stack zugreifen, schnell, da neue Daten immer nur zuoberst auf dem *stack *abgelegt werden und umgekehrt Daten nur immer von dort geholt werden
- Eine andere Eigenschaft, die den stack sehr schnell macht, ist, dass nur Daten in ihm gespeichert werden können, deren (fixe) Grösse zur Kompilierzeit bekannt ist. D.h. skalare Datentypen (integers, floats, chars, bools) und primitive *compound types *(arrays, tuples).
- Pushing onto the stack
- Popping off the stack
Heap #
- Pointer (im stack) zeigen auf Daten im heap
- Allocating (on the heap)
Ownership-Regeln #
- Jeder Wert in Rust hat eine Variable, die ihr *owner *genannt wird
- Es kann nur immer ein owner zu einer Zeit existieren
- Wenn der owner nicht mehr im *scope *ist, dann wird der Wert im Speicher gelöscht
Moves #
Transferieren Werte von einem Owner zu einem anderen
Borrows #
Lassen Code einen Wert benutzen, ohne dass die Ownership angetastet wird.
Smart Pointers #
- Box
- Cell
- RefCell
- Rc
- Arc
Structs #
Es existieren drei Typen von Structs:
-
Named-field struct:
struct GrayscaleMap { pixels: Vec, size: (usize, usize), }
-
Tuple-like struct:
struct Bounds(usize, usize);
-
Unit-like struct:
struct Onesuch;
Enums #
Es existieren drei Typen von Enums:
-
“Einfache” Enums:
enum HttpStatus { Ok, NotModified, NotFound, } Solche Enums werden intern als Integers gespeichert. Optional kann den Elementen auch ein spezfischer Integer-Wert zugeordnet werden (bspw.
Ok = 200
). -
Enums mit tuple variants:
enum RoughTime { InThePast(TimeUnit, u32), InTheFuture(TimeUnit, u32), } Solche Enums werden anschliessend folgendermassen initialisiert:
RoughTime::InThePast(TimeUnit::Years, 4*20 + 7);
-
Enums mit struct variants:
enum Shape { Sphere { center: Point3d, radius: f32 }, Cuboid { corner1: Point3d, corner2: Point3d }, } Sie werden folgendermassen initialisiert:
Shape::Sphere { center: ORIGIN, radius: 1.0 };
Ein Enum kann auch eine Mischung aus verschiedenen Typen enthalten
Traits #
Fn Traits #
Trait Object #
The difference between a trait object and a bounded generic is that the latter is always monomorphized during compilation, while the former is an instance of a dynamically sized type and can therefore only be used behind some type of pointer. This pointer actually contains to pointers: One to the data, and one to a vtable (virtual table), which contains in its turn, for each method of the trait and its supertraits that T implements, a pointer to T’s implementation (i.e. a function pointer).
Important Traits #
Types #
Dynamically Sized Types (DST) #
Newtype Pattern #
Module #
Begrifflichkeiten #
- Packages sind ein Feature von cargo, das es erlaubt, crates zu erstellen, testen und publizieren.
- Crates sind ein Modulbaum in Form einer Binary oder einer Library
- Mit Modules und dem
use
Schlüsselwort lassen sich die Reichweite und die Öffentlichkeit von paths steuern - Paths ist ein Weg, ein item wie ein
struct
, eine Funktion oder ein Modul zu benennen
Packages #
Cargo.toml
+src/main.rs
: Cargo erstellt eine Binary mit dem gleichen Namen wie das Package, undmain.rs
ist die crate rootCargo.toml
+src/lib.rs
: Cargo erstellt eine Library mit dem gleichen Namen wie das Package, undlib.rs
ist die crate root- Befinden sich sowohl
main.rs
als auchlib.rs
imsrc/
-Verzeichnis, werden zwei Crates erstellt: Eine Library und eine Binary - Ein Package kann 0-1 Library Crates enthalten und beliebig viele Binary Crates
- Soll mehr als ein Binary Crate erstellt werden, werden die entsprechenden Dateien in
src/bin/
abgelegt
Module #
- Module helfen, Code zu gruppieren
- Ein Modul wird mit dem
mod
-Schlüsselwort erstellt. Sein Inhalt befindet sich in geschweiften Klammern - Die Crate Root befindet sich implizit im Module
crate
Pfade #
- Absoluter Pfad: Beginnt mit dem Name der Crate oder mit einem literalen
crate
- Relativer Pfad: Startet vom aktuellen Module und hat als erstes Element
self
,super
oder ein Identifier im aktuellen Modul
Sichtbarkeitsregeln #
- Alle Elemente (Funktionen, Methoden,
structs
,enums
, Modules und Konstanten) sind standardmässig privat - Ist ein Element privat, können nur Elemente in demselben Modul und dessen Kindmodulen darauf zugreifen.
- Mit dem Schlüsselwort
pub
wird ein Element öfffentlich und ist von überallher zugänglich
Overall, these are the rules for item visibility:
If an item is public, it can be accessed through any of its parent modules. If an item is private, it can be accessed only by its immediate parent module and any of the parent’s child modules.
Advanced Error Handling #
Benötigt:
-
Ein Enum mit den benötigten Fehlerklassen (implementiert als tuple-like variants)
-
Enum implementiert Traits Debug (derivable) und Display (nicht derivable)
-
Enum implementiert From-Traits für die benötigten Typkonversionen (fremder Error-Typ -> eigener Error-Typ)
-
Optional:
type
-Definition fürResult
-Alias -
auch die Crate error_chain2
Beispiel: use std::error; use std::fmt::{self, Display, Formatter}; use std::io; use std::num::ParseIntError; use std::result;
#[derive(Debug)]
enum Error {
Io(io::Error),
Parse(ParseIntError),
}
// The trait Display is used for user-facing output (other than the Debug trait)
impl Display for Error {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
match *self {
Error::Io(ref error) => error.fmt(formatter),
Error::Parse(ref error) => error.fmt(formatter),
}
}
}
// Error is a trait representing the basic expectations for error values,
// i.e. values of type E in Result<T, E>. Errors must describe themselves
// through the Display and Debug traits
impl error::Error for Error {
}
// Needed for implicit casting
impl From<io::Error> for Error {
fn from(error: io::Error) -> Self {
Error::Io(error)
}
}
impl From<ParseIntError> for Error {
fn from(error: ParseIntError) -> Self {
Error::Parse(error)
}
}
type Result<T> = result::Result<T, Error>;
fn main() -> Result<()> {
let mut line = String::new();
std::io::stdin().read_line(&mut line)?;
let mut sum = 0;
for word in line.split_whitespace() {
let num = word.parse::<i64>()?;
sum += num;
}
println!("Sum: {}", sum);
Ok(())
}
Weitere Ressourcen #
- Rust for functional programmers
- Is Rust good for Data Mining?
- https://stackoverflow.com/questions/41549516/is-there-a-way-to-use-locked-standard-input-and-output-in-a-constructor-to-live
- https://stackoverflow.com/questions/36106280/forcing-bufread-trait-compatibility-between-iostdio-and-bufreader
- https://stackoverflow.com/questions/51543177/why-do-i-get-a-match-arms-have-incompatible-types-error-in-a-function-returnin
- https://stackoverflow.com/questions/27535289/what-is-the-correct-way-to-return-an-iterator-or-any-other-trait
- https://stackoverflow.com/questions/29760668/conditionally-iterate-over-one-of-several-possible-iterators
- https://www.reddit.com/r/rust/comments/32rjdd/reading_from_a_file_or_stdin_based_on_command/
- https://joshleeb.com/posts/rust-traits-and-trait-objects/
- https://stackoverflow.com/questions/31904842/return-a-map-iterator-which-is-using-a-closure-in-rust