Einführung

Webseiten

Rust Starter Pack

Awesome Rust

Rust By Example

Ecosystem Guide

Unsafe Rust

Idiomatic Rust

Rust Design Patterns

Rust Performance Guide

Rust CLI Tools

Videos

Rust, for people who know some C++

A Simpler Way to See Results

What is a Lifetime?

Blogs

Rust Blog

Inside Rust Blog

RISC-V OS in Rust

RISC-V Emulator in Rust

Systemnahe Programmierung

Bei systemnaher Programmierung muss sich der Programmierer direkt mit den Ressourcen und Funktionen des zugrunde liegenden Betriebssystems oder der Hardware auseinandersetzen.

Im Gegensatz zur anwendungsnahen Programmierung, bei der der Programmierer sich auf die Verwendung von Bibliotheken und APIs konzentriert.

Direkter Zugriff auf:

  • Hardware
  • Speicher
  • Systemcalls

Typische Aufgaben:

  • Betriebssystemfunktionen
    • Prozessverwaltung
    • Speicherverwaltung
    • Dateisystemverwaltung
  • Entwicklung von Treibern
  • Systemdienste
    • Netzwerkdienste
    • Datenbankdienste

C

 -----------------
< Systemnaehe? C! >
 -----------------
       \
        \   .~-~.
          /^     ^\
         (__ O O __)
          \ \< >/ /

The good

  • Standard seit 70er
  • Portabel
  • Low-Level
  • Manual Memory Management
  • Legacy Code

the bad

  • Undefined Behaviour
  • Keine Memory Safety Garantie
  • Fehleranfaellige Parallelverarbeitung
  • Error handling
  • Dynamisches Linking

and the ugly

  • Keine einheitliche Paketverwaltung
  • Mehrere Build-Systeme

Undefined Behaviour

In C ist Undefined Behaviour (UB) nicht immer offensichtlich. Anhand des unsafe Keywords kann man in Rust sofort Codestellen erkennen, an denen Undefined Behaviour auftreten kann. Keine Scheu vor unsafe!

C - Compiler Unterschiede

int main() {               // 1
  int i = 2;               // 2  Initialize variable
  int result = --i + i++;  // 3
  return 0;                // 4
}

gcc: result=3 clang: result=2


C - Buffer Overflow

  • Buffer mit bestimmter Groesse erstellen
  • Daten in Buffer schreiben
  • Daten sind groesser als Buffer
  • Naheligender Speicher wird ueberschrieben -> Overflow
#include <stdio.h>
#include <string.h>
int main() {
  char buffer[10];
  char *src = "This is a long string.";
  memcpy(buffer, src, strlen(src));
  printf("%s\n", buffer);
  return 0;
}

C - Use after free

  • Allokieren von Speicher
  • Schreiben in den Speicher
  • Freigeben des Speichers
  • Lesen aus dem Speicher -> Fehlerhaft
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int *)malloc(sizeof(int));  // TODO check allocation success
    *ptr = 42;
    printf("ptr: %d\n", *ptr);
    free(ptr);
    // Pointer is dangling
    printf("ptr after free: %d\n", *ptr);
    return 0;
}

C - Double Free

  • Speicher wird allokiert
  • Pointer wird an free uebergeben
  • Speicher is freigegeben, Pointer existiert weiter
  • Pointer wird erneut an free uebergeben -> Welcher Speicher wird bereinigt?
#include <stdio.h>
#include <stdlib.h>
int main() {
    int *ptr = (int *)malloc(sizeof(int));
    free(ptr);
    free(ptr);
    return 0;
} 

C - Invalid Index

  • Index ist groesser als array
  • Value enthaelt falsche Daten
#include <stdio.h>
int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int value = arr[6];
    printf("Value: %d\n", value);
    return 0;
}

C - Divide by Zero

  • Undefinded
  • Manche Programme brechen ab, manche arbeiten mit falschem Ergebnis weiter
#include <stdio.h>
int main() {
    int numerator = 10;
    int denominator = 0;
    int result = numerator / denominator;
    printf("Result: %d\n", result);
    return 0;
}

C - Dangling Pointer

  • Value in einem lokalen Scope
  • Dereferenzieren des Values im lokalen Scope
  • Referenz (Pointer) wird returned
  • Value existiert nicht mehr, Pointer schon
  • Pointer zeig auf fehlerhaften Speicher
int *getDanglingPointer() {
    int value = 42;
    return &value;
}

int main() {
  int *ptr = getDanglingPointer();
  *ptr = 10;  // Unsicher welcher Speicher beschrieben wird
  printf("%d", *ptr);
  return 0;
}

Rust

Rust ist keine esoterische Programmiersprache.

Denn Rust loest folgende Probleme:

  • Ownership
  • Memory Safety
  • Unsafe
  • Statisches Linking
  • Cargo
  • Paketverwaltung
  • Build System
  • Package Registry crates.io
  • Parallelverarbeitung
  • Explicit Error handling
 ------------
< Yay, Rust! >
 ------------
       \
        \   .~-~.
          /^     ^\
         (__ O O __)
          \ \< >/ /

Typsysteme

statisch

  • Kompilierung

dynamisch

  • Laufzeit

hybride

  • Jit, etc.

Typsysteme sind ein wichtiger Bestandteil von Programmiersprachen. Sie helfen dabei, Programme korrekter und sicherer zu machen.

ADTs

Algebraische Datentypen

In der Praxis ermöglichen algebraische Datentypen eine klare und strukturierte Modellierung von Daten, was besonders in funktionalen Programmiersprachen wie Haskell, ML oder Scala häufig genutzt wird. Sie bieten eine elegante Möglichkeit, komplexe Datenstrukturen zu definieren und zu manipulieren.

Summen Typ

Ein Summentyp repräsentiert eine Menge von möglichen Werten.

Beispiel:

Option = Some(bool) | None

Hier bedeutet Some(bool), dass dieser Typ von einem Boolean begleited wird. None hat keinen Wert, sondern steht nur als Typ fuer sich.

Dieser Typ kann im Ast Some 2 Werte einnehmen |{true, false}|. Im Ast None genau einen |{None}|. Daraus ergibt sich die Summe 3.

Produkt Typ

Ein Produkttyp repräsentiert eine Struktur, bei der alle enthaltenen Werte gleichzeitig vorhanden sind

Beispiel:

Punkt = x: u8, y: u8

Hier ist Punkt ein Produkttyp mit den Feldern x und y, die beide benötigt werden, um einen Punkt vollständig zu definieren.

Ideen

Sammlungen

Creative Projects for Rust Programmers

Practical Rust Projects: Building Game, Physical Computing, and Machine Learning Applications Springer oder O'Reilly

Exercism

Anregungen

Shing Lyu, Practical Rust Projects: Building Game, Physical Computing, and Machine Learning Applications, Apress 2020. Z.B. Kapitel 5, Physical Computing in Rust, ein simples Experiment für Raspberry Pi 3 mit LED und Taster auf einem Steckbrett.

Shing Lyu, Practical Rust Web Projects: Building Cloud and Web-Based Applications, Apress 2021.

Carlo Milanesi, Creative Projects for Rust Programmers, Packt Publishing 2020.

Ken Youens-Clark, Command-Line Rust, O'Reilly 2022.

Python Module in Rust erstellen

Dateiverschlüsselung, von Go nach Rust übertragen

Joseph F. Nusairat, Rust for the IoT. Building Internet of Things Apps with Rust and Raspberry Pi, z.B. Kapitel 9, Raspberry Pi Sense Hat.

Tooling

Rustup

rustup ist ein Tool um die Installation und Verwaltung von "Rust-Toolchains" zu erleichtern. Diese Toolchains sind Compiler fuer verschiedene Betriebssysteme und Architekturen. Mit rustup kann man mehrere dieser Compiler installieren, verwalten und updaten.

rust-analyzer

rust-analyzer ist ein Language Server fuer Rust. Dieser bietet Funktionen zur statischen Code-Analyse, Autovervollständigung, Refactoring, Fehlererkennung und andere hilfreiche Features.

Cargo

In Rust ist Cargo sowohl ein Build-System als auch ein Paketmanager, was bedeutet, dass es sich um den gesamten Build-Prozess kümmert und gleichzeitig die Verwaltung von Abhängigkeiten erleichtert. Dies trägt dazu bei, dass Rust-Projekte leichter zu erstellen, zu teilen und zu warten sind.

  • Build-System
    • Kompilierung von Software
    • Definiert, wie Quellcode in ausführbaren Code umgewandelt wird (Kompilierungsoptionen)
    • Dependencies
  • Paketmanager
    • Verwaltung von Abhängigkeiten und Bibliotheken in einem Softwareprojekt
    • Herunterladen, Installieren und Aktualisieren von Bibliotheken oder Modulen
    • Richtige Versionen von Abhängigkeiten

Clippy

clippy ist ein Linter, der dazu dient, Quellcode statisch zu analysieren.

  • Identifiziert syntaktische Fehler oder logische Unstimmigkeiten im Code
  • Ueberprüft den Code auf die Einhaltung von Coding-Standards oder Stilrichtlinien
  • Weist auf bewährte Methoden und Muster hin, um die Qualität und Lesbarkeit des Codes zu verbessern

Empfohlene Einstellungen:

cargo clippy --fix -- \
-W clippy::pedantic \
-W clippy::nursery \
-W clippy::unwrap_used \
-W clippy::expect_used

rustfmt

rustfmt ist ein Formatter, der den Quellcode eines Programms automatisch neu formatiert.

  • Einhaltung von Stilrichtlinien und Konventionen
  • Lesbarerer Code
  • Konsistenter Code-Stil

Rustdoc

rustdoc generiert automatisch Dokumentation aus Doc-Comments (diese werden mit /// geschrieben).

  • Einheitliche Dokumentation
  • Dokumentierter Code
  • Keine Dokumentations Redundanz
  • Automatische Dokumentations Webseite in einheitlichem Stil

Weitere Informationen im Rustdoc Book.

Bacon

Bacon ist ein Interaktiver rust code checker.

CLI

Referenz

Warum CLI

  • Flexibel
  • Transparent

Principles

  • Benutzerfreundlichkeit
    • Human readable
    • Verwendung nicht nur von anderen Programmen
  • Einfache Bausteine
    • Kombinieren von kleinen, einfachen Programmen zu groesserem System
    • Pipes und Filterarchitektur
    • Kombinierbar durch stdin/stdout/stderr, signals, exit-codes
  • Konsistenz über Programme hinweg
    • Bereits bestehenden Mustern folgen
    • Intuitiv
  • Robustheit
    • Falscher input muss korrekt behandelt werden

Guidelines

  • Use a command-line argument parsing library where you can
  • Return zero exit code on success, non-zero on failure.
  • Send output to stdout
  • Send messaging to stderr
  • Display help text when passed no options, the -h flag, or the --help flag
  • Display a concise help text by default
    • A description of what your program does
    • One or two example invocations.
    • Descriptions of flags
  • Display help for Subcommands (like git commit --help)
  • In help text, link to the web version of the documentation
  • Lead with examples

Output

  • Human-readable output is paramount
  • Have machine-readable output where it does not impact usability
  • If human-readable output breaks machine-readable output, use --plain to display output in plain, tabular text format for integration with tools like grep or awk
  • If you change state, tell the user.
  • Make it easy to see the current state of the system
  • Use color with intention
  • Disable color if your program is not in a terminal or the user requested it

Errors

Arguments and Flags

Subcommands

Configuration and Environment Variables

Anyhow

crates.io

Chrono

crates.io

Clap

crates.io

Installation: cargo add clap --features derive

  1. Derive Parser from clap::Parser
#![allow(unused)]
fn main() {
#[derive(Parser)]
struct Cli {}
}
  1. Configure the Parser
#![allow(unused)]
fn main() {
#[derive(Parser)]
#[command(
  name = "Cli",
  author = "John Doe <john.doe@example.com",
  version = "1.0",
  about = "Does awesome things",
  long_about = None,
)]
struct Cli {}
}

Or read values from Cargo.toml:

#![allow(unused)]
fn main() {
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {}
}
  1. Change Parser behaviour

Change the help text. A list of possible placeholders can be found here:

#![allow(unused)]
fn main() {
#[command(help_template = "{name} ({version}) - {usage}")]
}

Disable the help flag (-h and --help):

#![allow(unused)]
fn main() {
#[command(disable_help_flag = true)]
}

Require each argument to have a help text:

#![allow(unused)]
fn main() {
#[command(help_expected = true)]
}
  1. Add arguments
  • Positional
#![allow(unused)]
fn main() {
#[derive(Parser)]
struct Cli {
  /// This is a positional, required argument
  positional: String,
  /// This is an optinal argument
  positional: Optional<String>,
  // Variadic positional arguments
  name: Vec<String>,
}
}
  • Flag
#![allow(unused)]
fn main() {
#[derive(Parser)]
struct Cli {
  /// On/Off switch
  #[arg(short, long)]
  release: bool,
  /// Set the default value to true
  #[arg(short, long, action = clap::ArgAction::SetFalse)]
  quiet: bool,
  /// Flag that counts how often it is set, eg. -vvv would be 3, -vv would be 2
  #[arg(short, long, action = clap::ArgAction::Count)]
  verbose: u8
}

}
  • Subcommand
#![allow(unused)]
fn main() {
}
  • Default
  1. Validate arguments
  • Enumerated Values
  • Validated Values
  • Argument Relations
  • Custom Validation

Arg Actions

Set Append SetTrue SetFalse Count Help HelpShort HelpLong Version

Daemonize

crates.io

  • Hintergrundprozess

Init-System

  • PID1 systemd, runit, openrc

Serde

crates.io

Beginners Cheatsheet

Traits

Display

Wie lassen sich Structs als String darstellen?

  • Python def __str__()
  • Java public String toString()
  • Rust trait Display
#![allow(unused)]
fn main() {
pub trait Display {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>;
}
}

Beispiel:

#![allow(unused)]
fn main() {
struct ID(u32);
impl Display for ID {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[test]
fn test_display() {
    let id = ID(123);
    let s = format!("{id}");
    assert_eq!(s, "ID(123)");
    assert_eq!(id.to_string(), "ID(123)");
}
}

Docs

From & Into

Wie lassen sich Typen value-to-value konvertieren?

  • Java constructor overloading
  • Rust trait From
#![allow(unused)]
fn main() {
pub trait From<T>: Sized {
    fn from(value: T) -> Self;
}
}

Beispiel:

#![allow(unused)]
fn main() {
struct ID(u32);
impl From<u16> for ID {
    fn from(value: u16) -> Self {
        Self(value as u32)
    }
}

#[test]
fn test_from() {
    let id = ID::from(123);
    assert_eq!(id.0, ID(123).0);
    let id: ID = 123.into();
    assert_eq!(id.0, ID(123).0);
}
}

From implementiert auch den Into-Trait mit, dieser ermoeglicht den .into()-Call From darf nicht fehlschlagen, dafuer existiert der TryFrom-Trait, z. B. kann man diesen gut fuer Konvertierung von Strings in eigene Typen verwenden, um bei Fehlerhaften Strings ein Result zurueck zu geben.

Docs

Deref

#![allow(unused)]
fn main() {
pub trait Deref {
  // Ergebnis-Typ nach dereferenzierung
  type Target: ?Sized;
  fn deref(&self) -> &Self::Target;
}

#[test]
fn test_deref() {
  let id = ID(123);
  assert_eq!(123, *id);
}
}

Beispiel:

#![allow(unused)]
fn main() {
struct ID(u32);
impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}
}

Docs

Default

Wie kann man einem Typen default Werte geben?

  • Python __init__(self, id=123, username="foo")
  • Rust trait Default
#![allow(unused)]
fn main() {
pub trait Default: Sized {
    // Required method
    fn default() -> Self;
}
}

Beispiel:

#![allow(unused)]
fn main() {
struct ID(u32);
impl Default for ID {
    fn default() -> Self {
        Self(0)
    }
}
}

Beispiel mit Enum:

#![allow(unused)]
fn main() {
enum Command {
    Help,
    Run,
    Build,
}

impl Default for Command {
    fn default() -> Self { Command::Help }
}
}

Docs

Iterator

Wie mache ich eigene Typen iterierbar?

  • Python def __next__(self)
  • Java implements Iterable<T>
  • Rust trait Iterator

Beispiel

#![allow(unused)]
fn main() {
struct IDS(Vec<u32>);

impl Iterator for IDS {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        self.0.pop()
    }
}

#[test]
fn test_iterator() {
    let mut ids = IDS(vec![1, 2, 3]);
    assert_eq!(ids.next(), Some(1));
    assert_eq!(ids.next(), Some(2));
    assert_eq!(ids.next(), Some(3));
}
}

Docs

Drop

Sized

Clone

Copy

Deref & DerefMut

Default

AsRef and AsMut

Borrow and BorrowMut

From & Into

TryFrom & TryInto

ToOwned

Smart Pointer

Box

Rc & Arc

RefCell: Ref & RefMut

Funktionen

Function Pointers

Closures

FnOnce, FnMut, Fn