Auf Nummer sicher: Sicheres Programmieren mit Rust

Die Programmiersprache Rust hat sich als Alternative zu C/C++ etabliert. Was macht Rust in puncto Sicherheit anders und vielleicht besser als die Platzhirsche?

In Pocket speichern vorlesen Druckansicht 737 Kommentare lesen

(Bild: Andrew Derr/Shutterstock.com)

Lesezeit: 17 Min.
Von
  • Jens Siebert
Inhaltsverzeichnis

Betrachtet man die Geschichte der System-Programmierung über die letzten Jahrzehnte, mit dominierenden Programmiersprachen wie C oder C++, so fällt vor allem eines auf: Fehlerfreiheit oder Informationssicherheit werden oft erst im Nachgang zur Implementierung einer Anwendung oder als Prozess-Themen betrachtet.

Insbesondere für Anwendungen, die Standards für sicherheitskritische Systeme entsprechen müssen, wird eine hohe Testabdeckung sowie die Anwendung statischer Codeanalyse empfohlen oder vorgeschrieben. Das ist unter anderem in der Medizintechnik oder im Banken- und Finanzwesen der Fall.

Sonderheft zu sicherer Softwareentwicklung

Dieser Artikel stammt aus dem neuen iX-Developer-Sonderheft "Sichere Software entwickeln". Es behandelt auf 156 Seiten unter anderem die Themen Web-Application-Security, Codeanalyse und Cloud-Security.

Der Schwerpunkt zu den Programmiersprachen zeigt die Sicherheitskonzepte von Rust auf und ein weiterer Artikel hilft, Speicherfehler in C++ aufzuspüren sowie zu vermeiden. Wer mit Java entwickelt, findet eine Übersicht der Änderungen an der Security von Java 11 bis Java 17.

Der Themenbereich Kryptografie geht von den Grundlagen über die Fallstricke beim Einbinden kryptografischer Verfahren in eigene Anwendungen bis zum Ausblick auf die Post-Quanten-Kryptografie. Ein weiterer Schwerpunkt zu DevSecOps zeigt Methoden, Werkzeuge und Reifegradmodelle auf.

Das Heft ist ab sofort im heise Shop als PDF für 12,99 Euro verfügbar. Die gedruckte Ausgabe lässt sich für 14,90 Euro vorbestellen. Ein Bundle aus gedruckter Ausgabe plus PDF ist ebenfalls verfügbar.

Eine der Ursachen für solche Empfehlungen und Vorgaben ist in den verwendeten Programmiersprachen zu suchen. Sowohl C als auch das abwärtskompatible C++ erlauben es, ihre Konstrukte so einzusetzen, dass sie zu undefiniertem Verhalten eines Programms führen können (s. Listing 1).

int main() {
    unsigned long a[1];
    a[3] = 0xaaaabbbbdeadbeefUL;
    return 0;
}

Listing 1: Syntax-Highlighting in C

Dieses standardkonforme, jedoch offensichtlich fehlerhafte Beispiel lässt sich von jedem C/C++-Compiler in ein ausführbares Programm übersetzen und ausführen. Das Verhalten des Programms ist gemäß dem C/C++-Standard undefiniert:

"Undefined Behavior: Behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements." (Aus der Spezifikation: ISO/IEC 9899:2018, Kapitel 3.)

Implementierungen dieser Standards ist es freigestellt, wie sie mit undefiniertem Verhalten umgehen. Im besten Fall erkennt der Compiler die Gefahr und gibt eine Warnung oder gar einen Fehler aus, oder es passiert wie in den meisten Fällen einfach nichts. Das öffnet Tür und Tor für unbeabsichtigte Fehler und Sicherheitslücken, die in unserer stark vernetzten Welt umso stärker zum Tragen kommen, wie man beinahe täglich den (IT-)Nachrichten entnehmen kann.

Hier schlägt oft die Stunde von Werkzeugen zur statischen Codeanalyse, die in der Lage sind, solche Fehler zu erkennen. Jedoch sind diese Werkzeuge meist recht teuer in der Anschaffung und müssen erst aufwendig in den Entwicklungsprozess integriert werden. Prinzipbedingt erkennen sie bei Weitem nicht alle potenziellen Fehler.

Sinnvoller wäre es, eine System-Programmiersprache von vornherein so zu entwerfen, dass sie undefiniertes Verhalten weitestgehend unterbindet. Mit diesem Anspruch wird die Programmiersprache Rust seit 2010 von einer internationalen Open-Source-Community vorangetrieben. Zunächst als Projekt des Entwicklers Graydon Hoare bei der Mozilla Corporation gestartet und durch sie unterstützt, erfreut sich die Sprache mittlerweile wachsender Beliebtheit. Auch große Player wie beispielsweise Microsoft oder Amazon nutzen und unterstützen Rust.

Das Ziel der Entwicklung von Rust war von Beginn an, eine Programmiersprache zu entwickeln, die die Themen Sicherheit (Safety), Geschwindigkeit (Performance) und Nebenläufigkeit (Concurrency) unter einen Hut bringt und dabei so benutzerfreundlich wie möglich bleibt. Zur Umsetzung dieser Ziele implementiert Rust verschiedene Konzepte, die im Folgenden näher betrachtet werden sollen.

Hinter den Stichworten "Ownership" und "Borrowing" verbirgt sich bei Rust die Implementierung mächtiger Konzepte, die die sichere Verwendung von Speicher zur Laufzeit eines Programms bereits bei der Übersetzung durch den Compiler sicherstellen. Um das Konzept zu illustrieren, zeigt Listing 2 ein Beispiel in C++.

std::vector<int> v { 1, 2 };
int *vp = &v[1];
v.emplace_back(3);
std::cout << *vp;

Listing 2: Potenzieller "use-after-free"-Fehler

Der Code erzeugt einen Vektor mit zwei Elementen und eine Referenz, die auf ein Element innerhalb dieses Vektors verweist. Anschließend erhält der Vektor ein weiteres Element hinzu und das Element, auf welches die Referenz verweist, wird ausgegeben.

Das Beispiel sieht zunächst nicht sonderlich gefährlich aus. Die Gefahr geht hier jedoch von der Implementierung des Vektors sowie der Referenz auf das Element innerhalb des Vektors aus. Ein Vektor in C++ ist ein dynamisch wachsender Container gleichartiger Elemente. Das Erzeugen eines Vektors reserviert den Speicherbereich für eine bestimmte Anzahl von Elementen. Überschreiten die Elemente beim Hinzufügen die Kapazität des Vektors, so löst das die Reservierung eines neuen, größeren Speicherbereiches aus und die Elemente des bisherigen Vektors werden dorthin verschoben. Der bisher genutzte, nun nicht mehr benötigte Speicherbereich wird freigegeben. Spannend bleibt dabei die Frage, was nun mit der Referenz auf das Element innerhalb des Vektors geschieht, die ja lediglich einen Verweis auf eine Speicheradresse darstellt. Hierbei handelt es sich um undefiniertes Verhalten. Man spricht in diesem Fall auch von "pointer invalidation" beziehungsweise sogenannten "use-after-free"-Fehlern.

Ein äquivalentes Beispiel lässt sich auch in Rust implementieren, wie Listing 3 demonstriert.

fn main() {
    let mut v = vec![1, 2];
    let vp = &mut v[1];
    v.push(3);
    println!("{}", *vp);
}

Listing 3: Potenzieller "use-after-free"-Fehler, diesmal in Rust

Das Übersetzen des Beispiels führt jedoch zu einem Compiler-Fehler (s. Listing 4.).

error[E0499]: 
cannot borrow `v` as mutable more than once at a time
 --> src/main.rs:4:5
  |
3 |     let vp = &mut v[1];
  |                   - first mutable borrow occurs here
4 |     v.push(3);
  |     ^ second mutable borrow occurs here
5 |     println!("{}", *vp);
  |                    --- first borrow later used here

Listing 4: Fehlermeldung beim Versuch, Listing 3 zu übersetzen

In diesem Beispiel wechselte der Wert (der Vektor mit den Elementen 1 und 2) durch die Definition der veränderbaren Referenz vp den Besitzer. Eine spätere Modifikation des Vektors über den Aufruf der push-Methode auf der Referenz v ist somit nicht mehr möglich.

Was zunächst wie eine massive Einschränkung und eher unflexibel anmutet, ist eines der Grundprinzipien von Rust: das Ownership-Konzept. Ein Wert in einem Speicherbereich, den ein Rust-Programm zur Laufzeit reserviert, hat einen eindeutig festgelegten Besitzer (Owner). Wenn der Lebenszyklus des Besitzers endet (beispielsweise am Ende eines Blocks), gibt das Programm den reservierten Speicherbereich frei. Dieses Konzept wird bereits während der Übersetzung eines Programms durch den Rust-Compiler durchgesetzt.

Im weiteren Verlauf dieses Artikels wird sich herauskristallisieren, dass das Konzept, so einschränkend es auf den ersten Blick auch wirken mag, in der Vermeidung von Speicherfehlern ausgesprochen mächtig ist. Es ist möglich, in Rust ähnlich flexibel und produktiv Software zu entwickeln wie in C oder C++. Listing 5 verdeutlicht das Ownership-Konzept.

fn consume(v: Vec<i32>) {
    println!("{:?}", v);
}

fn main() {
    let mut v = vec![1, 2];
    consume(v);
    v.push(3);
}

Listing 5: Fehlerhaftes Rust-Programm illustriert Ownership-Konzept

Hier entsteht ein veränderbarer Vektor mit zwei Elementen, den das Programm an eine Funktion übergibt, die diesen Vektor verarbeitet. Anschließend soll dem Vektor ein weiteres Element hinzutreten, was der Rust-Compiler jedoch in Listing 6 mit einer Fehlermeldung quittiert.

error[E0382]: borrow of moved value: `v`
 --> src/main.rs:8:5
  |
6 |     let mut v = vec![1, 2];
  |         ----- move occurs because `v` has type `Vec<i32>`, 
            which does not implement the `Copy` trait
7 |     consume(v);
  |             - value moved here
8 |     v.push(3);
  |     ^ value borrowed here after move

Listing 6: Fehlermeldung des Rust-Compilers: Der Borrow-Checker akzeptiert den Code aus Listing 5 nicht.

Das Beispiel zeigt, dass der Wert von v mit der Übergabe an die Funktion consume den Besitzer wechselt (value moved here). Der Besitz wird anschließend nicht an die aufrufende Funktion zurückgegeben, auch nicht implizit, was "Move-Semantik" heißt. Stattdessen wird der Speicherbereich, den der Vektor belegt hatte, nach der Verarbeitung durch die Funktion consume wieder frei und v lässt sich nicht weiter verwenden (value borrowed here after move).

Um die weitere Verwendung von v nach der Übergabe an die Funktion zu ermöglichen, gilt es, den Vektor als Referenz an die Funktion zu übergeben. Hierzu ist das Beispiel wie in Listing 7 anzupassen.

fn consume(v: &Vec<i32>) {
    println!("{:?}", v);
}

fn main() {
    let mut v = vec![1, 2];
    consume(&v);
    v.push(3);
}

Listing 7: Korrektur des Codes aus Listing 5 durch Übergabe des Parameters der Funktion als shared reference

Die Funktion consume übergibt nun den Parameter mittels einer Referenz (hier: &). Weiterhin ist auch beim Aufruf der Funktion die explizite Angabe erforderlich, dass der Wert v als Referenz zu übergeben ist. Durch die Änderung wird der Vektor beim Aufruf der Funktion lediglich "ausgeliehen" (borrowed), wechselt jedoch nicht vollständig den Besitzer. Folglich handelt es sich bei der Referenz um eine "shared reference", durch die nur lesender Zugriff auf den referenzierten Wert möglich ist. Der Rust-Compiler wechselt bei solchen Referenzen aus Gründen der Effizienz von der zuvor verwendeten Move-Semantik zu einer Copy-Semantik.

Soll der Vektor hingegen innerhalb der Funktion verändert werden, ist das durch eine Änderung des Codes analog zu Listing 8 möglich.

fn consume(v: &mut Vec<i32>) {
    v.push(4);
}

fn main() {
    let mut v = vec![1, 2];
    consume(&mut v);
    v.push(3);
    println!("{:?}", v);
}

Listing 8: Modifikationen des Wertes innerhalb der Funktion: Nur möglich, wenn der Parameter als mutable reference deklariert wurde.

Das Beispiel ersetzt die zuvor verwendete shared reference durch eine mutable reference, also durch eine veränderbare Referenz mit dem Schlüsselwort &mut. Diese Art von Referenz erlaubt nun auch die Änderung referenzierter Werte innerhalb von Funktionen, selbst, wenn diese nicht die ursprünglichen Besitzer des Werts sind. Allerdings gilt hier die Einschränkung, dass zwar beliebig viele shared references zugleich auf einen Wert aktiv sein dürfen, jedoch nur eine einzige mutable reference.

Veränderbare Referenzen folgen dabei wieder der Move-Semantik. Daraus ergibt sich, dass nicht einmal der ursprüngliche Besitzer eines referenzierten Wertes den Wert verändern kann, solange eine veränderbare Referenz auf den Wert aktiv ist. Dies ist bei nebenläufigen Programmen wichtig, damit sich referenzierte Werte nicht aus unterschiedlichen Threads heraus gleichzeitig ändern lassen. Das verhindert Race Conditions durch gleichzeitige, konkurrierende Zugriffe auf dieselbe Ressource.

Durch die explizite Semantik ist es dem Borrow Checker im Rust-Compiler jederzeit möglich, den Lebenszyklus jedes einzelnen Wertes, der in einem Rust-Programm zum Einsatz kommt, nachzuvollziehen. So lässt sich nicht mehr benötigter Speicher freigeben, sobald sichergestellt ist, dass der betroffene Wert nicht mehr referenziert wird. Das Ergebnis ist automatisch ein sicheres Speichermanagement bereits während der Übersetzung des Programms, wohingegen andere Sprachen wie Java, C# oder Go automatisches Speichermanagement lediglich zur Laufzeit bieten, was jedoch die Performance beeinträchtigen kann.

Das strikte Ownership-Konzept von Rust ist zwar äußerst sicher und gleichzeitig sehr flexibel, deckt jedoch einige Anwendungsfälle nicht ab. So lassen sich bestimmte Datenstrukturen nicht oder nur schwer mit einem so strikten Modell umsetzen. Hierzu gehören unter anderem doppelt verkettete Listen, bei denen ein Knoten sowohl von einem Vorgänger- als auch von einem Nachfolger-Knoten referenziert wird. Die Referenzen gilt es zusätzlich als veränderbare Referenzen zu definieren, damit eine Änderung des referenzierten Wertes möglich wird.

Um diesem Umstand Rechnung zu tragen, bietet Rust Möglichkeiten, auch solche Situationen sicher zu handhaben. Listing 9 veranschaulicht das.

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let cr = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = cr.lock().unwrap();
            *num += 1;
        });

        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap());
}

Listing 9: Mutex-API und Atomic Reference Counting in einem nebenläufigen Programm

Bei dem Listing handelt es sich im Wesentlichen um ein Programm, das nebenläufig bis 10 zählt. Für jedes Inkrement startet es einen eigenen Thread. Alle Threads besitzen eine Referenz auf einen gemeinsamen Zählerwert, der jedoch sicher inkrementiert wird.

Hierbei kommen zwei Techniken zum Einsatz, die gleichzeitigen Zugriff über eine veränderbare Referenz (shared mutable state) sowie gleichzeitigen Zugriff mehrerer Besitzer über eine unveränderliche Referenz (reference counting) mithilfe einer sicheren API ermöglichen.

Die in Listing 9 verwendete Mutex-API (std::sync::Mutex) ermöglicht es, eine Referenz auf einen Wert so zu kapseln, dass mehrere Konsumenten der Referenz (beispielsweise Threads) den Wert auf sichere Weise modifizieren können. Zu diesem Zweck wird der veränderbare Wert (der Startwert 0 des Zählers) an den Konstruktor eines Mutex-Objektes übergeben. Der Konstruktor liefert eine Referenz auf das Mutex-Objekt zurück. Den Zugriff auf den Wert der Referenz regelt dabei die lock-Methode des Mutex-Objektes. Sie liefert die eigentliche, veränderbare Referenz auf den Wert an einen Konsumenten und sperrt die Referenz gleichzeitig gegen weitere Zugriffe, bis die Sperre explizit oder implizit aufgehoben wird. Hebt man die Sperre auf, werden weitere potenzielle Konsumenten darüber benachrichtigt, dass nun wieder ein Zugriff auf den Wert möglich ist. Ab diesem Zeitpunkt ist es wieder möglich, die Sperre an einen weiteren Konsumenten neu zu vergeben.

Manch einer mag sich nun fragen, wie mehrere Konsumenten wie die Threads im obigen Beispiel gleichzeitig eine eigene Referenz auf ein Objekt halten können. Grundsätzlich schreibt das Ownership-Konzept ja vor, dass eine Referenz immer einen eindeutigen Besitzer haben muss, wie im vorigen Abschnitt besprochen.

Das ist zwar korrekt, jedoch gibt es auch hierfür eine sichere API, die mittels Reference Counting solche Konstrukte ermöglicht. Die Reference-Counting-API gibt es in zwei Ausprägungen: nicht thread-safe (std::rc::Rc), aber auch thread-safe in Form des Atomic Reference Counting (std::sync::Arc). Beide APIs leisten jedoch ähnliches, weshalb es an dieser Stelle genügt, sich auf das Atomic Reference Counting (Arc) zu beschränken.

Listing 9 verpackt die Referenz auf das Mutex-Objekt in einem Arc-Objekt. Die Referenz lässt sich dann beliebig oft klonen, unter anderem für jeden neu gestarteten Thread. Dabei lässt sich die Anzahl der Klone fest vorgeben, denn Zugriffe über eine solche Referenz sind nur lesend erlaubt. Das automatische Speichermanagement des Rust-Compilers verwaltet auch die Arc-Klone. Sobald der letzte Klon am Ende seines Lebenszyklus angekommen und zerstört worden ist, zerstört das Speichermanagement auch das Objekt, auf das die Arc-Referenz verweist. Im Beispiel betrifft das das Mutex-Objekt mit dem darin enthaltenen Zählerwert.

Die dargestellten Konzepte und APIs erlauben es dem Rust-Compiler, auch in so komplexen Fällen wie nebenläufigen Programmen mit gemeinsamem Zugriff auf geteilte Ressourcen den Lebenszyklus von Werten im Speicher nachzuvollziehen. Dadurch wird eine sichere Speicherverwaltung möglich, die Freiheit von Speicherfehlern und Race Conditions auch in komplexen Szenarien weitgehend garantiert.

Aufmerksamen Lesern wird das folgende Dilemma nicht entgangen sein: Wie lassen sich Konstrukte wie das Atomic Reference Counting oder die Mutex-API, die einen shared mutable state umsetzen, in Rust selbst implementieren, wo doch das strikte Ownership-Konzept genau das eigentlich verhindern soll?

Die Antwort lautet: Die Konstrukte lassen sich in Rust nicht implementieren, jedenfalls nicht in dem sicheren Teil von Rust, den der Artikel bislang behandelt hat. Mit "Unsafe Rust" gibt es jedoch eine Möglichkeit, die strikten Regeln von "Safe Rust" so weit zu lockern, dass auch unsichere Konstrukte verwendet werden können. Dies bedeutet jedoch nicht, dass die Sicherheitsmechanismen von "Safe Rust" für diesen unsicheren Code außer Kraft treten. Es wird lediglich die Verwendung unsicherer Konstrukte ermöglicht, auf welche die Sicherheitsmechanismen nicht angewendet werden können, wie Listing 10 zeigt.

fn main() {
    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("r1 is: {}", *r1);
        println!("r2 is: {}", *r2);
    }
}

Listing 10: Explizite Markierung eines Code-Blocks als "unsafe" beim Zugriff auf raw-pointer

Der Code erzeugt zwei sogenannte raw pointer, also Referenzen auf einen Wert, die nicht durch das automatische Speichermanagement des Compilers abgedeckt sind. Die Referenz r1 ist dabei eine shared reference, die Referenz r2 eine mutable reference. Beide Referenzen zeigen auf denselben Wert, was der Borrow Checker in Safe Rust verhindern würde. Das Erzeugen solcher raw pointer ist in Safe Rust durchaus erlaubt, nicht jedoch der Zugriff auf den derart referenzierten Wert. Der Zugriff darf nur innerhalb eines unsafe-Blocks stattfinden, durch den die Verwendung unsicherer Konstrukte gekennzeichnet ist.

Die Idee hinter "Unsafe Rust" ist es, unsicheren Code durch unsafe-Blöcke explizit kenntlich zu machen und ihre Verwendung auf ein Minimum zu reduzieren. Sollte in einem Programm, das unsicheres Rust verwendet, ein Speicherfehler auftreten, so weiß die Entwicklerin beziehungsweise der Entwickler sofort, an welchen Stellen man den Fehler suchen sollte. Weiterhin gilt die Empfehlung, unsicheren Code hinter einer sicheren API zu verstecken, um ihn zu isolieren.

Unsicheres Rust kann auch dazu verwendet werden, um externen Code wie Funktionen aus Bibliotheken aufzurufen, die in C oder C++ implementiert wurden. Auch solche Funktionsaufrufe sind in einem unsafe-Block durchzuführen, da es sich um potenziell unsichere Aufrufe handelt.

extern "C" {
    fn pow(input: f64, exp: f64) -> f64;
}

fn main() {
    unsafe {
        println!("2^3 is: {}", pow(2.0, 3.0));
    }
}

Listing 11: Der Zugriff auf externe C-Biblitheksfunktionen muss ebenfalls als "unsafe" gekennzeichnet werden

In dem Beispiel wird die Potenzfunktion aus der C-Standardbibliothek aufgerufen. Der externe C-Block definiert dabei die Signatur der externen Funktion sowie die Art, wie die Funktion auf Ebene des Assemblers aufzurufen ist. Da es sich um den Aufruf einer unsicheren Funktion handelt, unterliegt das Speichermanagement nicht der Kontrolle des Rust-Compilers, und der Aufruf muss in einem eigenen unsafe-Block erfolgen.

Zusammenfassend lässt sich festhalten, dass die Programmiersprache Rust mit ihren Konzepten zum automatisierten Speichermanagement durch den Compiler große Vorteile gegenüber etablierten System-Programmiersprachen wie C und C++ bietet. Ganze Klassen von Fehlern, die sich in diesen Sprachen durch den Einsatz zusätzlicher Werkzeuge oder Prozesse lediglich verringern lassen, werden in Rust durch das Design der Sprache sowie durch den Compiler bereits bei der Übersetzung abgefangen. Dabei bietet Rust die gleiche Flexibilität und Performance, die man von den bisher verwendeten Sprachen gewohnt ist. Gepaart mit einer mächtigen Toolchain und einer äußerst hilfsbereiten Community steht Rust eine glänzende Zukunft als System-Programmiersprache bevor.

Jens Siebert
ist Senior Software Engineer bei der doks.innovation GmbH in Kassel. Er ist seit rund 20 Jahren in unterschiedlichen Rollen in der Softwareentwicklung tätig und hat bereits diverse Artikel in Fachzeitschriften veröffentlicht sowie Vorträge auf Fachkonferenzen gehalten.

Ferris Talks – die Kolumne für Rustaceans
Ferris Talks – Neuigkeiten zu Rust: Die Kolumne für Rustaceans
Ferris Talks – Neuigkeiten zu Rust: Die Heise-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 die beiden Rust-Experten Rainer Stropek und Stefan Baumgartner 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 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. Wer die beiden beruflich treffen möchte, kann sie unter anderem als Vortragende und als Workshop-Trainer bei den Konferenzen von Heise erleben, so zuletzt bei der Rust-Konferenz 2021.

(sih)