Migration von Go zu Rust
(corrode.dev)- Der Wechsel von Go zu Rust ist weniger eine Entscheidung für mehr Geschwindigkeit als dafür, Probleme wie
nil, Fehlerbehandlung, Data Races und Ressourcenlebensdauer in Compile-Time-Garantien zu überführen - Go punktet mit schnellen Compile-Zeiten, einfachen Goroutines und einem starken Backend-Ökosystem, aber Rust verhindert mit
Option,ResultundSend/Syncmehr Fehler bereits im Typsystem - Rusts Borrow Checker sowie
async/awaitbringen eine Lernkurve und Kosten bei der Benutzbarkeit mit sich; auch die Compile-Zeiten sind gegenüber Go klar als Rückschritt zu sehen - Für den Umstieg eignet sich eher eine Strategie, die mit klar abgegrenzten Komponenten beginnt – etwa Hot-Path-Services, Worker oder einzelne Endpunkte hinter einem Gateway – statt mit einer vollständigen Neuschreibung
- Die erwarteten Effekte lassen sich als 20–60 % weniger CPU, 30–50 % weniger Speicher, flachere P99-Latenzen sowie weniger Ausfälle durch
nil-Dereferenzierungen und Race Conditions zusammenfassen
Fokus des Wechsels
- Der Wechsel von Go zu Rust ist weniger eine Frage von „Ist Rust schneller?“ als eine Abwägung von Korrektheitsgarantien, Runtime-Kompromissen und Unterschieden in der Developer Experience
- Im Zentrum des Vergleichs stehen Backend-Services, wobei Go mit kleinen statischen Binärdateien, einer auf Netzwerke ausgerichteten Standardbibliothek und einem Ökosystem für HTTP-Server, gRPC und Datenbanken als Referenz dient
- Teile des Inhalts lassen sich auch auf CLI-Tools, Embedded-Firmware und Game Engines anwenden, diese sind aber nicht das optimierte Ziel
- Als Hintergrundmaterial werden der Beitrag von 2017 “Go vs Rust? Choose Go.” und der Beitrag des Shuttle-Teams “Rust vs Go: A Hands-On Comparison” genannt
- Go ist eine erfolgreiche Sprache, doch Designentscheidungen wie die breite Verwendung von
nil, eine Fehlerbehandlung, die eher auf Disziplin als auf Typen setzt, und lange fehlende Generics sind im Vergleich zu Rust zentrale Streitpunkte - In der JetBrains Developer Ecosystem Survey wird Go als Sprache mit einem Anteil von 17–19 % aktiver Entwickler aufgeführt; Rust wächst zwar stetig, bleibt aber bei einem kleineren Anteil
Tooling
- Sowohl Go als auch Rust verfügen über ein Batteries-included-Tooling, das Build, Test, Formatierung, Linting und Dependency-Management über konsistente Interfaces bereitstellt
cargobietet als primäres Tool ein breiteres Spektrum an Funktionen, die den Go-Werkzeugen entsprechengo.mod/go.sum→Cargo.toml/Cargo.lock: Projektkonfiguration und Dependency-Manifestgo get/go mod tidy→cargo add/cargo update: Dependencies hinzufügen und auflösengo build→cargo build: Kompilierengo run .→cargo run: Nach dem Build ausführengo test ./...→cargo test: Testsgo vet ./...→cargo clippy: Linter;Clippyist dabei deutlich meinungsstärker alsvetgofmt/goimports→cargo fmt: Auto-Formatter ohne Konfigurationgolangci-lint run→cargo clippy -- -D warnings: Strikter Lint-Modusgo doc→cargo doc --open: API-Dokumentation erzeugen und öffnenpprof→cargo flamegraph/samply: CPU-Profilinggovulncheck→cargo audit: Schwachstellenprüfung auf Basis einer Advisory-Datenbank
- In Go werden Lücken oft mit Third-Party-Tools wie
golangci-lint,mockgen,airodergoreleasergeschlossen, während Rusts primäres Ökosystem mehr Funktionen standardmäßig abdeckt - Selbst wenn externe Crates nötig sind, lassen sie sich mit einem einzigen
cargo install cargo-nextestinstallieren und verhalten sich dann mitcargo nextestwie native Tools – ähnlich wiecargo watchodercargo nextest - Der Vorteil von
gofmtundrustfmtliegt weniger in Detailfragen des Stils als darin, Stil-Debatten in Code-Reviews zu eliminieren- Zitat aus Rob Pikes Go Proverbs: “Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.”
Zentrale Unterschiede zwischen Go und Rust
- Beide Sprachen sind kompilierte, statisch typisierte Sprachen mit Deployment als einzelne Binärdatei und einem starken Nebenläufigkeitsmodell; der Unterschied liegt jedoch darin, was der Compiler garantiert und wie stark sich das Runtime-Verhalten kontrollieren lässt
- Die wichtigsten Vergleichspunkte sind:
- Stable Release: Go 2012, Rust 2015
- Typsystem: Go ist statisch und strukturell typisiert und unterstützt seit 1.18 Generics; Rust ist statisch und nominal typisiert und unterstützt Generics, Traits und Lifetimes
- Speicherverwaltung: Go nutzt nebenläufige Garbage Collection mit niedriger Latenz, Rust basiert auf Ownership und Borrowing und hat keine GC
- Null-Sicherheit: In Go ist
nilweit verbreitet, Rust kennt kein Null und nutztOption<T>als Ersatz auf Typebene - Fehlerbehandlung: Go verwendet das
error-Interface undif err != nil { ... }, Rust verwendetResult<T, E>, den?-Operator und vollständiges Pattern Matching - Nebenläufigkeit: Go setzt auf CSP mit Goroutines und Channels, Rust auf
async/awaitauftokiosowie auf Channels und Threads - Abbruch/Cancelation: Go nutzt das konventionsbasierte
context.Context, Rust explizite und typgeprüfte Weitergabe etwa mitCancellationToken - Data Races: Go erkennt sie probabilistisch zur Laufzeit mit
-race, Rust erkennt sie zur Compile-Zeit mitSend/Sync - Compile-Zeit: Go ist sehr schnell, Rust ist vor allem bei Clean Builds langsam
- Runtime: Go bringt eine Runtime von rund 2 MB und GC mit, Rust hat abgesehen von
libckeine Runtime oder kann mit MUSL vollständig statisch gebaut werden - Größe des Ökosystems: Go etwa 750.000+ Module, Rust 250.000+ Crates
- Prüfungen für
nil-Behandlung, Fehlerweitergabe, Data Races, Ressourcenlebensdauer, Abbruch und Generics, die in Go auf Konventionen, Tools und Laufzeiterkennung beruhen, werden in Rust in das Typsystem verlagert - Rusts
Mutex<T>erlaubt den Zugriff auf den inneren Wert nur über einen per.lock()erhaltenen Guard und entfernt damit den ganzen „Pfad, auf dem man vergisst zu locken“, bereits auf Typebene - Dasselbe Muster wiederholt sich bei
Option,Result,&mut T,Send/Syncund RAII-Guards; mit wachsender Vertrautheit übernimmt der Compiler damit einen Teil der mentalen Checkliste
Die Grenzen von Go, die zu einer Prüfung von Rust führen
- Da Go für die meisten Backend-Workloads ausreichend schnell ist, liegen die Hauptgründe für eine Prüfung von Rust eher in der Ausführlichkeit der Fehlerbehandlung, der Gefahr von
nil-Zeigern und dem Fehlen ausgefeilter Features des Typsystems wie Enums und Traits als in der Geschwindigkeit - Go-Interfaces sind kein vollwertiger Ersatz für Rust-Traits, und da in der Standardbibliothek ein
Set-Typ fehlt, sind idiomatische Umgehungen wiemap[T]struct{}nötig -
nil-Panics in der Produktion- Ein Go-Service kann monatelang normal laufen und dann auf einem bestimmten Codepfad in eine goroutine-Panic laufen, weil eine Prüfung auf einen
nil-Zeiger übersehen wurde - Im Beispiel gibt
Find(*User, error)zurück, und bei „not found“ isterrorzwarnil, die Prüfung vonuserbleibt aber dem Aufrufer überlassen user.Account.Notify()kann abstürzen, wennuseroderAccountnilist- Linter und IDE-Prüfungen wie
nilawayundstaticcheckerkennen einen Teil dieser Fälle, sind aber Opt-in, probabilistisch und funktionieren nicht zuverlässig über Paketgrenzen hinweg - Rusts
Option<T>verhindert das Dereferenzieren, solange der FallNonenicht behandelt wurde, und beseitigt damit diese Kategorie von Ausfällen
- Ein Go-Service kann monatelang normal laufen und dann auf einem bestimmten Codepfad in eine goroutine-Panic laufen, weil eine Prüfung auf einen
-
Datenrennen, die
-racenicht erfasst hatgo test -raceist ein hervorragendes Tool, aber als Laufzeit-Detektor findet es nur Datenrennen, die während der Tests tatsächlich ausgeführt wurden- In Go wird auch Code kompiliert, in dem zwei goroutines ohne Lock eine map verändern, und dieser kann dann unter Last in der Produktion scheitern
- In Rust erfordert gemeinsam genutzter veränderbarer Zustand zwischen Threads Typen, die
SendundSyncimplementieren, und der Versuch, eine gewöhnlicheHashMapzwischen Threads zu teilen, kompiliert nicht - Man wird dazu gezwungen, entweder
Arc<Mutex<...>>,Arc<RwLock<...>>oder Channels zu verwenden, wodurch Race Conditions zu Typfehlern werden - Paul Dix nennt die Beseitigung von Datenrennen ausdrücklich als Grund für das Rewrite von InfluxDB 3.0
- "[The main benefit is] fearless concurrency — eliminating data races essentially, which we had before. Really gnarly bugs in version 1 of Influx due to that."
- Quelle: Paul Dix, Founder & CTO, InfluxData, Rust in Production
-
Komponierbare Fehlerbehandlung
- Gos
if err != nil { return err }kann die eigentliche Logik einer Funktion verwässern, und das Einbetten von Kontext mitfmt.Errorf("doing X: %w", err)hängt von Disziplin statt von Compiler-Regeln ab - Im Lobste.rs-Thread widersprechen erfahrene Go-Entwickler und argumentieren, dass
errcheckundgolangci-lintdie meisten ausgelassenen Fehlerbehandlungen erkennen und explizitesif err != nillesbarer sei als dichte?-Ketten - Peter Bourgon stellt die explizite Fehlerbehandlung in Go als bewusst intendierten kulturellen Wert dar
- "I think that error handling should be explicit, this should be a core value of the language."
- Quelle: Peter Bourgon, GoTime #91, zitiert in Dave Cheneys Zen of Go
- Rusts
Result<T, E>ist selbst Teil der Typsignatur und kann daher nicht vergessen werden; mit perthiserror::Errordefinierten Enums und#[from]erhält man Fehlerkonvertierung und Vollständigkeitsprüfungen - Fügt man eine neue Error-Variante hinzu, zeigt der Compiler an, welche
match-Stellen aktualisiert werden müssen
- Gos
-
Generics ohne Boxing
- Die Generics in Go 1.18 sind nützlich, haben aber Einschränkungen wie das Fehlen von Methoden mit Typparametern, GC shape stenciling und mitunter überraschende Performance-Eigenschaften
- Rust-Generics werden monomorphisiert, sodass jede Instanziierung spezialisierten Code erzeugt und keine Laufzeitkosten verursacht
- In Kombination mit Traits sind Zero-Cost-Abstractions möglich
- Das ist weniger wichtig für Handler-Code als für gemeinsam genutzte Infrastruktur wie Middleware, Generic Repositorys, Decoder und Parser; Go landet in solchen Bereichen oft wieder bei
interface{}/anyund Type Assertions
-
Vorhersehbare Latenz
- Gos GC ist ausgezeichnet, nebenläufig und latenzarm und gut auf typische Service-Workloads abgestimmt, aber „low-pause“ ist nicht gleich „no-pause“
- In Szenarien mit vielen Allokationen kann der P99-Latency-Tail schlechter ausfallen als bei einer Rust-Implementierung, die auf dem Hot Path nicht alloziert
- In latenzsensitiven Systemen wie Trading, Real-Time-Bidding, Netzwerk-Proxys oder Ingestion mit hohem Durchsatz ist das Fehlen von GC-Pausen ein realer Vorteil
- Stephen Blum sagt, dass Rust nötig sei, um bei der Größenordnung von PubNub die erforderliche Preis-pro-Dollar-Performance-Kapazität zu erreichen
- "Go is great at our scale, but we really need something that is going to give us the price-per-dollar performance capacity that we need, and Rust is going to get us there. That’s why basically everything is heading towards Rust these days."
- Quelle: Stephen Blum, CTO, PubNub, Rust in Production
Rust-Entsprechungen zu Go-Mustern
- Ein schneller Weg, sich an Rust zu gewöhnen, besteht darin, bereits bekannte Go-Muster auf die entsprechenden Rust-Muster abzubilden
- Ein längeres Beispiel, das denselben Backend-Service in beiden Sprachen implementiert, findet sich im Shuttle comparison
-
Fehlerbehandlung:
if err != nilvs.Result<T, E>- Go gibt nach
os.ReadFile(path)undjson.Unmarshalmitif err != nileinen Fehler zurück, der mit Kontext angereichert ist - Rust besteht aus
fs::read_to_string(path)?,serde_json::from_str(&data)?,Ok(cfg) - Der Operator
?ersetzt das Musterif err != nil { return err }und übernimmt auch die Typumwandlung, wennFrom<E1> for E2implementiert ist #[from]austhiserrorunterstützt diese Umwandlung idiomatisch
- Go gibt nach
-
Null:
nilvs.Option<T>- In Go gibt
GetUser(id string) *Usernilzurück, wenn kein Benutzer gefunden wird, und wenn der Aufruferfmt.Println(u.Name)ausführt, führt das beinilzu einer Panic - In Rust gibt
get_user(id: &str) -> Option<User>entwederSome(User)oderNonezurück let user = get_user("123"); println!("{}", user.name);führt zu einem Compilerfehler, weiluserkeinUser, sondern einOption<User>ist- Mit
match get_user("123")müssen sowohlSome(u)als auchNonebehandelt werden - In sicherem Rust gibt es kein
nil, und Referenzen können nicht null sein
- In Go gibt
-
Interfaces vs. Traits
- Go-Interfaces sind strukturell, und ein Typ erfüllt ein Interface implizit
- Rust-Traits sind nominal und müssen explizit implementiert werden
- Der Go-Ansatz eignet sich gut für spontanes Duck Typing, der Rust-Ansatz ist besser für Refactoring und Discoverability, und man kann per grep bestimmte Trait-Implementierungen finden
- Generische Funktionen mit Trait Bound wie
fn handle<R: Reader>(r: R)decken in den meisten Fällen alles ab, und dank Monomorphisierung gibt es kein Runtime-Dispatch - Um heterogene Implementierungen mit Runtime-Dispatch zu speichern, verwendet man
Box<dyn Trait>oderArc<dyn Trait>
-
Goroutine vs. Async-Task
- Gos Nebenläufigkeitsmodell ist so einfach wie
go doWork(ctx, input); Goroutinen sind leichtgewichtig, und die Runtime schedult sie über OS-Threads - Ein großer Vorteil von Go ist, dass es keinen syntaktischen Unterschied zwischen sequentiellem und parallelem Code gibt
- Rust verwendet in Backend-Services fast immer
async/awaitauf einemtokio-Executor - Async-Funktionen geben ein
Futurezurück und werden erst ausgeführt, wenn sie awaited oder gespawnt werden - Der Compiler verfolgt
Send/Syncvor und nach.await-Punkten; wenn ein non-Send-Wert über ein await hinaus gehalten wird, entsteht ein Compilerfehler - Da es keine goroutine-artige eingebaute Präemption gibt, kann bei lang laufender CPU-gebundener Arbeit innerhalb einer Async-Task der Executor verhungern; solche Arbeit sollte an
tokio::task::spawn_blockingoderrayonübergeben werden
- Gos Nebenläufigkeitsmodell ist so einfach wie
-
context.Contextvs.CancellationToken- In Go wird
context.Contextan alle blockierenden Aufrufe weitergegeben - Rust hat kein eingebautes
context.Context; das nächstliegende Gegenstück für Abbruch isttokio_util::sync::CancellationToken - Timeouts werden mit
tokio::time::timeout(dur, fut)umgesetzt, indem das Future umhüllt wird - Deadlines und Werte werden statt über ein einzelnes Context-Objekt oft über explizite Argumente oder
tracing-Spans weitergegeben - Zitat aus Dave Cheneys The Zen of Go:
- „Go doesn’t have a way to tell a goroutine to exit. There is no stop or kill function, for good reason. If we cannot command a goroutine to stop, we must instead ask it, politely.”
- In Go ist diese „höfliche Bitte“ üblicherweise das weitergereichte
context.Context; in Rust sind esCancellationTokenoderwatch-Channels, aber der Compiler kann auf Auslassungen hinweisen
- In Go wird
-
Strings:
stringvs.Stringund&str- Gos
stringist ein UTF-8-Byte-Slice; bei Zuweisung wird der Header kopiert, während die zugrunde liegenden Bytes als unveränderliche Struktur gemeinsam genutzt werden - Rust teilt das in zwei Typen auf
String: besitzt die Daten, ist auf dem Heap alloziert und vergrößerbar&str: eine geborgte Sicht auf andere String-Daten und entspricht in den meisten Fällen einem Go-string-Parameter
- Die Faustregel lautet, für Argumente
&strzu verwenden und beim Erzeugen neuer DatenStringzurückzugeben - Die Trennung von
&strundStringzeigt in komprimierter Form Rusts Modell von „borrow vs own“
- Gos
Bewertung von Go-Generics
- Go führte Generics in Version 1.18 im März 2022 ein, also 13 Jahre nach dem Start der Sprache
- Generics sind nützlich, bieten aber nicht in vollem Umfang die Vorteile, die man von Rust, Haskell oder modernem C++ erwartet, und bringen zugleich einen erheblichen Teil der Nachteile generischer Typsysteme mit sich
-
Die Standardbibliothek nutzt sie kaum
- Auch drei Jahre nach der Einführung von Generics vermeidet die Go-Standardbibliothek sie größtenteils
sort.Slicenimmt weiterhin einefunc(i, j int) bool-Closure statt einescmp.Ordered-Constraint ansync.Mapist weiterhin alsany/anytypisiert- Vorhandene generische Helper finden sich nur in wenigen Paketen wie
slices,maps,cmpund einigen Einträgen untersync - Das Go-1-Kompatibilitätsversprechen erklärt teilweise, warum sich bestehende nicht-generische APIs nur schwer umbauen lassen, aber anders als Rust nutzt Go Generics nicht als primäres Werkzeug
- In Rust sind Generics von Anfang an in
Option<T>,Result<T, E>,Vec<T>,HashMap<K, V>,Iterator,From/Intosowie in allen Collections und Smart Pointern verankert
-
Kein Trait-System, nur strukturelle Constraints
- Rust-Generics sind mit Traits verknüpft, die ad-hoc-Polymorphismus, Supertraits, Associated Types, Blanket Impls und Coherence abdecken
- Go-Constraints ähneln eher Interfaces, die um den
~-Operator für Type-Set-Membership ergänzt wurden - Go kennt keine Supertrait-Hierarchie wie in Rusts
trait Ord: Eq + PartialOrd, keine Associated Types wietype Item;inIteratorund keine Blanket Impls wieimpl<T: Display> ToString for T - In Go lassen sich Methoden mit Typparametern nicht verwenden, daher sind Formen wie
func (s Set[T]) Map[U](<https://corrode.dev/learn/migration-guides/go-to-rust/f func(T>) U) Set[U]nicht möglich - Sobald eine Abstraktion über „eine Funktion, die für ein beliebiges
Tmit einigen Operationen funktioniert“ hinausgeht, fällt Go wieder aufany, Type Assertions, Codegenerierung und Runtime-Reflection zurück
-
Unterschiede bei Typinferenz und Implementierungsstrategie
- Rust propagiert Typinformationen über ganze Ausdrücke hinweg, einschließlich Closures, Iterator-Chains und dem
?-Operator - Die Inferenz in Go ist flacher und leitet Typparameter meist aus Funktionsargumenten ab, kann sie aber nicht aus dem Rückgabekontext ableiten, weshalb an der Aufrufstelle oft explizite Type Arguments nötig sind
- Go wählt mit GCShape stenciling and dictionaries einen Mittelweg, um schnelle Compile-Zeiten zu behalten, dafür kann bei jedem Methodenaufruf auf Typparametern eine Indirektion entstehen
- Als Beleg dafür wird der PlanetScale-Artikel angeführt
- Rust erzeugt dagegen jeweils spezialisierten Maschinencode für
Vec<i32>undVec<String>und benötigt kein Runtime-Dispatch - Der Preis der Monomorphisierung ist die Compile-Zeit; beide Sprachen optimieren auf unterschiedliche Ziele hin
- Rust propagiert Typinformationen über ganze Ausdrücke hinweg, einschließlich Closures, Iterator-Chains und dem
-
Schließt die Lücken des Typsystems nicht
- In Rust beseitigen Generics und Traits die meisten Situationen, in denen sonst
Box<dyn Any>oder Runtime-Reflection nötig wären - Go-Generics schaffen es nicht,
any,reflectoder die dominanten Codegenerierungsmuster bei ORMs, Decodern und Mocks zu verdrängen encoding/jsonnutzt weiterhin Reflection,database/sqlweiterhinany, undmockgenerzeugt weiterhin Code- Go-Generics wirken wie ein neues Werkzeug, das in engen Fällen nützlich ist, während Rust-Generics wie ein Fundament funktionieren, ohne das die Sprache zusammenbrechen würde
- In Rust beseitigen Generics und Traits die meisten Situationen, in denen sonst
Rust-Backend-Ökosystem
- Auch im Rust-Ökosystem gibt es für allgemeine Backend-Services inzwischen eine gewisse Konvergenz bei den „Standardoptionen“
- Typische Entsprechungen:
- HTTP-Server: Go
net/http,chi,gin,echo,fiber→ Rustaxumaufhyper - HTTP-Client: Go
net/http,resty→ Rustreqwest - gRPC: Go
google.golang.org/grpc+protoc-gen-go→ Rusttonic+prost - SQL: Go
database/sql,sqlc,sqlx,gorm→ Rustsqlx,sea-orm,diesel - Migrationen: Go
golang-migrate,goose→ Rustsqlx migrate,refinery - JSON: Go
encoding/json,sonic,goccy/go-json→ Rustserde+serde_json - Logging: Go
log/slog,zerolog,zap→ Rusttracing+tracing-subscriber - Metriken: Go
prometheus/client_golang→ Rustmetrics+metrics-exporter-prometheus - Konfiguration: Go
viper,koanf→ Rustconfig/ config-rs,figment - CLI: Go
cobra,urfave/cli→ Rustclapderive - Fehler: Go
errors,pkg/errors→ Rustthiserrorfür Bibliotheken,anyhowfür Binärprogramme - Testen: Go
testing,testify,gomega→ Rust eingebautes#[test],rstest,assert_matches - Mocking: Go
mockgen,moq→ In Rust sind handgeschriebene Fakes idiomatisch,mockallwird ebenfalls verwendet - Hintergrund-Tasks: Go-Goroutines +
errgroup→ Rusttokio::spawn+JoinSet
- HTTP-Server: Go
- Für einen typischen Backend-Service deckt die Kombination
axum+sqlx+tokio+tracing+serde+claplaut Darstellung 90 % dessen ab, was benötigt wird
Borrow Checker und Lernkurve
- Beim Wechsel von Go zu Rust sollte man davon ausgehen, dass man gegen eine Wand laufen wird
- Die Go-Runtime kümmert sich um Speicher und Aliasing selbst, Rust verlagert diese Entscheidungen dagegen ins Typsystem, weshalb in den ersten Wochen Code, der „eigentlich funktionieren müsste“, vom Compiler abgelehnt werden kann
- Muster, auf die Go-Entwickler häufig stoßen:
- Langlebige Referenzen: In Go ist es selbstverständlich, ein
*User, das aus einer Map geholt wurde, lange zu halten, in Rust verhindert diese Ausleihe jedoch Änderungen an der Map, solange sie lebt - Selbstreferenzielle Structs: In Go kann man Daten und einen Iterator über diese Daten in derselben Struct unterbringen, in Rust braucht man dafür
Pin,ouroborosoder ein Redesign - Gemeinsamer veränderbarer Zustand zwischen Goroutines: Das Go-Muster
mu sync.Mutex; data map[K]Vwird in Rust zuArc<Mutex<HashMap<K, V>>> - Referenzen aus Funktionen zurückgeben: Lifetime-Annotationen kommen ins Spiel, ein neues Konzept für Go-Entwickler
- Langlebige Referenzen: In Go ist es selbstverständlich, ein
- Der Borrow Checker sollte nicht als störender „Torwächter“ gesehen werden, sondern als Mechanismus, der real existierende Bugs aufdeckt
- Er filtert bereits zur Compile-Zeit Fälle heraus, in denen Werte nach einem Move erneut verwendet werden, mehrere Threads gleichzeitig dieselben Daten anfassen, Null- oder Dangling-Pointer dereferenziert werden oder Referenzen länger leben als die Werte selbst
- Wenn man das Borrowing-Konzept verinnerlicht, wird der Borrow Checker vom Gegner zum Verbündeten, und erfahrene Rust-Entwickler sagen meist, dass er innerhalb von 4 bis 12 Wochen zu einem Helfer geworden ist
- PubNub-CTO Stephen Blum beschrieb den ersten Monat bei Rustacean Station als „wie damals, als ich zum ersten Mal Programmieren lernte“, weil er sich zwangsläufig mit Borrow Checker und Lifetimes auseinandersetzen musste
clap-Maintainer Ed Page sagte bei Rustacean Station: clap with Ed Page, der Borrow Checker habe ihm geholfen, sich auf Probleme höherer Ebene zu konzentrieren, und auch Dinge gefunden, die seiner eigenen Analyse entgangen waren
Zentrale Hürden beim Umstieg auf Rust
-
Compile-Zeit
- Rust-Compile-Zeiten muss man im Vergleich zu Go klar als Rückschritt sehen; ein sauberer Release-Build eines mittelgroßen Services kann Minuten dauern, während Go fast sofort kompiliert
- Incremental Builds und
cargo checksind praktikabel, und die Compile-Zeiten haben sich über die Jahre verbessert, aber der Unterschied zu Go ist deutlich spürbar - Für die Editier-Schleife sollte man
cargo checknutzen, bei erkennbarem Nutzen in Workspaces aufteilen und Crates mit vielen prozeduralen Makros in separaten Crates halten, damit sie nur bei Änderungen neu kompiliert werden - Mehr dazu findet sich in Tipps zum Verkürzen von Rust-Compile-Zeiten
-
Async-Coloring-Problem
- Die Trennung zwischen
async fnundfnin Rust ist beim Wechsel von Go eine der größten Usability-Verschlechterungen - Async Trait ist seit Rust 1.75 stabil, beim Zusammenspiel mit dynamischem Dispatch gibt es aber weiterhin raue Kanten
- In manchen Situationen greift man zum Crate
async-trait, um diese Probleme zu kaschieren
- Die Trennung zwischen
-
Kleineres Ökosystem
- Das Rust-Crate-Ökosystem wächst und die Qualität der Bibliotheken ist insgesamt hoch, aber Go liegt in einigen backendnahen Bereichen vorn
- Bereiche, in denen Go voraus ist, umfassen Kubernetes-Operatoren, SDKs von Cloud-Anbietern und Datenbanktreiber für bestimmte Nischen-Storage-Systeme
- Bevor man die Migration festzurrt, sollte man sich etwa einen Tag Zeit nehmen, um zu prüfen, ob es für die benötigten Bibliotheken brauchbare Rust-Alternativen gibt
- Manche Teams müssen verwaiste Crates zur XML-Schema-Validierung aktualisieren oder Clients für weniger verbreitete Protokolle selbst schreiben
Integrationsstrategien
- Ein erfolgreicher Wechsel von Go zu Rust ist eher eine taktische Entscheidung als ein komplettes Rewrite in einem Zug
- Microsoft Principal Engineer Victor Ciura sagte in Rust in Production: „Es geht nicht darum, alles zum Spaß in Rust neu zu schreiben, sondern um eine taktische Entscheidung, Rust dort einzusetzen, wo neue Komponenten besser dazu passen.“
-
1. Hot Path als Service herauslösen
- Wenn ein bestimmter Service wiederholt Probleme macht, ist es die risikoärmste Migration, genau diesen Service hinter demselben API-Vertrag in Rust neu zu schreiben
- Das Ziel kann ein Service mit hoher CPU-Auslastung, Latenzempfindlichkeit oder wiederkehrenden Stabilitätsproblemen sein
- Andere Go-Services kommunizieren weiter per HTTP/gRPC und müssen die interne Implementierungssprache nicht kennen
- Radar-CTO Jeff Kao sagte in Rust in Production, dass Discords Beitrag zum Wechsel von Go zu Rust Radar auf dieselbe Idee gebracht habe
-
2. Sidecar- oder Worker-Prozesse ersetzen
- Hintergrund-Worker, Queue-Consumer, Ingestion-Pipelines und CPU-gebundene Batch-Jobs sind gute erste Kandidaten
- Sie haben in der Regel klare Ein-/Ausgabegrenzen wie Queues oder Topics und keinen In-Process-Shared-State mit dem Rest des Systems
-
3. cgo ist möglich, aber schmerzhaft
- Aus Go heraus kann man Rust über cgo aufrufen, und es gibt auch eine gute Anleitung dazu
- Für Backend-Services wird das meist nicht empfohlen
- Build-Komplexität und FFI-Overhead heben die Vorteile gegenüber dem Ansatz „einen Rust-Service aufsetzen und hinter einen Netzwerkaufruf stellen“ oft auf
- Für Bibliotheken und CLI-Tools kann es praktikabler sein
-
4. Strangler Pattern hinter einem Gateway anwenden
- Mit einem API-Gateway oder Reverse Proxy kann man nur bestimmte Endpoints an den neuen Rust-Service weiterleiten und den Rest in Go belassen
- Das passt besonders gut, wenn ein abgegrenzter Bounded Context wie Authentifizierung, Suche oder Bezahlung als Migrationseinheit taugt
- Dieses Muster heißt „strangler fig“, weil der neue Service um den bestehenden herumwächst und ihn am Ende vollständig ersetzt
Praxistipps für die Migration
- Man sollte mit Services mit klaren Grenzen beginnen und nicht den zentralsten oder am häufigsten deployten Service wählen
- Wähle einen Service, dessen Vertrag zum Rest des Systems gut definiert ist und dessen Auswirkungsradius klein bleibt
-
Denselben API-Vertrag beibehalten
- Wenn der Go-Service eine REST-API bereitstellt, sollte der Rust-Service dieselben Pfade, dieselben JSON-Formen und dieselben Error-Wrapper beibehalten
- Für Clients bleibt die Migration unsichtbar, und per Gateway kann Traffic schrittweise umgestellt werden
-
Idiome nicht wörtlich übertragen
if err != nil { return err }wird zu?- Das Muster „eine Goroutine pro Request“ wird nur dann mit
tokio::spawnübernommen, wenn es wirklich nötig ist axumverarbeitet Requests bereits parallel- Interfaces mit nur einer Methode werden meist zu Trait Bounds in Generics statt zu
Box<dyn Trait>
-
Den Compiler wie einen Pair-Programmer nutzen
- Rust-Compiler-Fehlermeldungen sind in der Regel hochwertig, und wenn man sie langsam liest, zeigen sie fast immer die richtige Lösung
- Teammitglieder, die am längsten kämpfen, sind meist diejenigen, die den Compiler nicht als Partner sehen, sondern gegen ihn arbeiten
-
Früh in Training investieren
- Wenn man versucht, die Rust-Migration nebenbei zu lernen, endet das oft nicht gut
- Man sollte sich tatsächlich Lernzeit nehmen, etwa für Workshops, Online-Kurse oder Pairing-Sessions an echter Codebasis
- Sobald das Team sicherer wird, zahlt sich diese Vorabinvestition mehrfach aus
Bereiche, in denen Go weiterhin passend ist
- Es ist nicht nötig, alles nach Rust zu migrieren, und es gibt Bereiche, in denen Go besonders gut geeignet ist
-
Kubernetes-native Tools
- Bei Operatoren, Controllern und CRDs ist das Ökosystem überwältigend stark auf Go ausgerichtet
-
CLI-Utilities und Entwickler-Tools
- Schnelle Kompilierung, einfaches Cross-Compiling und unkomplizierte Bereitstellung sind klare Stärken
-
Glue-Services
- Bei dünnen API-Schichten, Proxys und Formatkonvertern lohnt sich der Boilerplate-Anteil von Rust möglicherweise nicht
-
Wo Team-Geschwindigkeit wichtiger ist als absolute Korrektheitsgarantien
- In Bereichen, in denen man sich schnell bewegen muss, kann Go weiterhin passend sein
- Canonical VP of Engineering Jon Seager sagt in Rust in Production, dass Go eine sehr gute Wahl für Netzwerkdienste sei, dass es bei Canonical viel Go gebe und dass Juju ebenfalls eine riesige Go-Codebasis sei
- Eine Hybridstrategie ist üblich, und viele Teams landen bei einem mehrsprachigen Backend: Go für „langweilige“ Services, Rust für Services, bei denen zusätzliche Stabilität und Performance den Mehraufwand rechtfertigen
Zu erwartende Verbesserungen
- Die Zahlen variieren je nach Workload stark und sollten daher als grobe Orientierung, nicht als Versprechen verstanden werden
- Ungefähre beobachtete Verbesserungsbereiche bei Migrationen von Go nach Rust:
- CPU-Auslastung: 20–60 % weniger
- Da Go bereits effizient ist, fällt der Effekt meist weniger dramatisch aus als bei einer Migration von Python nach Rust
- Vorteile entstehen durch das Fehlen von GC und durch engere Loops
- Arbeitsspeicher: 30–50 % weniger
- Hauptgründe sind der Wegfall des GC-Overheads und eine kleinere Runtime
- P99-Latenz: deutlich konsistenter
- Rust-Services zeigen tendenziell weniger GC-bedingten Jitter und flachere Latenzspitzen als Go-Services
- Seit der Einführung der Low-Latency-GC hat sich auch Go stark verbessert, aber unter hoher Last bleibt ein Unterschied
- Produktionsstörungen: der Bereich, in dem Teams die Verbesserungen am häufigsten hervorheben
- Fehlerarten wie Data Races, nil-Dereferenzierungen oder fehlende Fehlerpfade, die
go test -racebestehen und trotzdem in Produktion gelangen, kompilieren in Rust nicht - Nach einer Rust-Migration werden On-Call-Rotationen in der Regel sehr langweilig
- Fehlerarten wie Data Races, nil-Dereferenzierungen oder fehlende Fehlerpfade, die
- CPU-Auslastung: 20–60 % weniger
- InfluxData Staff Engineer Andrew Lamb sagt in Rustacean Station: Rebuilding InfluxDB with Rust, dass nach dem Rewrite von InfluxDB Abstürze, seltsame Multithreading-Race-Conditions und das Nachverfolgen zuvor sehr zeitaufwendiger Probleme wegfielen
- Wer von Go nach Rust wechselt, wird die 10-fache Durchsatzsteigerung, wie sie bei einem Wechsel von Python nach Rust möglich sein kann, eher nicht sehen
- Der eigentliche Nutzen liegt in weniger „absurden Fehlern“, flacheren Latenz-Tails und der Fähigkeit, mit derselben Sprache auch in andere Bereiche wie Embedded-Entwicklung oder Systemprogrammierung vorzudringen
Zusätzliche Hinweise
- Das Typsystem von Rust beseitigt nicht jeden Fehler in der Synchronisationslogik, aber Typen, die nicht ohne Synchronisation zwischen Threads geteilt werden dürfen, kompilieren nicht
- Die Art von Problemen, bei denen „vergessen, zu locken“ zu stiller Datenkorruption führt, kann das Typsystem von Rust verhindern
- Go-
stringist eine unveränderliche Byte-Sequenz und konventionsgemäß UTF-8, aber nicht auf Typebene garantiert - Die nächstliegenden Entsprechungen sind Go-
string↔ Rust-&strfür schreibgeschützte Sichten und Go-[]byte↔ Rust-Vec<u8>für veränderliche Buffer - Rust-
Stringist die besitzende, erweiterbare Version von&strund bietet zusätzlich die Garantie, dass der Inhalt gültiges UTF-8 ist - Mehr dazu findet sich in Strings, bytes, runes and characters in Go
- Seit Go 1.18 sind generische Funktionen und generische Typen möglich, Typ-Parameter direkt an Methoden wurden jedoch nicht eingeführt
- Iterator-Ketten wie in Rusts
(0..100).filter(|n| ...).collect()können auf Go-Entwickler ungewohnt wirken, aber auch in Rust lassen sichfor-Loops verwenden, und für einmaligen Code sind sie oft die richtige Wahl
Fazit
- Der Wechsel von Go zu Rust unterscheidet sich vom Wechsel von Python oder TypeScript nach Rust
- Entwickler mit Go-Hintergrund kennen die Vorteile statischer Typisierung und kompilierter Sprachen bereits, es geht also nicht darum, dynamische Typen oder eine langsame Runtime aufzugeben
- Der zentrale Tausch besteht darin,
nilhinter sich zu lassen und dafür eine robustere Codebasis, weniger Fallstricke und einen strengeren Compiler zu bekommen, der mehr Fehler schon zur Compile-Zeit findet - Dafür ist die Lernkurve steiler
- Bei Diensten, von denen eine Organisation abhängt, die hohe Verfügbarkeit benötigen und geschäftskritisch sind, wie grundlegende Services, ist dieser Tausch eindeutig wertvoll
- Bei anderen Services kann Go weiterhin die richtige Antwort sein
- Das Ziel einer Migration ist, jede Aufgabe in der Sprache zu platzieren, die das jeweilige Problem am besten löst
1 Kommentare
Hacker-News-Kommentare
Ein Wechsel von C/C++ oder Python zu Rust ist aus mehreren Gründen nachvollziehbar, aber für ein Web-Backend scheint Go eine gute Wahl zu sein
Ich verwende fast nur Rust, aber als ich zuletzt an einem Webserver in Rust gearbeitet habe, dachte ich, ich hätte lieber Go nehmen sollen
Im Original wird angemerkt, dass Gos Syntax für Fehlerbehandlung umständlich ist, und das stimmt. Rust hatte dasselbe Problem und hat dann die
?-Syntax ergänzt, die bei einem Fehler den Fehlerwert zurückgibt. Gos Fehlerbehandlung ist meistens die ausgeschriebene Form davonRust hat keinen vereinheitlichten Fehlertyp, sondern mehrere wichtige Fehler-Ansätze wie
io::Error,thiserrorundanyhow, was beim Weiterreichen entlang der Aufrufkette nach oben lästig istEs gibt Dinge, die in einer neuen Sprache fehlen können und sich später nur schwer nachrüsten lassen. Dazu gehören Konstantentypen, Boolesche Typen, Fehlertypen, mehrdimensionale Array-Typen sowie Vektor-/Matrix-Typen der Größe 2/3/4 und Standardoperationen darauf. Wenn man das nicht früh standardisiert, verbringt man viel Zeit damit, mehrere Darstellungen desselben Konzepts aufeinander abzustimmen
Abgesehen von der Fehlerbehandlung wirkt sich das bei Webentwicklung weniger aus, aber bei numerischen Berechnungen, Grafik und Modellierung ist es sehr schmerzhaft, weil man Standardoperationen auf Zahlenarrays anwenden können muss
Go hat bei Webservices zwei Vorteile. Der erste sind die im Original erwähnten Goroutinen, der zweite sind die im Original weniger behandelten Bibliotheken. Go hat die meisten Bibliotheken, die man für Webservices braucht, und viele davon werden auch intern bei Google verwendet und haben daher sehr harte Umgebungen überstanden. Rust-Crates sind dagegen oft weniger ausgereift und haben keine offizielle Qualitätssicherung
Außerdem hängt Rust im Vergleich zu Go noch immer stark von vielen C/C++-Bibliotheken ab, wodurch Cross-Compiling, reproduzierbare Builds und die Erstellung statischer Binärdateien leicht problematisch werden
Der Nachteil von Go ist, dass der Garbage Collector zu simpel ist. Wenn Latenzspitzen auftreten, gibt es außer einem schmerzhaften Rewrite kaum Gegenmaßnahmen
Die aufgezählten Dinge sind nur gängige Nutzungsweisen davon, und selbst nur mit
Boxgibt es überhaupt kein Problem. Das ist im Wesentlichen ähnlich zu dem, wasanyhow::ErrormachtBei der Standardbibliothek hat Go es meiner Meinung nach aber deutlich besser gemacht als Rust
Ich mag die Sprache Rust und nutze sie für Embedded-Firmware und PC-Anwendungen, aber für Web-Backends verwende ich immer noch Python. Rust hat einfach kein Toolset auf Django- oder Rails-Niveau
Es gibt Dinge ähnlich wie Flask, aber nicht das robuste Flask-Ökosystem. Ich habe wenig Go-Erfahrung, aber für ein Web-Backend würde ich vermutlich Go statt Rust wählen. Der Grund ist das Ökosystem aus Bibliotheken und Frameworks
Außerdem mag ich aus den üblichen Gründen Async Rust nicht besonders. Im Rust-Web-Ökosystem ist asynchrone Nutzung fast überall praktisch Pflicht
io::Errorist nur einer von vielen Typen, die es implementieren, und nichts Besonderes. Mitthiserrordefinierte Fehler implementieren dieses Trait ebenfallsanyhowdient nur dazu, bequem „irgendein Error“ sagen zu können, wenn man den Fehlertyp, den eine Funktion ausgeben kann, nicht detailliert als API-Vertrag ausschreiben willRust macht es leichter als Go, Code deterministisch zu halten, was sehr nützlich ist, wenn man deterministische Simulationstests und eigenschaftsbasierte Tests braucht
Ich habe kürzlich mit Go ein Postgres-to-Iceberg-Datenspiegelungs-Tool geschrieben: https://github.com/polynya-dev/pg2iceberg
Ich habe es aber nach Rust portiert, weil ich deterministische Simulationstests machen wollte, ohne gegen die Go-Runtime kämpfen zu müssen
Wenn die betreffende Domäne diese Art von Tests allerdings nicht rechtfertigt, würde ich jederzeit Go statt Rust wählen
Verwandter Artikel: https://www.polarsignals.com/blog/posts/2024/05/28/mostly-ds...
Es klingt vielleicht offensichtlich und wiederholt, aber mein größter Kritikpunkt an Rust ist die Lage beim Paketmanagement, und ich halte das vollständig für das Ergebnis der Denkweise der Entwickler
Die Usability auf Rust-Seite gefällt mir. Der funktionale Ansatz bei Datentypen ist schön. Aber ich arbeite gerade parallel an einem Rust-Projekt und einem Go-Projekt, und die Abhängigkeitsbäume sind völlig unterschiedliche Tiere
Das Go-Projekt kommt größtenteils mit der Standardbibliothek aus, aber im Rust-Projekt habe ich nur
rusqlite(sqlite),clap(CLI),ratatui(TUI) undtauri(GUI) angefordert, und trotzdem scheint es über 400 Abhängigkeiten zu geben. Vor allemtauriist der mit Abstand größte Verursacher, aber selbst ohne das sind es fast 100, was sich verrückt anfühltWenn es gut gepflegte Rust-Crate-Alternativen gäbe, die vernünftig mit Abhängigkeiten umgehen, wäre das deutlich besser, aber ich habe sie noch nicht gefunden. Ich will mir einfach keinen Shai-Hulud ins System holen, aber die Rust-Web-Leute scheinen in dieser Hinsicht
cargozunpmmachen zu wollenDadurch wirkt die Anzahl der Abhängigkeiten größer, als sie tatsächlich ist. Oft haben getrennte Crates denselben Maintainer und sind Teil desselben Upstream-Git-Repositories
Trotzdem stimme ich dem Gesamteindruck zu. In Rust gibt es viele halb verlassene Crates in Version 0.x, und oft gibt es keine bessere Alternative
Danach bekommt man dann
httplib3und anschließendhttplib4Anders gesagt: Ich bevorzuge den Rust-Ansatz deutlich. Ob ich von der Standardbibliothek oder einer anderen Abhängigkeit abhänge, macht für mich keinen großen Unterschied. Es ist so oder so eine Abhängigkeit
Dass etwas Teil der Standardbibliothek ist, bedeutet nicht automatisch, dass es qualitativ besser oder besser gepflegt ist; das sind getrennte Fragen
Am Ende hängt alles von den Ressourcen ab. Natürlich kann eine Standardbibliothek mehr Ressourcen bekommen, aber sie kann genauso gut aufblähen und unwartbar werden
rusqlite,clap,ratatuiundtaurienthältAußerdem besteht Tauri selbst aus 14 Crates, und jede davon taucht im Build-Tree auf
https://github.com/tauri-apps/tauri/blob/dev/Cargo.toml
Ratatui besteht ebenfalls aus 6
https://github.com/ratatui/ratatui/blob/main/Cargo.toml
Niemand hat es „gelöst“, und ich glaube auch nicht, dass es künftig eine einzelne Lösung geben wird
Bei Go muss man darauf vertrauen, dass Bibliotheksentwickler Semantic Versioning korrekt einhalten, und man kann Versionen nicht pinnen. Das finde ich persönlich ziemlich störend
Es gibt ein paar Workarounds. Man kann SHAs wie Git-Commit-Hashes verwenden, um so etwas wie Versionen zu erzeugen, oder Vendoring als bekannten Abhängigkeits-Cache nutzen. Vendoring bringt allerdings eigene Cache-Management-Probleme mit sich
Ich musste am Wochenende mit Python-Virtual-Environments arbeiten, und das endete nicht gut; es erinnerte mich wieder daran, warum ich Python verlassen habe
Perl mit CPAN, Java mit Maven/Gradle, Ruby mit Gems, Go mit dep/glide/vgo/modules, Rust mit Cargo, Node mit npm/yarn — alle haben ähnliche Probleme
Bei Betriebssystemen ist es Redhat mit yum/rpm, Debian mit apt, Ubuntu mit snap und so weiter. Besonders bei snap frage ich mich wirklich, was das soll
Für den genannten Anwendungsfall könnte es vielleicht Sinn ergeben, das Frontend weiter in Go zu lassen und nur das Backend in Rust zu machen
Dieses Dokument fühlt sich seltsam an, weil es zugleich ein Migrationsleitfaden und ein Rust-Plädoyer sein will
Wenn man am Ende überlegt, ob man Rust oder Go einsetzen soll, läuft die Kernfrage fast vollständig darauf hinaus: „Will man eine Managed Runtime oder nicht?“ Eine Generation von Rust-Programmierern hat sich eingeredet, dass Managed Runtimes schlecht seien und dass ihr Fehlen ein wichtiges Feature sei
Das ist aber offensichtlich falsch. Es gibt mehr Programmierdomänen, die eine Managed Runtime wollen, als solche, die keine wollen
Das bedeutet nicht, dass Go in all diesen Fällen die Standardwahl sein sollte. Es gibt viele subjektive Gründe, Rust zu bevorzugen. Wenn ich Go benutze, vermisse ich
match, abertokiound Async Rust vermisse ich nichtBeide sind in fast allen Fällen legitime Optionen, in denen man den Problemraum nicht gewaltsam verbiegen muss. Ein Linux-Kernel-Modul in Go zu schreiben, wäre zum Beispiel eine seltsame Wahl
Der Rust-gegen-Go-Kampf ist ein merkwürdiger und peinlicher Seitenarm unseres Fachs. Ein großer Teil der Branche baut ganze Systeme sehr erfolgreich mit Python oder Node und lacht über die Nerds, die sich darüber streiten, welche statisch typisierte kompilierte Sprache sie verwenden sollen. Die eigentliche Frage ist Python gegen Rust/Go, nicht Rust gegen Go
Insgesamt sollten Rust und Go aber ihre Kräfte gegen die Übel des dynamischen Tippens bündeln. Wenn Type Hints inzwischen als Best Practice gelten, ist das doch im Grunde ein Eingeständnis, dass da vorher ein Defekt war
Selbst gute Type Hints sind schlechter als Typinferenz. Typinferenz erlaubt es, bei Typänderungen viel Code unverändert zu lassen und verhindert trotzdem unbeabsichtigte Typänderungen
Ich wünschte, TS hätte etwas mehr Runtime. Das Einzige, worauf ich bei Python neidisch bin, ist, wie natürlich sich dort JSON-Schema-Validierung an HTTP-Endpunkten anfühlt
Das Prozedere mit Zod ist für mich ständig eine Quelle des Ärgers, und ich halte das für ein Problem, das durch die dogmatische Haltung des TS-Teams entstanden ist
Die Spuren von LLM-Schreibstil werden immer subtiler, sind aber noch immer sehr deutlich zu sehen. Besonders beim Wort genuine
Etwa in Sätzen wie „This is the area where Go genuinely shines, and it’s worth being precise about why“, „the lack of GC pauses is a genuine selling point“, „Humans are genuinely bad at reasoning about memory“ oder „There are cases where the borrow checker is genuinely too strict“
Ich glaube nicht, dass der ganze Text KI-generiert ist, aber er wirkt KI-unterstützt. Falls das so ist, hat der Autor es genuinely gut gemacht
Dass andere das nicht ansprechen, deutet darauf hin, dass es dem Inhalt nicht massiv schadet, aber es fühlt sich seltsam an, dass so etwas immer häufiger und schwerer erkennbar wird
Spätestens bei „Go is clearly working for a lot of people,“ begann ich KI-Unterstützung zu vermuten. Natürlich muss das nicht stimmen; ich bin nicht besonders gut darin, das zu erkennen
Ironischerweise ist es weniger ein konkreter Hinweis als eher ein Gefühl. Wenn sich ein Text nach KI-Unterstützung „anhört“, verliere ich sofort das Interesse, auch wenn der Text an sich okay ist
Ich wünschte, die Leute würden sich wieder wohler damit fühlen, ihre eigenen Gedanken direkt so aufzuschreiben, wie sie ihnen kommen
it's worth being precise about ...ist ein noch viel stärker KI-artiger Ausdruck als die genuine-VerwendungDieses Absatzbeispiel etwa wirkt so: „Go got generics in 1.18, and they’re useful, but the implementation has constraints (no methods with type parameters, GC shape stenciling, occasional surprising performance characteristics). Rust generics monomorphize, each instantiation produces specialized code with zero runtime cost. Combined with traits, this gives you real zero-cost abstractions.”
Jeder Satz sagt etwas, jeder Satz ist wichtig und erfüllt seine Aufgabe. So etwas erwartet man eher in einem sehr fachlichen Buch oder Paper als in einem Blogpost
Gerade dadurch wird der Text schwerer lesbar und langweiliger
Ich erwarte nicht, dass LLM-generierter Text frei von Gemeinplätzen ist. Ich hoffe nur, dass wir alle wieder ein besseres Gespür fürs Redigieren bekommen, damit wir nicht ständig dieselbe Stimme lesen
Bei einem neuen Projekt kann man es ruhig in Rust schreiben
Wenn aber schon bestehender Code da ist, das System funktioniert und Geld verdient, sollte man nur die Teile neu schreiben, die wirklich neu geschrieben werden müssen, und sonst in der ursprünglichen Sprache weitermachen
Verbessere das System in kleinen, messbaren Schritten mit einer Sprache, die du kennst, und einem Team, dem du vertrauen kannst. Alles andere ist verschwenderischer Religionskrieg
Ich mochte Rust schon vor Benchmarks, aber bei den meisten LLMs war der Unterschied in der Effizienz, Rust und Go zu schreiben, viel größer als ich gedacht hatte. Besonders bei agentischen Harnesses, die anfängliche Umgebungsprobleme beheben können
Das hat mich ziemlich stark zu einem Rust-Evangelisten gemacht. Ich habe gute Ergebnisse erzielt, indem ich Batch-Verarbeitungs-Tools in Rust geschrieben habe, die aus bestehendem Code aufgerufen werden, aber eine vollständige Produktionsmigration habe ich noch nicht versucht
Die im Text genannten Probleme von Go, insbesondere rund um
nil, scheinen sich durch sehr gründliche Code-Reviews mit Codex zunehmend abfedern zu lassen. Noch besser wäre es natürlich, wenn das Problem gar nicht erst existierte, aber für Entwickler, die in Review und Verständnis ähnlich viel Mühe investieren wie in Design und Implementierung, werden solche Sicherheitsbugs zunehmend optionalDie Sprachdaten stehen hier: https://gertlabs.com/rankings?mode=agentic_coding
Rust zwingt den Nutzer stark auf einen vorgegebenen Pfad. Codex bekommt immer irgendetwas zum Kompilieren hin
Der Nachteil ist, dass es manchmal scheitern sollte, wenn ein idiomatischer Ansatz nicht möglich ist, stattdessen aber eine dumme Implementierung herauskommen kann, die kompiliert und die Anfrage erfüllt
LLMs schreiben Code schneller als Menschen, daher fällt die Wartezeit auf die Kompilierung relativ stärker ins Gewicht. Bei Projekten einer gewissen Größe, etwa ab 100.000 Zeilen, beginnt Rusts ungefähr 10-fach langsamere Kompilierung zum Flaschenhals zu werden
Wenn man Kerninfrastruktur schreibt, lohnt sich dieser Preis, aber bei internen Services, die nicht öffentlich im Internet stehen, ist Entwicklungsgeschwindigkeit womöglich wichtiger
Ich denke, langsame Kompilierung beeinflusst auch die Entwicklungsgeschwindigkeit von Menschen, aber seltsamerweise versuchen nur sehr wenige Entwickler, das zu quantifizieren
Wenn Umständlichkeit das Hauptproblem ist, könnte das hier, das voraussichtlich in Go 1.28 kommen soll, vieles reduzieren
https://github.com/golang/go/issues/12854#issue-110104883
Die Formulierung „ein Service, von dem die Organisation abhängt, der hohe Verfügbarkeit braucht und geschäftskritisch ist“ ist lustig
Besonders dann, wenn dieser Rust-Service auf Kubernetes läuft
Ich nutze bereits Rust und habe keine Go-Erfahrung, daher ist der Artikel für mich vielleicht nicht ideal zugeschnitten
Eine Sache stößt mir aber auf. Zu sagen, dass Datenrennen in Rust „zur Compile-Zeit abgefangen werden“, wirkt zumindest etwas übertrieben
Diese Formulierung kann den Eindruck erwecken, Rust könne auch Dinge wie Lock-Starvation oder andere Nebenläufigkeitsprobleme behandeln. Das kann es in Wirklichkeit nicht
Ich weiß, dass Datenrennen ein formaler Begriff mit enger Bedeutung ist, aber ich finde trotzdem, dass man das klarer formulieren könnte