Sprachwahl im Backend

Rust oder Go: wo die Trennlinie wirklich verläuft

Go bringt ein Team in zwei Wochen zum Liefern, Rust in zwei Monaten. Ob sich das lohnt, entscheidet nicht der Geschmack, sondern was ihr an Latenz und Speicher zusagen müsst.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Entschieden wird nach Benchmarks, die im eigenen Dienst niemand nachrechnet

Der Ablauf ist bekannt: Ein neuer Dienst steht an, jemand bringt eine Messung mit, in der Rust um ein Vielfaches vor Go liegt, und die Diskussion ist gelaufen. In einem echten Backend geht die Zeit aber in Datenbankabfragen, Netz, Serialisierung und Warten. Ein Dienst, der acht Millisekunden in der Datenbank und vier Zehntel im eigenen Code verbringt, wird durch einen Sprachwechsel nicht schneller, er wird nur später fertig.

In die Rust-Richtung falsch entschieden kostet Kalenderzeit. Nach drei Monaten ringt ein Teil des Teams noch mit Eigentumsfragen in asynchronem Code, die Fahrpläne sind verschoben, und der Dienst tut das, was eine Go-Fassung nach sechs Wochen getan hätte. Danach hat Rust im Haus einen Ruf, und der ist schwerer zu reparieren als der Code.

In die Go-Richtung falsch entschieden kostet später und stiller. Ein Dienst mit einer harten Zusage zur Antwortzeit hat wenig Spielraum für die Rechenzeit, die der Sammler nebenher belegt, und für die Lastspitzen, bei denen viel gleichzeitig alloziert wird. Dazu kommt der Speicher: Die Laufzeitumgebung hält einen Puffer vor, und bei dreistelliger Instanzzahl steht das als Posten in der Cloud-Rechnung. Rechenarbeit in der Anfrage, etwa Bilder, Kryptografie oder große Nutzlasten, ist die dritte Stelle, an der der Abstand sichtbar bleibt.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der direkte Vergleich

Die Kriterien, an denen die Wahl tatsächlich hängt

Verglichen werden hier keine Messreihen aus fremden Projekten, sondern die Eigenschaften, die sich in deinem Dienst bemerkbar machen: Einarbeitung, Antwortzeit unter Last, Speicher, Rechenarbeit, Ökosystem und Lesbarkeit für den nächsten Kollegen.

Go

kleine Sprache mit Laufzeitumgebung, Garbage Collector und Goroutinen, in etwa zwei Wochen erlernbar

Rust

keine Laufzeitumgebung und kein Sammler, dafür Eigentumsregeln, die der Compiler vor dem ersten Start prüft

Wie schnell liefert jemand, der die Sprache heute noch nicht kann?

Go

Wer Java, C# oder Python kann, schreibt in der ersten Woche brauchbaren Go-Code. Die Sprache ist bewusst klein gehalten, und die Spezifikation liest sich an einem Nachmittag durch.

Rust

Kompilierender Code entsteht auch hier in Tagen. Bis jemand eigene Datenstrukturen entwirft, ohne gegen die Eigentumsregeln zu laufen, vergehen erfahrungsgemäß mehrere Wochen.

Wie berechenbar ist die Antwortzeit unter Last?

Go

Die Phasen, in denen der Sammler das Programm anhält, sind sehr kurz. Spürbar wird er über die Rechenzeit, die er nebenher belegt, und über Lastspitzen, bei denen viel gleichzeitig alloziert wird.

Rust

Speicher wird freigegeben, wenn der Besitzer endet. Es gibt keinen Sammler, der irgendwann läuft, und damit auch keine Schwankung aus dieser Richtung.

Wie viel Arbeitsspeicher belegt ein Dienst im Dauerbetrieb?

Go

Die Laufzeitumgebung hält Halden-Speicher als Puffer vor. Ein schlanker Dienst startet trotzdem klein, bei vielen Instanzen summiert sich der Aufschlag aber sichtbar.

Rust

Ohne Laufzeitumgebung und ohne Haldenpuffer bleibt der belegte Speicher nah an dem, was die Daten tatsächlich brauchen, und er schwankt weniger.

Wie sieht es bei Rechenarbeit in der Anfrage aus?

Go

Für die meisten Aufgaben schnell genug. Bei engen Schleifen über große Datenmengen bleibt Abstand, unter anderem weil du weniger Kontrolle über Speicherlayout und Kopien hast.

Rust

Übersetzt ohne Zwischenschicht auf Maschinencode, mit Kontrolle über Layout und Kopien. Rechenschleifen lassen sich mit Rayon über alle Kerne verteilen, ohne dass Datenrennen möglich werden.

Wie gut ist das Ökosystem für Cloud- und Betriebswerkzeuge?

Go

Kubernetes, Docker, Terraform und Prometheus sind selbst in Go geschrieben. Clients, Operatoren und Erweiterungen für diese Welt gibt es dort zuerst und am vollständigsten.

Rust

Für HTTP-Dienste, Datenbanken, Serialisierung und Kryptografie ist die Lage gut. Bei Clients für Betriebswerkzeuge greifst du öfter zu einer Fremdbibliothek oder schreibst direkt gegen die HTTP-Schnittstelle.

Wie leicht liest jemand Neues den bestehenden Code?

Go

Die Sprache lässt wenige Wege zu, die Fehlerbehandlung sieht überall gleich aus, und über Formatierung wird nicht diskutiert.

Rust

Traits, Generics und Makros geben viel Ausdruckskraft. Eine Codebasis, in der jemand davon reichlich Gebrauch gemacht hat, kostet beim Einstieg echte Zeit.

Was passt wann

Wenn der Dienst überwiegend auf Datenbank und Netz wartet und in wenigen Wochen stehen soll
nimm Go und steck die gesparte Zeit in Tests und in die Betriebsfähigkeit.
Wenn eine Zusage zur Antwortzeit im 99. Perzentil im Vertrag steht oder pro Anfrage gerechnet wird
nimm Rust und plan die Einarbeitung als eigenen Posten in den Fahrplan.
Wenn aus einem laufenden Go-Dienst nur ein rechenintensives Stück heraussticht
lass den Dienst in Go und schreib allein dieses Stück als eigene Komponente in Rust.

Fünf Fragen, und die Sprache steht fest

  1. 01 Wie viel Zeit einer Anfrage verbringt dein eigener Code? Bei wenig zählt Go.
  2. 02 Welchen Wert muss das 99. Perzentil halten? Ohne harte Zusage bleibt es bei Go.
  3. 03 Wie viele Instanzen laufen dauerhaft? Erst im dreistelligen Bereich zählt der Speicher.
  4. 04 Steckt Rechenarbeit in der Anfrage, etwa Bilder, Krypto oder große Nutzlasten?
  5. 05 Wie oft wechselt das Team? Häufige Wechsel sprechen für die kleinere Sprache.
Was du mitnimmst

Was du danach in einer Sitzung entscheidest

Die Wahl hängt an fünf Zahlen und einer Einschätzung zum Team. Alle fünf liegen entweder schon vor oder lassen sich an einem Nachmittag messen, und danach ist die Diskussion in der Regel beendet.

Die Latenzzusage konkret machen

Nicht "muss schnell sein", sondern: welches Perzentil, welcher Wert, gemessen an welcher Stelle. Wenn im 99. Perzentil ein paar Millisekunden Luft sind, ist die Frage nach dem Garbage Collector beantwortet und Go steht.

Rechenzeit von Wartezeit trennen

Miss, welcher Anteil einer Anfrage im eigenen Code liegt und welcher im Warten auf Datenbank und Netz. Ist der eigene Anteil klein, bewegt die Sprachwahl die Gesamtzahl kaum, und die Entscheidung fällt an anderen Kriterien.

Speicher pro Instanz einrechnen

Ein Rust-Dienst kommt ohne Laufzeitumgebung und ohne Haldenpuffer aus und bleibt näher an dem, was die Daten selbst brauchen. Bei fünf Instanzen ist das Rauschen, bei mehreren hundert ist es eine Zeile im Budget, die du vorher ausrechnen kannst.

Die Nebenläufigkeit richtig lesen

Goroutinen bekommst du mit der Sprache, ohne Abhängigkeit und ohne Entscheidung. Async in Rust ist eine Bibliotheksentscheidung, in der Praxis Tokio, und sie färbt die Signaturen: Eine Funktion async zu machen zieht async bis zum Einstiegspunkt hoch.

Die Einarbeitung als Termin behandeln

In Go schreibt eine erfahrene Backend-Person in der ersten Woche Code, der ins Review kann. In Rust entsteht kompilierender Code ebenfalls in Tagen, die Entwurfsentscheidungen darüber, wem Daten gehören und wann ein Klon angebracht ist, brauchen aber Wochen an echtem Code.

Fluktuation einpreisen

Go ist bewusst klein gehalten, es gibt wenige Wege, dieselbe Sache zu schreiben, und die Formatierung ist nicht verhandelbar. Wer neu dazukommt, liest bestehenden Code in Tagen. Rust gibt dir Traits, Generics und Makros, und eine Codebasis, in der jemand davon reichlich Gebrauch gemacht hat, kostet beim Einstieg echte Zeit.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Nebenläufigkeit: Goroutinen kosten nichts, async färbt den Code

In Go startest du mit einem vorangestellten go eine Goroutine, die Laufzeitumgebung verteilt sie auf Betriebssystem-Threads, und die Verständigung läuft über Kanäle. Eine Goroutine ist billig, hunderttausend gleichzeitig sind normal. Blockierende Aufrufe sind unproblematisch, weil der Planer sie kennt und die Goroutine so lange parkt.

In Rust gibt es zwei Welten. Threads aus der Standardbibliothek sind echte Betriebssystem-Threads und die richtige Wahl für Rechenarbeit. Daneben steht async mit async fn und await, das eine Laufzeitumgebung braucht, und diese Laufzeitumgebung ist eine Bibliotheksentscheidung, in der Praxis Tokio. Async ist ansteckend: Eine asynchrone Funktion lässt sich nur aus einem asynchronen Zusammenhang heraus abwarten, deshalb zieht sich die Entscheidung durch die gesamte Aufrufkette bis nach oben.

Der Unterschied, der im Betrieb zuerst weh tut, ist ein anderer. Ein blockierender Aufruf innerhalb einer asynchronen Aufgabe belegt einen Arbeiter-Thread der Laufzeitumgebung und bringt damit fremde Aufgaben zum Stillstand, die auf demselben Arbeiter liegen. In Go parkt derselbe Aufruf einfach die Goroutine. Das ist die mit Abstand häufigste Überraschung, wenn ein Go-Team nach Rust wechselt, und der Grund, warum blockierende Arbeit dort ausdrücklich auf eigene Threads gehört.

Welche Fehlerklassen die eine Sprache abnimmt und die andere nicht

Datenrennen sind in sicherem Rust ausgeschlossen. Der Compiler prüft über Send und Sync, ob ein Wert eine Thread-Grenze überschreiten darf, und geteilter veränderlicher Zugriff ohne Sperre kommt gar nicht erst durch. In Go ist die klassische Fehlerquelle eine Map oder ein Slice, in die mehrere Goroutinen schreiben. Das übersetzt anstandslos und fällt unter Last auseinander. Der eingebaute Race Detector, den du mit go test -race einschaltest, findet es, allerdings nur, wenn ein Testlauf den betreffenden Pfad tatsächlich nimmt.

Beim Umgang mit Fehlern und mit fehlenden Werten unterscheiden sich beide ebenfalls. In Go ist der Fehler ein zusätzlicher Rückgabewert, den du mit einem Unterstrich wegwerfen kannst, und der Zugriff auf einen nil-Zeiger bricht zur Laufzeit ab. In Rust ist Result ein Typ, der eine Warnung auslöst, wenn du ihn nicht auswertest, und Option zwingt dich, den leeren Fall zu behandeln, bevor du an den Wert kommst.

Was keine der beiden Sprachen ausschließt: Verklemmungen zwischen zwei Sperren, unbegrenzt wachsende Warteschlangen, Logikfehler und blockierende Aufrufe an der falschen Stelle. Wer Rust mit "keine Nebenläufigkeitsfehler mehr" verkauft, hat die Zusage falsch wiedergegeben. Sie lautet: keine Datenrennen.

Was der Betrieb von beiden merkt

Beide liefern ein einzelnes Programm ohne Laufzeitumgebung daneben, und beide starten in Millisekunden, was für Dienste zählt, die auf null skalieren oder als kurzlebige Aufgaben laufen. Ein Detail lohnt trotzdem die Aufmerksamkeit: Ein Go-Programm ohne cgo ist standardmäßig statisch gebunden, ein Rust-Programm für Linux bindet die C-Bibliothek des Systems dynamisch ein. Wenn du ein wirklich statisches Programm für ein minimales Container-Abbild brauchst, baust du in Rust gegen das musl-Ziel, und das ist eine Zeile in der Baukonfiguration, an die man einmal denken muss.

Bei Metriken, Ablaufverfolgung und Protokollierung ist Go im Vorteil, weil die Werkzeuge dieser Welt selbst in Go geschrieben sind und die zugehörigen Bibliotheken dort zuerst erscheinen. In Rust deckt das tracing-Ökosystem denselben Bedarf ab, Anbieter-Bibliotheken kommen aber häufig später und manchmal nur als Beitrag aus der Gemeinschaft.

Die Bauzeit ist der Punkt, den Teams im Vergleich am häufigsten übersehen. Go baut schnell, das war ein erklärtes Entwurfsziel. Rust baut langsam. Bei einem mittelgroßen Projekt ist das der Unterschied zwischen einem Durchlauf von zwei Minuten und einem von einer Viertelstunde, und er wirkt bei jeder Änderung. Cargo check für die Rückmeldung beim Schreiben, eine Aufteilung in mehrere Crates und ein Zwischenspeicher für Bauartefakte im Bauserver holen einen Teil davon zurück, aber eben nur einen Teil.

Die Fälle, in denen die Frage gar nicht gestellt werden muss

Wenn das Team bereits Go schreibt und der nächste Dienst wieder eine Schnittstelle mit Datenbank dahinter ist, gibt es nichts zu entscheiden. Der Wechsel müsste sich gegen eine funktionierende Praxis rechnen, und das tut er in diesem Fall nicht.

Umgekehrt gibt es Fälle, in denen Go schlicht ausfällt. Wenn eine native Bibliothek entstehen soll, die aus Python, Node oder der JVM heraus aufgerufen wird, ist das Rust-Gebiet: Go bringt seine eigene Laufzeitumgebung mit, die Threads besitzen will, und die Grenze über cgo ist teuer und heikel. Dasselbe gilt für Software ohne Betriebssystem darunter, etwa auf einem Mikrocontroller. Und bei WebAssembly können zwar beide das Ziel bedienen, die Werkzeuge und Bibliotheken auf der Rust-Seite sind dort aber weiter.

Der dritte klare Fall ist ein sehr kleines Team mit häufigem Wechsel. Dann gewinnt die Sprache, die weniger Wege kennt, dieselbe Sache zu schreiben, und das ist Go. Diese Überlegung wiegt in der Praxis schwerer als jeder Messwert, weil sie jeden Monat wirkt und nicht nur im Lastversuch.

Dazu passende Kurse

Wenn diese Abwägung im Team ansteht und nicht jeder dieselbe Vorstellung von Rust mitbringt, sind Rust-Kurse für Backend-Teams der schnellste Weg zu einer gemeinsamen Grundlage.

Fällt die Wahl auf die kleinere Sprache, kannst du bei cmt Go von Grund auf lernen .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Sehr angenehmes, gut vorbereitetes Seminar, kann ich nur empfehlen.
Rust Fortgeschrittenenkurs

Häufige Fragen

Ist Rust wirklich schneller als Go?
In engen Rechenschleifen ja, und zwar messbar. In einer typischen Backend-Anfrage verschwindet der Unterschied hinter Datenbank- und Netzzeit. Verlässlich sichtbar bleibt er an zwei Stellen: am oberen Ende der Latenzverteilung, weil kein Sammler dazwischenfunkt, und beim Speicher pro Instanz.
Können wir beide Sprachen nebeneinander betreiben?
Ja, und das ist der Normalfall. Dienste reden über HTTP oder gRPC miteinander, und dahinter ist die Sprache egal. Der Preis sind zwei Werkzeugketten, zwei Sätze Basis-Abbilder und zwei Bauprozesse. Das sollte eine bewusste Entscheidung sein und nicht das Ergebnis einer persönlichen Vorliebe in einem einzelnen Team.
Sind Goroutinen und Rust-Aufgaben dasselbe?
Nein. Beide sind billige Einheiten der Nebenläufigkeit, der Unterschied liegt daneben. Goroutinen kommen mit der Sprache, die Laufzeitumgebung ist keine Wahl. In Rust ist die Laufzeitumgebung eine Abhängigkeit, und async färbt die Signaturen bis nach oben. Dazu kommt das Verhalten bei blockierenden Aufrufen: Go parkt die Goroutine, in Rust belegt der Aufruf einen Arbeiter-Thread und bremst fremde Aufgaben mit aus.
Wie lange braucht ein Go-Team für den Umstieg auf Rust?
Kompilierender Code entsteht in Tagen. Die realistische Marke für "entwirft neuen Code ohne Hilfe" liegt bei mehreren Wochen an echtem Code mit Review durch jemanden, der es schon gemacht hat. Was Go-Leute dabei am meisten überrascht, ist selten der Borrow-Checker, sondern die Erkenntnis, dass die Entscheidung für async sich durch die gesamte Aufrufkette zieht.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Rust-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.