zurück zum Artikel

Ferris Talk #1: Iteratoren in Rust

Rainer Stropek
Ferris Talk – Neuigkeiten zu Rust. Eine Heise-Kolumne von Rainer Stropek und Stefan Baumgartner für Rustaceans

Startschuss der neuen Rust-Kolumne von Rainer Stropek und Stefan Baumgartner: Die erste Ausgabe des ab jetzt monatlichen Ferris Talks stellt die Iteratoren vor.

In der ersten Ausgabe unserer Rust-Kolumne erklären wir zunächst für Rust-Einsteiger und -Einsteigerinnen, was Iteratoren in Rust sind und wie sie funktionieren. Anschließend gehen wir der Frage nach, welche Lücken die Rust-Version 1.53 hinsichtlich Iteratoren und Feldern geschlossen hat und warum Entwicklerinnen und Entwickler so lange auf diese scheinbar einfache Sprachfunktion warten mussten. Dabei lässt sich auch über die Sprachprinzipien von Rust einiges lernen.

Ferris Talks – die Kolumne für Rustaceans
Ferris Talk – Neuigkeiten zu Rust. Die neue Kolumne für Rustaceans

Vielfach heißt es, dass sich die Welt im Bereich der Softwareentwicklung schneller ändert als in anderen Bereichen. Auch wenn das stimmen mag, sind fundamentale Umbrüche mit langfristiger Wirkung auch in der Informatik selten. Nicht jedes JavaScript-Framework stellt die Art, Software zu entwickeln, komplett auf den Kopf. Rust aber gehört zu den seltenen Änderungen, die nachhaltig, langfristig und unserer Einschätzung nach positiv wirken werden. In dieser Kolumne möchten wir abwechselnd regelmäßig über Neuerungen und Hintergründe im Bereich Rust berichten. Sie soll Teams, die Rust schon verwenden, helfen, auf dem Laufenden zu bleiben. Einsteigerinnen und Einsteiger sollen durch die Kolumne tiefere Einblicke in die Funktionsweise von Rust erhalten.

Kolumnenautoren vom Rust Meetup Linz

Der Kolumnen-Titel nimmt Bezug auf Ferris, das krabbenförmige inoffizielle Maskottchen [16] der Rust-Gemeinde. Die Ferris Talks schreiben Stropek und Baumgartner ab sofort monatlich und im Wechsel – mehr zu den Autoren steht am Ende des Artikels. Die beiden Kolumnisten sind überzeugte Rustaceans und organisieren das Rust Meetup Linz [17], die Fachtreffen werden in Videoform festgehalten und sind auf der Rust-Linz-Playliste bei YouTube [18] abrufbar. Wer die beiden beruflich treffen möchte, kann sie unter anderem als Vortragende und als Workshop-Trainer bei der Rust-Konferenz 2021 [19] von Heise erleben.

Rainer Stropek
ist IT-Unternehmer, Softwareentwickler, Trainer, Autor und Vortragender im Microsoft-Umfeld. Er ist seit 2010 MVP für Microsoft Azure und entwickelt mit seinem Team die Zeiterfassung für Dienstleistungsprofis time cockpit [23].

Stefan Baumgartner
arbeitet bei Dynatrace im österreichischen Linz. In seiner Freizeit organisiert er Stahlstadt.js [24], ScriptConf, DevOne, Rust Meetup Linz [25] und das legendäre Technologieplauscherl [26]. Wenn noch ein wenig Freizeit bleibt, spricht er beim Working Draft Podcast, genießt italienische Pasta, belgisches Bier und britischen Rock.

Iteratoren sind ein Konzept, das sich in vielen Programmiersprachen findet. C#, Java, JavaScript, C++ bieten Abstraktionen für das Iterator-Konzept, mit denen man im Code die Elemente einer Datenstruktur (etwa einer Liste oder eines Feldes) durchlaufen kann. Darauf aufbauend bringen die Plattformen Mengen- und Aggregationsfunktionen wie das Projizieren, Filtern und Summieren, das Berechnen des Durchschnitts sowie weitere Funktionen mit, die in der Praxis hilfreich sind.

Rust ist keine Ausnahme, im Gegenteil. Iteratoren spielen in der Sprache eine große Rolle. Die zugrundeliegenden Schnittstellen und viele auf ihnen aufbauende Funktionen sind von Haus aus eingebaut. Zusatzbibliotheken wie Itertools [27] fügen weitere hinzu.

Mit Version 1.53 [28] hat Rust diesen Sommer eine wesentliche Erweiterung in Sachen Iteratoren über Felder erhalten: Der IntoIterator-Trait wurde für Felder mit konstanter Größe vollständig implementiert. Damit ziehen Iteratoren für Felder nach und bieten die gleichen Funktionen wie Iteratoren über andere Datentypen wie Vektoren. Damit kann man endlich mit einer for-Schleife über ein Array iterieren, und zwar by Value, nicht nur by Reference. Folgendes Listing zeigt, was seit Rust 1.53 möglich ist [29]:

fn main() {
    // This worked already in Rust < 1.53
    for &number in &[1, 1, 2, 3, 5, 8, 13] {
        println!("The next value is {}", number);
    }

    // This works in Rust >= 1.53
    for number in [1, 1, 2, 3, 5, 8, 13] {
        println!("The next value is {}", number);
    }
}

Die wichtigsten beiden Abstraktionen von Rust, die Iteratoren zugrunde liegen, sind die Traits std::iter::IntoIterator (https://doc.rust-lang.org/std/iter/trait.IntoIterator.html) und std::iter::Iterator (https://doc.rust-lang.org/std/iter/trait.Iterator.html). Der Der IntoIterator-Trait legt fest, wie beispielsweise ein Vektor in einen Iterator umgewandelt wird und sieht so aus:

pub trait IntoIterator {
    type Item;
    type IntoIter: Iterator;
    fn into_iter(self) -> Self::IntoIter;
}

Der Trait enthält eine Methode into_iter, die für das Erzeugen des jeweiligen Iterators zuständig ist (siehe InterIter im Trait). Der Iterator verwaltet Elemente vom Typ Item.

Jeder Typ, der den Trait IntoIteratorimplementiert, lässt sich in Schleifen verwenden. Folgendes Beispiel steht im Rust-Playground zum Ausprobieren [30] bereit:

fn main() {
    let numbers = vec![1, 1, 2, 3, 5, 8, 13];
    
    // std::vec::Vec implements IntoIterator,
    // therefore we can use it in a for loop.
    for number in numbers {
        println!("The next value is {}", number);
    } 
}

Folgender Trait Iterator repräsentiert den eigentlichen Iterator:

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    //...
}

Das Herzstück des Iterator-Traits ist die Methode next. Sie rückt den Iterator zum nächsten Element vor und gibt dieses zurück. Wenn man am Ende der Iteration angelangt ist, ist das Ergebnis None. Vor dem ersten Aufruf steht der Iterator gedanklich vor dem ersten Element. Erst durch den Aufruf bewegt man sich zum ersten Element. Dabei ist zu bedenken, dass Iteratoren "faul" (lazy) sind. Das bedeutet, dass das Anlegen des Iterators an sich nichts bewirkt. Erst der erste next-Aufruf aktiviert den Iterator.

Die drei Auslassungspunkte im Trait Iterator deuten an, dass der Trait viele weitere Funktionen zum Arbeiten mit dem Iterator hat, die man in der täglichen Entwicklungsarbeit auch ständig braucht (unter anderem filter, map, min, max ). Sie basieren jedoch alle auf next und kommen mit einer fertigen Implementierung. Bei der Entwicklung eines eigenen Iterators reicht es, next zu implementieren. Alle weiteren Methoden bekommt man quasi geschenkt, da im Gegensatz zu Interfaces in vielen anderen Sprachen die Traits in Rust Implementierungen für Funktionen mitbringen können. Genau diese Tatsache nutzt Iterator.

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittplattformen (Google Ireland Limited) übermittelt werden. Mehr dazu in unserer Datenschutzerklärung [31].

Ein weiterer Punkt, der Quereinsteiger in Rust erstaunt, ist, dass into_iter die Kontrolle über die zugrundeliegende Datenstruktur übernimmt – in Rust wird das als Ownership bezeichnet. Wenn man beispielsweise einen Vektor von Zahlen anlegt und into_iter darauf aufruft, kann man den Vektor danach nicht mehr verwenden. Der Iterator übernimmt die Ownership über den Speicherbereich – das heißt, die variable numbers können nicht mehr verwendet werden. Ein Beispiel dafür:

let numbers = vec![1,2,3];
let other_numbers = numbers;

Auch hier kann man danach numbers nicht mehr verwenden, weil other_numbers nun die Ownership über den Speicherbereich hat. Bei into_iter() ist das Ergebnis ähnlich. Der Grund dafür sind die Ownership-Regeln von Rust. Lösen lässt sich das Problem durch den Einsatz einer Borrowed Reference, wie das folgende Beispiel zeigt. Es lässt sich im Rust-Playground ausprobieren [32]:

fn main() {
    let numbers = vec![1, 1, 2, 3, 5, 8, 13];
    println!("The min is {}", numbers.into_iter().min().unwrap());
    // The following line does NOT work because into_iter 
    // CONSUMES the vector (i.e. takes ownership of it). Once
    // you call into_iter, you cannot access the underlying vector
    // anymore.
    println!("The sum is {}", numbers.into_iter().sum::<i32>());

    // The following example DOES work because numbers is a 
    // borrowed reference to the vector, not the vector itself.
    let numbers = &vec![1, 1, 2, 3, 5, 8, 13];
    println!("The min is {}", numbers.into_iter().min().unwrap());
    println!("The sum is {}", numbers.into_iter().sum::<i32>());
}

In diesem Zusammenhang sind die Funktionen iter() und iter_mut() erwähnenswert. Wer anstelle von into_iter() eine dieser beiden Funktionen verwendet, bekommt spezielle Formen von Iteratoren:

Diese beiden Regeln sind nur Konventionen, sie sind nicht über Traits garantiert.

Im Gegensatz zu iter() und iter_mut() versucht der Rust-Compiler beim Einsatz von into_iter() aus dem Kontext zu ermitteln, welche Form von Iterator notwendig ist. Das folgende Beispiel zeigt die verschiedenen Funktionen zum Erzeugen von Rust-Iteratoren [33]:

struct Point {
    x: f64,
    y: f64,
}

fn main() {
    // Iterate over items in a vector by value.
    let points = vec![Point{x: 1.0, y: 1.0}, Point{x: 2.0, y: 2.0}];
    let _first_point: Point = points.into_iter().next().unwrap();

    // Iterate over items in a vector by reference
    let points = vec![Point{x: 1.0, y: 1.0}, Point{x: 2.0, y: 2.0}];
    let mut iter = points.iter();    // points.iter() is equivalent
                                                // to (&points).into_iter()
    let _first_point: &Point = iter.next().unwrap();

    // Iterate over items in a vector by mutable reference
    let mut points = vec![Point{x: 1.0, y: 1.0}, Point{x: 2.0, y: 2.0}];
    let mut iter = points.iter_mut();  // points.iter_mut() is 
                                                    // equivalent to (&mut points).into_iter()
    let first_point: &mut Point = iter.next().unwrap();
    first_point.x = 3.0; // As we have a mutable reference, we can change the content
    first_point.y = 4.0;
}

Die strikten Regeln für Speicherverwaltung in Rust, die bereits der Compiler erzwingt, sorgen dafür, dass der erzeugte Code sehr effizient ist. Dass Rust ohne Garbage Collector auskommt, wirkt sich positiv auf die Performance des Maschinencodes aus, den der Rust-Compiler erstellt. Man "bezahlt" Abstraktionen wie Iteratoren also nicht durch Geschwindigkeitsverlust (Zero-Cost Abstractions). Ich möchte das an einem einfachen Beispiel erklären [34]:

fn main() {
    let mut sum = 0;
    for i in 0..10 {
        sum += i;
    }
    
    println!("{}", sum);
}

Das Beispiel verwendet einen Iterator über die Zahlen Null bis Neun in einer Schleife. Interessant ist ein Blick auf den erzeugten Assemblercode. Denkbar wäre, dass im Hintergrund into_iter aufgerufen wird und die Schleife das Ergebnis Schritt für Schritt durch den Aufruf von next erzeugt. Tatsächlich ist das der Fall, wenn man den Code mit der Debug-Option kompiliert. Beim Umschalten auf den Relase-Modus hingegen bleibt vom Iterator nichts übrig. Tatsächlich erkennt der Compiler, dass das Ergebnis der Schleife schon zur Übersetzungszeit ermittelbar ist. Daher kann er die Schleife inklusive Iterator entfernen und das Ergebnis als Konstante in den Code einbauen.

Auch wenn das Beispiel extrem ist, verdeutlicht es, dass man beim Kompilieren der Release-Version nicht annehmen darf, dass die Iteratoren im Assembler-Code analog zum Rust-Code wiederzufinden sind.

betterCode() Rust – Dein Einstieg und Deep Dive in Rust
Heise-Konferenz zu Rust, Einstieg und Deep Dive, 13. Oktober 2021 online, mit Rainer Stropek und Stefan Baumgartner

Heise richtet am 13. Oktober 2021 eine Online-Konferenz zu Rust [35] für Einsteiger und Experten aus. Die beiden Ferris-Talk-Kolumnisten sind dort mit Vorträgen und Workshops präsent:

Rainer Stropek erklärt das Speichermanagement in Rust [36] und erläutert die Rust-Konzepte Ownership, References, Borrowing und Lifetimes. Er zeigt die Konzepte anhand praktischer Code-Beispiele. Wer teilnimmt, erlangt ein Problembewusstsein der Sicherheitsrelevanz von Speicherverwaltung, baut Verständnis auf für die Rust-Kernkonzepte und erkennt Unterschiede zwischen Rust und anderen gängigen Programmiersprachen.

Stefan Baumgartner widmet sich Serverless Rust [37] und führt vor, wie man Serverless-Workloads mit Rust ausführt. Wer teilnimmt, gewinnt ein tieferes Verständnis von AWS Lambda und Azure Functions, lernt die passenden Rust-Crates für Serverless-Workloads und begreift den unmittelbaren Nutzen von Rust dafür.

Flankierende Workshops von Stropek & Baumgartner (9-17 Uhr)

Um ein tieferes Verständnis für Iteratoren in Rust zu erhalten, entwickeln wir einen eigenen Iterator. Genau genommen handelt es sich um eine spezielle Form eines Iterators: einen Generator. Generatoren haben keine zugrundeliegende Datenstruktur, aus der sie Elemente bei der Iteration zurückgeben. Sie generieren den nächsten Wert erst, wenn sie mit next danach gefragt werden.

Das Beispiel implementiert einen einfachen Passwortgenerator. Es ist nicht das Ziel, kryptografisch starke Passwörter zu generieren, sondern das Prinzip der Rust-Iteratoren zu verdeutlichen. Das Augenmerk liegt im folgenden Codebeispiel [41] auf der Implementierung von IntoIterator für die Struktur Passwords sowie auf der Implementierung von Iterator für PasswordsIterator.

use rand::Rng;

struct Passwords {
    length: usize,
}

impl Passwords {
    fn new() -> Self {
        Self::with_length(10)
    }

    fn with_length(length: usize) -> Self {
        Passwords { length: length }
    }
}

impl IntoIterator for Passwords {
    type Item = String;
    type IntoIter = PasswordsIterator;
    fn into_iter(self) -> Self::IntoIter {
        PasswordsIterator {
            length: self.length,
        }
    }
}

struct PasswordsIterator {
    length: usize,
}

impl Iterator for PasswordsIterator {
    type Item = String;
    fn next(&mut self) -> Option<Self::Item> {
        let mut result = String::with_capacity(self.length);
        for _ in 0..self.length {
            // For demo purposes, we generate a password
            // consisting only of letters a..z
            result.push((b'a' + rand::thread_rng().gen_range(0..=(b'z' - b'a'))) as char);
        }

        Some(result)
    }
}

fn main() {
    // Generate three passwords and print them in a for loop
    for p in Passwords::new().into_iter().take(3) {
        println!("The next password is {}", p);
    }

    // Generate three passwords and print them with for_each
    Passwords::with_length(5)
        .into_iter()
        .take(3)
        .for_each(|p| println!("The next password is {}", p));
}

Zum Abschluss wollen wir der Frage nachgehen, warum die Implementierung von IntoIterator für Felder so lange gedauert hat.

In früheren Rust-Versionen war IntoIterator nur für Referenzen auf Felder implementiert, daher konnte man nur by Reference über Felder iterieren. Der Grund dafür war, dass Arrays in Rust immer eine fixe Länge haben und die Länge Teil der Typdefinition ist, beispielsweise let numbers: [i32; 7] für ein Feld mit sieben Elementen vom Typ i32. Für eine generische Implementierung von IntoIterator für Felder ist es daher notwendig, die konstante Länge als Parameter für den generischen Typ übergeben zu können (wie impl<T, const N: usize> IntoIterator for [T; N] {...}). Diese Sprachfunktion wird als Const Generics bezeichnet und kam erst Anfang 2021 mit Rust 1.51 [42].

Auf Basis der Const Generics-Funktion hätte man sofort IntoIterator für Felder implementieren können. Jedoch konnten Entwicklerinnen und Entwickler schon früher array.into_iter() aufrufen, was der Compiler aber implizit als (&array).into_iter() umgesetzt hat. Sie bekamen somit einen by Reference-Iterator. Das simple Hinzufügen von IntoIterator für Felder hätte also bestehenden Code gebrochen; und das ist nur bei Major Releases von Rust erlaubt.

Mit Rust 1.53 hat das Rust-Team zu einem Trick gegriffen: Wenn bestehender Code explizit array.into_iter() aufruft, wird daraus weiterhin (&array).into_iter(), allerdings mit einer Warnung, dass der Code in späteren Rust-Versionen nicht mehr funktionieren. Wenn array.into_iter() implizit aufgerufen wird (wie in einer for-Schleife), wird die neue by Value-Implementierung von IntoIterator für Felder verwendet. Dass sich der into_iter()-Aufruf für Felder anders verhält als der bei anderen Datenstrukturen (unter anderem Vektoren), ist nicht schön, bietet aber endlich die Möglichkeit, by Value über Felder zu iterieren und mit der for-Schleife Felder zu durchlaufen.

Das folgende Codebeispiel [43] demonstriert das Vorgehen. Zum besseren Verständnis sind die Datentypen der Variablen explizit angegeben sind, auch wenn Rust diese Typen implizit ermitteln könnte.

use std::{vec::IntoIter};

fn main() {
    // Iterate over vector by value
    let numbers: Vec<i32> = vec![1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: IntoIter<i32> = numbers.into_iter();
    let item: i32 = num_iter.next().unwrap();
    println!("{}", item);

    // Iterate over vector by reference
    let numbers: &Vec<i32> = &vec![1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: std::slice::Iter<i32> = numbers.into_iter();
    let item: &i32 = num_iter.next().unwrap();
    println!("{}", item);

    // In contrast to vector, iterating over an array means
    // iterating BY REFERENCE, not by value. Note that this
    // code leads to a warning because this behavior will
    // change in future Rust versions.
    let numbers: [i32; 7] = [1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: std::slice::Iter<i32> = numbers.into_iter();
    let item: &i32 = num_iter.next().unwrap();
    println!("{}", item);

    // Since Rust 1.53, we can explicitly call IntoIterator::into_iter
    // as it is now implemented for arrays. It will lead to an iteration
    // by value, NO LONGER by reference.
    let numbers: [i32; 7] = [1, 1, 2, 3, 5, 8, 13];
    let mut num_iter: std::array::IntoIter<i32, 7> = IntoIterator::into_iter(numbers);
    let item: i32 = num_iter.next().unwrap();
    println!("{}", item);

    // As IntoIterator has been implemented in Rust 1.53, the following
    // syntax is now possible. It iterates over the given array BY VALUE
    // using a for loop.
    for i in [1, 1, 2, 3, 5] {
        println!("{}", i)
    }
}

Iteratoren sind ein mächtiges Werkzeug in Rust, die sich nicht maßgeblich auf die Performance auswirken. Der Rust-Compiler optimiert Code mit Iteratoren, und der erzeugte Maschinencode ist performant.

In diesem Jahr wurden mit den Rust-Versionen 1.51 und 1.53 wichtige Verbesserungen eingeführt, die unangenehme Lücken im Umgang mit Iteratoren über Felder geschlossen haben.

Ferris Talk – Neuigkeiten zu Rust. Die Kolumnisten:
Rainer Stropek, timecockpit.com, Rust Meetup Linz, Autor der Ferris Talks, der Kolumne über die Programmiersprache Rust bei Heise Developer

Rainer Stropek, Autor von Ferris Talk #1

ist seit über 25 Jahren als Unternehmer in der IT-Industrie tätig.

Er gründete und führte in dieser Zeit mehrere IT-Dienstleistungsunternehmen und entwickelt neben seiner Tätigkeit als Trainer und Berater in seiner Firma software architects [44] mit seinem Team die preisgekrönte Software time cockpit [45].

Rainer hat Abschlüsse der höheren technischen Schule für Informatik Leonding (AT) sowie der University of Derby (UK). Er ist Autor mehrerer Fachbücher und Artikel in Magazinen im Umfeld von Microsoft .NET und C#, Azure, Go und Rust. Seine technischen Schwerpunkte sind Cloud Computing, die Entwicklung verteilter Systeme sowie Datenbanksysteme.

Rainer tritt regelmäßig als Speaker und Trainer auf namhaften Konferenzen in Europa und den USA auf. 2010 wurde Rainer von Microsoft zu einem der ersten MVPs (Most Valuable Professionals) für die Azure-Plattform ernannt. Seit 2015 ist er Microsoft Regional Director. 2016 hat er zudem den MVP Award für Visual Studio und Developer Technologies erhalten.

Fellow Rustacean und Co-Kolumnist
Stefan Baumgartner, Dynatrace.at, Rust Meetup Linz, Autor der Ferris Talks, der Kolumne über die Programmiersprache Rust bei Heise Developer

Stefan Baumgartner

lebt und arbeitet als Software-Architekt und Entwickler im österreichischen Linz mit Schwerpunkt auf Webentwicklung, Serverless und Cloud-basierte Architekturen.

Für den Smashing Magazine Verlag veröffentlichte er 2020 mit “TypeScript in 50 Lessons” sein zweites Buch [46], seine Onlinepräsenz fettblog.eu [47] enthält Artikel, Tutorials und Guides zum Thema TypeScript, Rust, React, und Software Engineering im Allgemeinen.

Stefan organisiert Meetups und Konferenzen, wie Stahlstadt.js [48], die DevOne, ScriptConf, Rust Meetup Linz [49], und das legendäre Technologieplauscherl [50]. Außerdem ist er regelmäßig Gastgeber im Working Draf [51]t, dem deutschsprachigen Podcast über Webtechnologien.

(sih [52])


URL dieses Artikels:
https://www.heise.de/-6175409

Links in diesem Artikel:
[1] https://www.heise.de/hintergrund/Ferris-Talk-1-Iteratoren-in-Rust-6175409.html
[2] https://www.heise.de/hintergrund/Ferris-Talk-2-Abstraktionen-ohne-Mehraufwand-Traits-in-Rust-6185053.html
[3] https://www.heise.de/hintergrund/Ferris-Talk-3-Neue-Rust-Edition-2021-ist-da-mit-Disjoint-Capture-in-Closures-6222248.html
[4] https://www.heise.de/hintergrund/Ferris-Talk-4-Asynchrone-Programmierung-in-Rust-6299096.html
[5] https://www.heise.de/hintergrund/Ferris-Talk-5-Tokio-als-asynchrone-Laufzeitumgebung-ist-ein-Fast-Alleskoenner-6341018.html
[6] https://www.heise.de/hintergrund/Ferris-Talk-6-Ein-neuer-Trick-fuer-die-Formatstrings-in-Rust-6505377.html
[7] https://www.heise.de/hintergrund/Ferris-Talk-7-Vom-Ungetuem-zur-Goldrose-eine-kleine-Rust-Refactoring-Story-6658167.html
[8] https://www.heise.de/hintergrund/Ferris-Talk-8-Wasm-loves-Rust-WebAssembly-und-Rust-jenseits-des-Browsers-7064040.html
[9] https://www.heise.de/hintergrund/Ferris-Talk-9-Vom-Builder-Pattern-und-anderen-Typestate-Abenteuern-7134143.html
[10] https://www.heise.de/hintergrund/Ferris-Talk-10-Constant-Fun-mit-Rust-const-fn-7162074.html
[11] https://www.heise.de/hintergrund/Ferris-Talk-11-Memory-Management-Speichermanagement-in-Rust-mit-Ownership-7195773.html
[12] https://www.heise.de/hintergrund/Ferris-Talk-12-Web-APIs-mit-Rust-erstellen-7321340.html
[13] https://www.heise.de/hintergrund/Ferris-Talk-13-Rust-Web-APIs-und-Mocking-mit-Axum-7457143.html
[14] https://www.heise.de/hintergrund/Ferris-Talk-14-Rust-bekommt-endlich-asynchrone-Methoden-in-Traits-8929334.html
[15] https://www.heise.de/hintergrund/Ferris-Talk-15-Bedingte-Kompilierung-in-Rust-9337115.html
[16] https://rustacean.net/
[17] https://rust-linz.at/
[18] https://www.youtube.com/playlist?list=PL85XCvVPmGQgL3lqQD5ivLNLfdAdxbE_u
[19] https://rust.bettercode.eu/#workshops
[20] https://www.youtube.com/watch?v=a2k8AEhHv8c&t=1508s
[21] https://wasm.bettercode.eu/
[22] https://rust.bettercode.eu/
[23] https://www.timecockpit.com
[24] https://stahlstadt.js.org/
[25] https://rust-linz.at/
[26] https://technologieplauscherl.at/
[27] https://github.com/rust-itertools/itertools
[28] https://www.heise.de/news/Programmiersprache-Rust-1-53-erlaubt-Unicode-fuer-Identifier-6110452.html
[29] https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a00fea28e10c76b9f482224fa49bd266
[30] https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=44d1173562f95670d0fc328654ecdc54
[31] https://www.heise.de/Datenschutzerklaerung-der-Heise-Medien-GmbH-Co-KG-4860.html
[32] https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=2b2f6902cc55b9a37e22f894c1159bf6
[33] https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=6bfc4b027708f37817ca7276321d43ae
[34] https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=560d0b9c1931f5d3640452fde2c69dd3
[35] https://rust.bettercode.eu/
[36] https://rust.bettercode.eu/veranstaltung-13466-se-0-rust-memory-management.html
[37] https://rust.bettercode.eu/veranstaltung-13471-se-0-serverless-rust.html
[38] https://rust.bettercode.eu/veranstaltung-13653-se-0-rust-101--einfuehrung-in-die-programmiersprache-rust.html
[39] https://rust.bettercode.eu/veranstaltung-13435-se-0-wasm-module-fuer-den-browser-mit-rust.html
[40] https://rust.bettercode.eu/veranstaltung-13654-se-0-netzwerk-applikationen-mit-dem-tokio-stack.html
[41] https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b682405fe2e13ce3036c20330ee61e33
[42] https://www.heise.de/news/Rust-1-51-Const-Generics-Implementierung-hat-jetzt-MVP-Status-5999105.html
[43] https://play.rust-lang.org/?version=stable&mode=release&edition=2018&gist=5cc73746c31c4ef93bbd3d8672183370
[44] https://www.linkedin.com/company/software-architects-og/about/
[45] https://www.timecockpit.com/
[46] https://typescript-book.com/
[47] https://fettblog.eu/
[48] https://stahlstadt.js.org/
[49] https://rust-linz.at/
[50] https://technologieplauscherl.at/
[51] https://workingdraft.de/
[52] mailto:sih@ix.de