7 Punkte von GN⁺ 2025-05-10 | 3 Kommentare | Auf WhatsApp teilen
  • Rusts Abhängigkeitsverwaltungssystem macht die Entwicklung bequem, doch die Anzahl und Qualität der Abhängigkeiten bereiten Sorgen
  • Selbst häufig genutzte Crates sind möglicherweise nicht aktuell, sodass eine eigene Implementierung manchmal besser sein kann
  • Nach dem Hinzufügen bekannter Crates wie Axum und Tokio umfasst die gesamte Anzahl der Codezeilen inklusive Abhängigkeiten 3,6 Millionen und ist kaum noch beherrschbar
  • Der von mir tatsächlich geschriebene Code umfasst nur rund 1.000 Zeilen, aber den umgebenden Code kann ich praktisch weder prüfen noch auditieren
  • Für die Frage, ob Rusts Standardbibliothek erweitert werden sollte und wie zentrale Infrastruktur implementiert werden soll, gibt es keine klare Lösung; stattdessen muss die gesamte Community gemeinsam das Gleichgewicht zwischen Leistung, Sicherheit und Wartbarkeit ausloten

Überblick über das Rust-Abhängigkeitsproblem

  • Rust ist meine Lieblingssprache, und Community und Nutzbarkeit der Sprache sind hervorragend
  • Die Entwicklung ist produktiv, aber in letzter Zeit mache ich mir Sorgen wegen des Abhängigkeitsmanagements

Vorteile von Rust-Crates und Cargo

  • Mit Cargo lassen sich Paketverwaltung und Build-Automatisierung erledigen, was die Produktivität stark verbessert
  • Der Wechsel zwischen verschiedenen Betriebssystemen und Architekturen ist einfach, und man muss sich nicht um die manuelle Dateiverwaltung oder die Konfiguration von Build-Tools kümmern
  • Man kann ohne zusätzliche Paketverwaltungsfragen direkt mit dem Schreiben von Code beginnen

Nachteile bei der Verwaltung von Rust-Crates

  • Weil man sich weniger um Paketverwaltung kümmern muss, wird man bei der Stabilität leicht nachlässig
  • So nutzte ich etwa das dotenv-Crate und erfuhr erst über ein Security Advisory, dass seine Wartung eingestellt worden war
  • Ich zog ein Ersatz-Crate (dotenvy) in Betracht, implementierte den tatsächlich benötigten Teil am Ende aber selbst in etwa 35 Zeilen
  • Da Probleme mit nicht mehr gepflegten Paketen in vielen Sprachen häufig auftreten, liegt das eigentliche Problem in Situationen, in denen Abhängigkeiten unvermeidlich sind

Der sprunghafte Anstieg der Codebasis durch Abhängigkeiten

  • Ich verwende wichtige und qualitativ hochwertige Pakete des Rust-Ökosystems wie Tokio und Axum
  • Als Abhängigkeiten wurden Axum, Reqwest, ripunzip, serde, serde_json, tokio, tower-http, tracing und tracing-subscriber hinzugefügt
  • Hauptzweck sind ein Webserver, das Entpacken von Dateien und Logging, daher ist das Projekt selbst einfach
  • Mit der Cargo-vendor-Funktion wurden alle abhängigen Crates lokal heruntergeladen
  • Eine Analyse der Codezeilen mit tokei ergab rund 3,6 Millionen Zeilen inklusive Abhängigkeiten (ohne vendorte Crates etwa 11.136 Zeilen)
  • Zum Vergleich: Der gesamte Linux-Kernel soll etwa 27,8 Millionen Zeilen umfassen, mein kleines Projekt entspricht also rund einem Siebtel davon
  • Der von mir tatsächlich geschriebene Code umfasst nur etwa 1.000 Zeilen
  • So viele Zeilen an Abhängigkeitscode zu überwachen und zu auditieren ist praktisch unmöglich

Überlegungen zu Lösungen

  • Derzeit gibt es keine klare Lösung
  • Einige plädieren dafür, wie bei Go die Standardbibliothek zu erweitern, doch auch das schafft neue Probleme wie zusätzlichen Wartungsaufwand
  • Rust strebt hohe Leistung, Sicherheit und Modularität an und will im Embedded-Bereich oder gegenüber C++ konkurrieren, daher muss eine Erweiterung der Standardbibliothek vorsichtig abgewogen werden
  • Selbst ein hochentwickeltes Runtime-System wie Tokio wird beispielsweise auf GitHub und Discord sehr aktiv gepflegt
  • Realistisch gesehen ist es für einzelne Entwickler zu viel verlangt, zentrale Infrastruktur wie asynchrone Runtimes oder Webserver selbst zu implementieren
  • Selbst ein großer Dienst wie Cloudflare nutzt die Abhängigkeiten von tokio und crates.io unverändert; wie oft dort Audits stattfinden, ist unklar
  • Auch Clickhouse hat Probleme mit Binärgröße und der Anzahl von Crates angesprochen
  • Mit Cargo ist es schwer, die tatsächlich im finalen Binary enthaltenen Codezeilen exakt zu identifizieren, und zudem wird auch plattformspezifisch unnötiger Code einbezogen
  • Letztlich bleibt nur die Realität, die Antwort von der gesamten Community einfordern zu müssen

3 Kommentare

 
codemasterkimc 2025-05-11

Wenn man Trivy laufen lässt, gibt es im Vergleich zu js NPM oder Java Maven deutlich weniger High- oder Critical-Funde und es ist sicherer. Was genau will der Artikel also mit Rust behaupten?

 
GN⁺ 2025-05-10
Hacker-News-Kommentare
  • Meiner Meinung nach führen Systeme, in denen Abhängigkeiten „einfach“ hinzugefügt werden können und bei Größe oder Kosten keine Nachteile entstehen, am Ende zwangsläufig zu Abhängigkeitsproblemen. Wenn man sich anschaut, wie Software in den letzten 40 Jahren ausgeliefert wurde, dann wurden Bibliotheken in den 80ern gekauft, und man hat in Umgebungen mit Speicherbeschränkungen nur die Teile ausgewählt, die man wirklich brauchte. Heute stapelt man Bibliothek auf Bibliothek. Man kann einfach import foolib schreiben, und niemand kümmert sich darum, was darin eigentlich steckt. Auf jeder Ebene braucht man vielleicht nur etwa 5 % der Funktionalität, aber je tiefer der Baum wird, desto mehr nutzloser Code sammelt sich an. Am Ende wird aus einer einfachen Binärdatei 500 MiB, und man holt sich eine Abhängigkeit nur für Zahlenformatierung ins Haus. Go und Rust fördern es, alles in eine Datei zu packen, wodurch es schwierig wird, nur Teile davon zu nutzen. Langfristig ist die echte Lösung eine ultrafeingranulare Symbol-/Abhängigkeitsverfolgung, bei der jede Funktion und jeder Typ genau angibt, was benötigt wird, sodass nur der wirklich notwendige Code verwendet und der Rest verworfen wird. Persönlich finde ich diese Idee nicht besonders gut, aber mir fällt kein anderer Weg ein, das aktuelle System zu lösen, das aus dem Abhängigkeitsbaum gleich das ganze Universum heranzieht
    • Ich bin vielleicht nur Student und weiß es nicht besser, aber der Rust-Compiler erkennt doch bereits ungenutzten Code, Variablen und Funktionen. Die meisten IDEs können das in den meisten Sprachen ebenfalls. Dann müsste man solche Teile doch einfach entfernen können, oder? Nicht verwendeter Code wird nicht kompiliert
    • Ich habe tatsächlich versucht, bei einer relativ schweren Bibliothek mit Abhängigkeitsbaum in Rust (Xilem) per Feature-Flags zu trimmen, aber fast alle Abhängigkeiten mussten je nach benötigter Funktionalität erhalten bleiben (vulkan, PNG-Decoding, Unicode-Shaping usw.). Unnötige Abhängigkeiten waren meist sehr klein, und nur serde_json konnte ich mit kleineren Änderungen entfernen. Größere Abhängigkeiten (winit/wgpu usw.) lassen sich ohne strukturelle Änderungen nicht einfach herausnehmen
    • Go und C# (.NET) sind gute Gegenbeispiele. Sie haben ein ebenso effektives Paketmanagement und Ökosystem wie Rust oder JS (Node), aber relativ wenig Dependency Hell. Das liegt daran, dass die Standardbibliothek so gut ist. So umfangreiche Standardbibliotheken sind etwas, worin nur große Unternehmen wie Google oder Microsoft investieren können
    • Warum entfernt der aktuelle Compiler dann keinen ungenutzten Code?
    • Früher hat man für jede Funktion eine .o-Datei erzeugt und sie in ein .a-Archiv gepackt, aus dem der Linker dann nur die benötigten Funktionen herausgezogen hat. Namespacing lief über Dinge wie foolib_do_thing(). Heute hat man eher das God-Object-Muster, bei dem ein Top-Level-Objekt alle Funktionen enthält, sodass beim Import von foolib alles mit hereingezogen wird. In so einem Zustand kann der Linker nur schwer beurteilen, welche Funktionen wirklich notwendig sind. Go hat dafür eine hervorragende Dead-Code-Eliminierung, sodass ungenutzte Teile im Build-Ergebnis abgeschnitten werden
    • Moderne Compiler und Linker führen bereits Symbol-Extraktion und Dead-Code-Eliminierung durch, und Rust unterstützt das mit Projekten wie min-sized-rust
    • Früher hat man alle Bibliotheken ins Projekt aufgenommen und direkt in die Build-Dateien integriert verwaltet. Das war viel Aufwand und lästig, führte aber zu deutlich tieferer Auseinandersetzung als einfach nur eine Zeile in einer deps-Datei hinzuzufügen
    • Go besteht tatsächlich nicht auf einer einzigen Datei, sondern unterstützt logisch aufgeteilte Dateien sehr einfach. Das gefällt mir wirklich
    • Dotnet setzt diese Idee bereits mit Trimming und Ahead Of Time Compilation um. Andere Sprachen können von Dotnet lernen
    • Mit LTO (Link Time Optimization) ist dieses Problem aus Sicht der Binärgröße vollständig gelöst. Nicht verwendete Teile werden durch Optimierung entfernt. Die Build-Zeit bleibt allerdings ein Thema
    • Ich denke eher, dass nicht die Bibliotheken selbst das Problem sind, sondern die fehlende Sicht darauf, was nach dem Hinzufügen einer Abhängigkeit intern wie stark genutzt wird. Man braucht eine Umgebung, die leicht Feedback über Performance, Build-Aufwand und den Anteil überflüssigen Codes je Paket liefert
    • Die Sprache Unison verfolgt einen Ansatz, der dieser Idee teilweise ähnelt. Jede Funktion wird anhand ihrer AST-Struktur definiert und aus einem hashbasierten globalen Register geladen und wiederverwendet
    • Statt verteilter Pflege vieler kleiner Bibliotheksteile wie bei npm mit isEven, isOdd, leftpad usw. bieten große universelle Bibliotheken, die von einem Verbundteam betreut werden, viel mehr Zukunftssicherheit und Kontinuität
    • Anstelle ultrafeingranularer Symbole/Abhängigkeiten wäre auch eine extrem feine Modulstruktur in Kombination mit bestehenden Tree-Shaking-Systemen eine Idee
    • Das tatsächliche Abhängigkeitsmanagement von Go kommt dem im Originalbeitrag beschriebenen Ideal nahe. Ein Modul ist eine Sammlung von Paketen, und beim Vendoring werden nur die tatsächlich verwendeten Pakete und Symbole einbezogen (ich weiß allerdings nicht genau, ob das wirklich bis auf Symbolebene funktioniert)
    • Das JS-Modulsystem unterstützt genau eine solche ultrafeingranulare Symbolverwaltung und Tree Shaking
    • Die ursprünglich vorgeschlagene ultrafeingranulare Abhängigkeitsidee wird in Rust bereits durch Section-Splitting wie --gc-sections gelöst
    • Rust ist eine Sprache, in der feingranulare Imports über API-Aufteilung mittels Crate-Features sehr gut funktionieren. Das ist anders als bei Go
    • Je nach Architektur, etwa bei einem lokal ausgerichteten Thick Client, ist eine Erstinstallation von 800 MB kein Problem, wenn im eigentlichen Betrieb nur stark eingeschränkte Kommunikation über das Netzwerk nötig ist. Dann sind auch wiederholt große Abhängigkeiten für die Zusammenarbeit im UI vertretbar
    • Die beste Methode zur Code-Wiederverwendung ist genau die Nutzung solcher Abhängigkeiten. Optimieren sollte man nur dort, wo es wirklich nötig ist
    • Schon in den 80ern wurde das Konzept wiederverwendbarer Softwarekomponenten mit Sprachen wie Objective-C Realität. Einer der großen Erfolge von Rust ist, dass eine solche Komponentisierung von Software auch in Systemprogrammiersprachen breit übernommen wurde
    • Mit Tree Shaking lassen sich Größen- und Code-Bloat-Probleme bis zu einem gewissen Grad lösen (auf Servern kümmert das oft gar nicht). Das ernstere Problem sind Risiken in der Dependency-Supply-Chain und die Sicherheit. Je größer das Unternehmen, desto eher gibt es Freigabeprozesse für den Einsatz von Open Source. Nur die Granularität zu erhöhen bringt sicherheitstechnisch nichts, wenn 1000 Funktionen von 1000 verschiedenen NPM-Autoren kommen
    • Wenn jede Ebene der Paketabstraktion nur zu 50 % genutzt wird, verdoppelt sich die Gesamtgröße auf jeder Ebene gegenüber dem tatsächlich Benötigten. Nach drei Ebenen sind 88 % nutzloser Code. Beispiel: Beim Taschenrechner von Windows 11 werden unnötige Bibliotheken mitgeliefert, bis hin zu Tools zur Kontowiederherstellung. Das ist ein Fall, in dem die Leichtigkeit des Hinzufügens von Funktionen zu mehr Komplexität führt
    • Ich stimme zu, dass die Anhäufung von Abhängigkeiten ein Problem ist. Die beste derzeitige Gegenmaßnahme ist, Systemabhängigkeiten extrem streng zu verwalten. Statt für eine einzelne 10-Zeilen-Funktion eine externe Bibliothek hereinzuholen, kopiert man den Code mitunter direkt hinein. Ein gesundes Bibliotheksökosystem ist eher die Ausnahme. Wenn Junior Engineers wahllos Abhängigkeiten hinzufügen, bremse ich das oft sofort aus
    • So selbstsicher über Rust zu urteilen, ohne auch nur die Grundlagen zu kennen, sieht man nicht oft
    • Dank Dead-Code-Eliminierung führt ein großer Abhängigkeitsbaum in kompilierten Sprachen wie Rust nicht zu aufgeblähten Binärdateien
  • Mein Problem mit dem npm-Ökosystem ist, dass viele Entwickler Abhängigkeiten einbinden, ohne über das Design nachzudenken. Zum Beispiel sollte eine glob-Bibliothek einfach nur eine Funktion für Globbing sein, aber der Autor bündelt auch ein Kommandozeilenwerkzeug und fügt dafür einen großen Parser als Abhängigkeit hinzu. Das führt zu häufigen Warnungen wie „dependency out-of-date“. Außerdem ist auch der Verantwortungsbereich einer glob-Bibliothek umstritten. Nur String-Pattern-Matching anzubieten ist ein flexibleres Design, etwa für Tests oder Dateisystemabstraktion. Viele Nutzer wollen aber allmächtige „Do everything“-Bibliotheken, und genau das erhöht die Nebenwirkungen. Ich denke nicht, dass Rust sich darin stark unterscheidet
    • Designgespür ist entscheidend, und gute Sprachen unterstützen oder behindern solche Entwicklerpräferenzen nicht. Rust, Zig und C sind Beispiele dafür. Statistisch treten Probleme dort seltener auf. Wenn sich eine Entwickler-„Masse“ bildet, entsteht ein „Bazaar-Modell“, in dem jeder frei Crates aufeinanderstapeln kann. Letztlich hoffe ich, dass Rust eine offizielle Standardbibliothek mit geordneten Namespaces und „batteries included“ bekommt, etwa stdlib::data_structures::automata::weighted_finite_state_transducer. Die Sprache selbst hat Versionsverwaltung und Abwärtskompatibilität eingebaut, daher erwarte ich weitere Verbesserungen
    • Die POSIX-glob-Funktion durchsucht tatsächlich das Dateisystem. Für String-Matching gibt es fnmatch. Idealerweise sollte fnmatch ein separates Modul sein, von dem glob abhängt. glob direkt selbst zu implementieren ist ziemlich schwierig, weil Verzeichnisstrukturen, Brace Expansion und andere komplexe Anforderungen berücksichtigt werden müssen; man braucht also eine gut entworfene Kombination von Funktionen
    • In Rust hat der Borrow Checker gewissermaßen als Schutzschild gegen Entwickler mit schwächerem Designgespür gewirkt. Wie lange dieser Effekt anhält, ist unklar
    • Einer der großen Vorteile von Rust ist, dass die Entwickler im Durchschnitt sehr kompetent sind und auch die Qualität der Crates hoch ist
    • Bun enthält ebenfalls Glob-Funktionalität
  • Es ist nicht nötig, Rust unnötig herauszugreifen; Abhängigkeitsprobleme und Supply-Chain-Angriffe sind bereits Realität. Wenn man eine neue Sprache entwerfen würde, müsste sie ein Capability-System eingebaut haben, um den gesamten Bibliotheksbaum sicher zu isolieren. Beim Design einer Bildladebibliothek könnte man zum Beispiel festlegen, dass sie nur Streams annimmt und keine Dateien, oder explizit angeben, dass sie „keine Berechtigung zum Öffnen von Dateien“ hat, sodass gefährliche Funktionen zur Compile-Zeit blockiert werden. In bestehenden Ökosystemen ist das schwer umzusetzen, aber wenn man es richtig macht, lässt sich die Angriffsfläche minimieren. Auch eine Kultur minimaler Abhängigkeiten löst das Problem nicht grundsätzlich, und Sprachen wie Go sind ebenfalls nicht frei von Supply-Chain-Angriffen
    • Es wäre wichtig, eine Sans-IO-Kultur aktiv zu verbreiten, also ein Design, bei dem Abhängigkeiten nicht direkt selbst IO ausführen. Bei der Vorstellung neuer Bibliotheken sollte man eine Kultur etablieren, in der direkte IO-Implementierungen kritisch hinterfragt werden. Natürlich reicht öffentliche Begutachtung allein nicht aus, aber es wäre gut, wenn sich das Sans-IO-Prinzip verbreitet
    • Ein Beispiel ist WUFFS, eine Sprache für Spezialzwecke. Praktisch kann sie nicht einmal „Hello world“ ausgeben und hat keinen String-Typ. Stattdessen ist sie ausschließlich auf das Parsen nicht vertrauenswürdiger Dateiformate spezialisiert. Es sollte mehr solcher Spezialsprachen geben. Sie sind schnell und ungefährlich, sodass unnötige Prüfungen reduziert werden können
    • Java und das .NET Framework hatten schon vor Jahrzehnten Funktionen für Partial Trust/Capabilities, wurden aber nicht breit genutzt und schließlich abgeschafft
    • In Rust gibt es eine leicht ähnliche Tendenz. Über #![deny(unsafe_code)] wird die Verwendung von unsafe-Code zu einem Compilerfehler und dem Nutzer kenntlich gemacht. Es ist aber keine allumfassende Erzwingung, denn mit ausdrücklicher Erlaubnis kann weiterhin unsafe verwendet werden. Man könnte sich die Einführung eines Capability-Systems vorstellen, das transitive Funktionen der Standardbibliothek ähnlich wie Feature-Flags steuert
    • Ich würde so eine Idee gern selbst umsetzen und hoffe, dass sie irgendwann Realität wird. In Rust ist ein teilweises Capability-Tracking über Linter-basierten Ansatz möglich. Die Unsoundness-Probleme des Compilers müssten allerdings gelöst werden
    • Eine perfekt statische Erzwingung in bestehenden Sprachen oder Ökosystemen ist schwierig, aber schon Laufzeitprüfungen würden den Großteil des Nutzens bringen. Wenn Bibliothekscode aus dem Quelltext kompiliert wird, könnte man um jeden Systemaufruf einen Berechtigungsprüfungs-Wrapper legen. Bei Verstößen würde ein Panic ausgelöst, und man müsste Capability-Profile je Bibliothek erstellen und verteilen. Dass Ähnliches funktionieren kann, wurde in TypeScript bereits gezeigt
    • Haskell setzt so einen Ansatz teilweise mit der IO-Monade um. Funktionen, die nicht direkt IO ausführen dürfen, werden über die Typsignatur eingeschränkt
    • Meiner Meinung nach müsste man für ein solches System die Art und Weise, wie mit dem OS kommuniziert wird, grundsätzlich ändern. Die Falle ist, dass selbst das Lesen eines Streams letztlich Systemaufrufe zum Dateilesen verwenden kann
    • Es gibt ein Projekt namens Capslock, das in Go ungefähr in diese Richtung arbeitet
    • Wenn man bereits beim Entry-Programm einschränkt, dass Bibliotheken keine System-APIs importieren dürfen, dann kann Capability-Weitergabe allein über Dependency Injection erfolgen. Das lässt sich auch in heutigen Sprachen entwerfen, aber in der Praxis bricht dann die Kompatibilität mit bestehenden Bibliotheken
    • Ich frage mich, ob so etwas Ähnliches schon einmal umgesetzt wurde. In heutigen Sprachen scheint die Anwendung sehr schwierig zu sein
    • Mit nur einer Sprache geht es nicht; man braucht ein mehrsprachiges Ökosystem
    • Im TypeScript-Ökosystem greift so eine Einschränkung zum Beispiel natürlich, wenn in einer Umgebung ohne Dateiklassen kompiliert wird und der Build deshalb fehlschlägt
  • Das ist ein allgemeines Problem der modernen Softwareentwicklung. Die Einstiegshürde ist niedrig, und die Wiederverwendung vorhandenen Codes nimmt zu. Abhängigkeiten sind letztlich nicht vertrauenswürdiger Code. Wenn es keine technische Lösung gibt, muss irgendwer dauerhaft Code Review, Wartung sowie soziale und rechtliche Vertrauenssysteme aufrechterhalten. Wenn man mehr in die Rust-Standardbibliothek aufnimmt, müsste das Kernteam die Verantwortung für den gesamten Code übernehmen, was die Wartungslast erhöht
    • Die sichtbare Schwere des Problems ist je nach Sprache unterschiedlich. Sprachen mit starker Standardbibliothek können mit minimalen externen Abhängigkeiten viel erreichen und sind im Vorteil. Bei Sprachen mit wenig Grundfunktionalität wie JS/Node sind externe Abhängigkeiten der Normalfall. „Leichtgewichtigkeit“ ist nicht immer gut
    • Ich denke, Rust braucht mehr Integration in die Standardbibliothek. Go hat eine hervorragende Standardbibliothek, während in Rust selbst grundlegende Dinge wie Web, tls, x509, base64 usw. bei Auswahl und Verwaltung von Bibliotheken schmerzhaft sind
    • Gilad Bracha hat einen interessanten Ansatz zum Sandboxing von Drittanbieterbibliotheken vorgeschlagen: Imports entfernen und alles über Dependency Injection lösen. Wenn Dinge wie ein IO-Subsystem nicht injiziert werden, kann Drittanbieter-Code darauf niemals zugreifen. Wenn man nur Lesefunktionen geben möchte, kann man auch nur diese gewrappt injizieren. Im Bereich der Systemprogrammierung gibt es jedoch Grenzen, etwa wegen unsafe-Code
    • Es wurde auch vorgeschlagen, alle Bibliotheken wie in QubesOS in isolierten Umgebungen auszuführen: der eigene Code in dom0, jede Bibliothek in einer separaten Template-VM und Kommunikation über Netzwerk-Namespaces. Für sensible Branchen wäre das praktikabel
    • Nach meinem Eindruck machen wir nicht kompliziertere Dinge, sondern dieselben Dinge nur auf kompliziertere Weise. Das Ziel selbst ist nicht schwieriger geworden
    • Tatsächlich ist die Lage je nach Sprache verschieden. In C/C++ ist das Hinzufügen von Abhängigkeiten schwierig, und wenn noch Cross-Platform-Support dazukommt, wird es viel umständlicher, weshalb ein ähnliches Problem dort nicht in gleichem Maße auftritt
    • Das Problem ist unnötiger Code-Bloat. Fast alle Projekte sind voller unnötiger Komplexität und Overengineering. Das ist ein Branchenproblem
  • blessed.rs empfiehlt eine Liste nützlicher Bibliotheken, die schwer in die Standardbibliothek aufgenommen werden können. Mir gefällt dieses System, weil die meisten Pakete dadurch auf einen bestimmten Zweck begrenzt und besser beherrschbar bleiben
    • cargo-vet ist ebenfalls zu empfehlen. Damit lassen sich vertrauenswürdige Pakete nachverfolgen und definieren, etwa von Paketen, die vor der Aufnahme ein Expertenaudit brauchen, bis hin zu halb-YOLO-Regeln wie „Pakete, die von Tokio-Maintainern gepflegt werden, vertrauen wir einfach“. Es ist etwas formaler als blessed.rs und eignet sich gut, um im Team eine offizielle Quasi-Standardliste zu teilen
    • So ein System hätte ich auch gern für Python
    • Nach dem Durchsehen scheint das wirklich ein sehr gutes Empfehlungsprojekt zu sein
  • Seit dem Leftpad-Vorfall haftet Paketmanagern ein negatives Image an. Dinge wie Tokio sind faktisch Funktionen auf Sprachebene; wenn der OP meint, man müsse das gesamte Go oder sogar die V8-Engine von Node direkt auditieren, ist das unrealistisch
    • Tatsächlich wird auch Tokio von irgendjemandem fortlaufend auditiert. Es tun nicht viele, aber zumindest jemand tut es
    • Dass Cargo zwei Versionen derselben Abhängigkeit gleichzeitig einbindet, wenn zwei Abhängigkeiten unterschiedliche Versionen benötigen, ist eine besondere Unterstützung von Cargo
  • Feature-Flags in Cargo sind wirklich großartig. Ich reiche oft PRs ein, um unnötige Abhängigkeiten hinter solche Flags zu packen. Mit cargo tree lässt sich der Abhängigkeitsbaum leicht ansehen. Eine Ansicht der Codezeilen, die tatsächlich in der Binärdatei landen, ist dagegen nicht besonders aussagekräftig. Durch Function-Inlining landet das meiste ohnehin in main
    • Schade, dass npm keine Feature-Flags hat. Ich frage mich, ob es einen Paketmanager gibt, der das unterstützt. Ich würde gern in internen Bibliotheken Code mit Abhängigkeiten zu bestimmten Frameworks isolieren und dadurch erweitern
  • Bei mir ist es ähnlich. Mit Cargo lassen sich Abhängigkeiten viel zu leicht hinzufügen, und selbst wenn man persönlich vorsichtig ist, führen schon ein paar zusätzliche Einträge zu Dutzenden transitiven Abhängigkeiten. Gleichzeitig ist es unrealistisch zu sagen, man solle sie einfach nicht verwenden. In C++ sieht man dieses Phänomen seltener. Bei Rust fühlt es sich wegen der starken Aufteilung in kleine Pakete so an, als würde man zufälligen Code aus dem Internet holen. Ich mag Rust selbst, aber diese Struktur gefällt mir nicht
    • In einem im Rust-Subreddit verlinkten Beitrag wurde erklärt, dass C++-Abhängigkeiten oft deshalb weniger sichtbar sind, weil sie meist als dynamische Bibliotheken bereitgestellt werden. Andererseits ist es auch ein Vorteil, dass man sich auf die Stabilitäts- und Sicherheitsverwaltung des Paketmanagers des Betriebssystems verlassen kann. Für Rust wäre so etwas wie eine erweiterte Standardbibliothek wünschenswert
    • C++-Abhängigkeiten sind kompliziert und die Build-Systeme chaotisch, deshalb halte ich die weniger stabilen Rust-artigen Abhängigkeiten für die bessere Alternative. Tatsächliche transitive C++-Abhängigkeiten sind wegen vorkompilierter Formen nur noch unsichtbarer
    • Die starke Aufteilung in kleine Pakete ist in Rust weniger eine „Philosophie“ als eine Folge der Build-Geschwindigkeit. Wenn ein Projekt wächst, teilt man es in Crates auf. Das geschieht nicht primär aus Abstraktionsgründen, sondern weil die Build-Performance einen zu solcher Strukturierung zwingt
    • Man muss der Logik „Dann benutze es eben einfach nicht“ nicht bedingungslos zustimmen. Darüber sollte man weiter nachdenken
    • C++ und CMake sind so schwierig, dass viele Softwareprojekte sie in der Praxis einfach nicht verwenden
  • Kernausgereifte Bibliotheken nutze ich als Open Source, kleinere Funktionen übernehme ich nach Sichtung aus Open Source direkt per Copy-and-Paste in den eigenen Code. Der Code wird dadurch zwar etwas unnötig größer, aber der Prüfaufwand für externen Code und die Angriffsfläche der Supply Chain sinken. Große Bibliotheken bleiben natürlich trotzdem ein Problem, aber man kann auch nicht alles selbst schreiben. Das ist kein reines Rust-Problem, sondern ein allgemeines
  • Früher habe ich bei wichtigen Systemen in anderen Sprachen eine Richtlinie für minimale Module/Pakete festgelegt, alle verwendeten Pakete in interne Repositories übernommen und jeden Branch und jedes Update auditiert. In Bereichen wie Frontend ist so eine strikte Verwaltung realistisch kaum möglich. In letzter Zeit stellen sich ähnliche Fragen beim Abhängigkeitsmanagement auch bei laut beworbenen Open-Source-AI-Tools und -Modellen. Selbst bei persönlichen Rust-Projekten sind mir explodierende Abhängigkeiten bei UI-/Async-Bibliotheken am unangenehmsten. Wenn auch nur eine davon verwundbar wird, ist das ein Einfallstor; es ist nur eine Frage der Zeit
    • In der Praxis ist es sinnvoll, CI/CD-Systeme nur mit offiziellen internen Repositories zu verbinden. Entwickler können lokal installieren, was sie wollen, aber nicht autorisierte Commits werden auf dem Build-Server blockiert
    • Es gibt RFCs, die Sicherheitsrisiken angehen wollen, aber wohl aus kulturellen Gründen kommt es nicht zu abrupten Änderungen
    • Das Coole an Rust ist, dass man Async auch direkt auf die gewünschte Weise selbst implementieren kann. Man ist nicht an eine bestimmte Implementierung gebunden
 
iolothebard 2025-05-11

Das ist nicht nur ein Problem von Rust.
Es ist ein gemeinsamer Vorteil und zugleich ein potenzielles Problem aller Sprachen, die öffentliche Paket-Repositories und Paketmanager mit transitiven Abhängigkeiten unterstützen.
Letztlich müssen die Leute, die es verwenden, eben gut damit umgehen …
Trotz des leftpad-Vorfalls bei Node&npm hat sich nichts geändert.