10 Punkte von GN⁺ 2025-08-28 | Noch keine Kommentare. | Auf WhatsApp teilen
  • Rust erhöht mit starken Sicherheitsgarantien die Produktivität und Wartbarkeit, weil sich selbst in großen Codebasen Refactorings mit Zuversicht durchführen lassen
  • Der Compiler erkennt Bugs im Zusammenhang mit asynchronem Scheduling im Voraus und stärkt die Stabilität, indem er undefiniertes Verhalten verhindert
  • Sprachen wie TypeScript haben aufgrund ihres lockeren Typsystems häufig asynchrone Bugs, die erst in der Produktionsumgebung entdeckt werden
  • Rusts Typsystem zeigt die Auswirkungen von Codeänderungen klar auf und erhöht so in komplexen Projekten Zuverlässigkeit und die Bereitschaft zu experimentieren
  • Zig ist im Gegensatz zu Rust bei der Fehlerbehandlung lockerer und kann deshalb Bugs durch Tippfehler übersehen, was die Zuverlässigkeit senkt

Zusammenfassung und Hintergrund

  • Das Backend von Lubeno ist zu 100 % in Rust geschrieben, und die Codebasis hat einen Umfang erreicht, bei dem sie sich nicht mehr vollständig im Kopf überblicken lässt
    • In großen Projekten ist es generell schwer, Nebenwirkungen von Änderungen zu erkennen, was normalerweise zu Produktivitätsverlusten führt
  • Rusts Sicherheitsgarantien machen bei Codeänderungen die Auswirkungen klar sichtbar und nehmen so die Angst vor Refactorings
    • Das trägt langfristig zu besserer Wartbarkeit und höherer Produktivität bei
  • Der Text beginnt mit einem Fall, in dem der Rust-Compiler einen asynchronen Bug erkannt hat, und untersucht daran Rusts Produktivitätsvorteile

Beispiel für Rusts Sicherheitsgarantien

  • Problemsituation: Eine Struktur wird für gleichzeitigen Zugriff in einen Mutex gepackt, nach dem Erwerb des Locks wird asynchrone Arbeit ausgeführt
    let lock = mutex.lock();  
    db.insert_commit(commit).await;  
    
  • Entdeckung des Problems: rust-analyzer zeigte keinen Fehler an, aber in der Datei mit der Router-Definition trat ein Compilerfehler auf
    .route("/api/git/post-receive", post(git::post_receive))  
                                         ^^^^^^^^^^^^^^^^^  
    error: future cannot be sent between threads safely  
    
  • Ursachenanalyse:
    • Das Web-Framework erstellt für jede HTTP-Verbindung einen asynchronen Task, und der Task-Scheduler verschiebt Tasks zwischen Threads
    • Ein Mutex erfordert, dass derselbe Thread den Lock wieder freigibt; wenn der Thread an einer .await-Stelle wechselt, kann undefiniertes Verhalten entstehen
    • Der Rust-Compiler verfolgt die Lebensdauer des Locks und erkennt, dass es möglicherweise auf einem anderen Thread freigegeben wird
  • Lösung: Den Lock vor .await freigeben
  • Bedeutung: Rust verhindert asynchrone Bugs bereits zur Compile-Zeit, selbst wenn sie in der Entwicklungsumgebung schwer zu reproduzieren sind

Vergleichsbeispiel mit TypeScript

  • Problemsituation: In TypeScript-Code tritt ein Bug bei einer asynchronen Weiterleitung auf
    if (redirect) {  
        window.location.href = redirect;  
    }  
    let content = await response.json();  
    if (content.onboardingDone) {  
        window.location.href = "/dashboard";  
    } else {  
        window.location.href = "/onboarding";  
    }  
    
  • Ursache des Problems:
    • window.location.href führt die Weiterleitung nicht sofort aus, sondern plant sie nur ein; der Code läuft weiter
    • Durch eine Race Condition kommt es zu einer unbeabsichtigten Weiterleitung
  • Lösung: Im if-Block ein return ergänzen
    if (redirect) {  
        window.location.href = redirect;  
        return;  
    }  
    
  • Einschränkung: TypeScript hat keine Nachverfolgung von Lebensdauern oder Borrowing-Regeln und kann solche Bugs daher nicht zur Compile-Zeit erkennen
    • Sie werden erst in Produktion entdeckt, und das Debugging kostet viel Zeit

Rusts Vorteile beim Refactoring

  • In der Webentwicklung bieten Python, Ruby und JavaScript/Node.js anfangs hohe Produktivität, doch mit wachsender Codebasis werden Änderungen durch lockere Kopplung schwieriger
    • Nach Änderungen treten unerwartete Fehler auf, was die Bereitschaft verringert, Code anzupassen
  • Rusts Typsystem zeigt die Auswirkungen von Änderungen klar auf und nimmt so die Angst vor Refactorings
    • Beispiel: Warnungen wie „Diese Änderung könnte andere Teile beeinflussen“ verhindern Probleme im Voraus
  • Selbst wenn die Codebasis wächst, steigt die Produktivität weiter, weil bestehender Code wiederverwendet werden kann und Änderungen stabil bleiben

Vergleich mit Tests

  • Tests sind bei Refactorings nützlich, um Regressionen zu vermeiden, aber da der Compiler sie nicht erzwingt, können sie ausgelassen werden
    • Das Schreiben von Tests bedeutet eine hohe mentale Belastung, weil entschieden werden muss: Abstraktionsebene, Verhalten vs. Implementierungsdetails und welche Fehler verhindert werden sollen
  • Rust blockiert mit dem Compiler typische Fehler im Voraus und reduziert so die Entscheidungsbelastung, die sonst bei Tests anfällt
    • Eigenschaften, die sich nicht über das Typsystem prüfen lassen, werden durch Tests ergänzt

Vergleich mit Zig

  • Zig ist wie Rust eine Systemprogrammiersprache, ist bei der Fehlerbehandlung jedoch lockerer
    • Beispiel für Fehlerbehandlungscode
      const FileError = error{ AccessDenied };  
      fn doSomethingThatFails() FileError!void {  
          return FileError.AccessDenied;  
      }  
      pub fn main() !void {  
          doSomethingThatFails() catch |err| {  
              if (err == error.AccessDenid) {  
                  std.debug.print("Access was denied!\n", .{});  
              }  
          };  
      }  
      
    • Durch den Tippfehler AccessDenid entsteht ein Bug, aber der Zig-Compiler behandelt dies als Zahl, sodass der Code trotzdem kompiliert
  • Bei Verwendung von switch wird ein Tippfehler erkannt, in einer if-Anweisung jedoch ignoriert, was Zuverlässigkeitsprobleme verursacht
  • Rust vermeidet solche Designlücken und prüft Tippfehler und logische Fehler strikt

Implikationen

  • Rust verbessert mit Sicherheitsgarantien und einem strengen Typsystem die Produktivität und Stabilität großer Projekte
  • Selbst komplexe Probleme wie asynchrone Bugs werden zur Compile-Zeit erkannt, was die Wartungskosten senkt
  • Die Beispiele mit TypeScript und Zig zeigen die Risiken lockerer Prüfungen und unterstreichen den Wert von Rusts strengem Compiler
  • Rust etabliert sich auch in der Webentwicklung als starkes Werkzeug nicht nur für anfängliche Produktivität, sondern auch für das langfristige Management von Codebasen

Noch keine Kommentare.

Noch keine Kommentare.