Rust vs. Java: Einsatzgebiete

31.01.2024

Java und Rust sind zwei Programmiersprachen, die unterschiedliche, aber wichtige Nischen besetzen. In meinem Blogbeitrag Rust vs. Java habe ich einen Vergleich zwischen den beiden Sprachen angestellt. Zum Abschluss der Reihe möchte ich noch verschiedene Einsatzgebiete aufzeigen und erörtern, wie gut sich die Sprachen jeweils dafür eignen.

 

Hauptfokus der Sprachen

Während Java als eine der ältesten und am weitesten verbreiteten Sprachen für Unternehmensanwendungen bekannt ist, hat sich Rust als moderne Systemsprache etabliert.

Obwohl initial nicht dafür gedacht, hat Java sich doch rasch als einer der führenden Akteure im Bereich der Unternehmens- bzw. Serveranwendungen etabliert. Mit der Zeit hat sich ein großes Ökosystem rund um Java entwickelt, das viele Spezifikationen und Frameworks zur Serverentwicklung bereithält, wie etwa JEE oder Spring, um nur die bedeutendsten zu nennen. Zudem ist Java in der Entwicklung mobiler Anwendungen für die Android-Plattform weit verbreitet.

Der Hauptfokus von Rust liegt in der Systemprogrammierung als Alternative zu C und C++, mit der robustere, weniger fehleranfällige Programme entwickelt werden können. Sowohl in der Entwicklung von Linux[1] als auch Microsoft Windows[2] wird Rust bereits eingesetzt.

 

Einsatz im Serverbereich

Doch auch im Bereich der Entwicklung von Serveranwendungen hat sich bei Rust einiges getan. Wie im letzten Beitrag[3] gezeigt, bietet Rust mittlerweile auch eine solide Auswahl an Tools für die Entwicklung von Webanwendungen und Microservices.

Die Tatsache, dass Java in der JVM läuft, die Bytecode interpretiert, resultiert in einem gewissen Ressourcen- und Effizienz-Overhead. Insbesondere gegenüber Rust, das direkt in nativen, ausführbaren Code kompiliert, und blitzschnelle Startzeiten aufweist.

Doch im Serverbereich spielen Startzeiten in der Regel eine eher untergeordnete Rolle. Darüber hinaus bietet Java viele Funktionen zur Optimierung. Der Just-in-Time-Compiler beispielsweise kompiliert zur Laufzeit häufig genutzte Programmteile in nativen Code, was insbesondere bei einer langen Laufdauer zum Tragen kommt.

So wird bei Java genau an den Stellen optimiert, die am häufigsten ausgeführt werden. Dies kann über eine lange Laufdauer auch die längere Startup-Zeit wieder wettmachen.

Auch bei Rust sind nutzungsbasierte Optimierungen möglich. Dafür kann ein Profil zur typischen Laufzeit des Programms erstellt werden, um auf Basis dieser Daten eine optimiere Binary zu erstellen (s. Rust – Profile guided optimization). Zudem kennt der JIT-Compiler von Java die Plattform, auf der die Anwendung läuft, und kann die Optimierungen auf genau diese Plattform abstimmen. Auch bei Rust ist es möglich, Binaries auf spezifische CPU-Architekturen zu optimieren, oder auch auf einzelne Instruction Sets. Dazu muss die Architektur der späteren Laufzeitumgebung vorab bekannt sein.

 

Serverless

Im Bereich „Functions as a Service“ (Faas“), kann Rust mit seinen blitzschnellen Startzeiten punkten, da sich die für Serverless Functions in Rechnung gestellten Kosten in der Regel nach der Laufdauer der Funktionen richten.

Dessen ungeachtet wird Rust überraschenderweise lediglich von AWS Lambda explizit unterstützt, und zwar durch den Rust runtime client[4], der sich derzeit noch in einem experimentellen Stadium befindet[5]. Andere Provider unterstützen Rust bislang nur indirekt anhand generischer Mechanismen, wie beispielsweise Microsoft Azure mit einem custom HTTP handler[6], oder Google via CloudRun[7]. Dennoch ist es auf allen genannten Plattformen möglich, Rust als Sprache für Functions as a Service (Faas) zu verwenden.

Die Unterstützung von Java bei den Serverless-Providern ist dagegen erstaunlich gut. Von allen Produkten der drei größten FaaS-Anbietern, nämlich AWS Lambda[8], Azure Functions[9] und Google Cloud Functions[10], ist entsprechendes Tooling für Java vorhanden.

Dazu gehören auch Möglichkeiten, wie beispielsweise bei AWS Lambda sie anbietet, die Runtimes via Provisioned Concurrency[11] „warm“ zu halten, um die Latenzen bei Lambda-Aufrufen möglichst gering zu halten. Dadurch werden die längeren Anlaufzeiten von Java zumindest teilweise wieder wettgemacht.

Darüber hinaus bieten sowohl Quarkus als auch Spring Unterstützung für Serverless-Funktionen, die Java-Entwicklern gewohnte Programmiermodelle anbieten, wie z. B. Dependency Injection, oder die Möglichkeit, durch Entkopplung von den Cloud Providern einen sogenannten „Vendor Lock-in“ zu vermeiden. Mit Quarkus hat man sogar die Möglichkeit, per Ahead-of-Time-Kompilierung nativen Code zu erzeugen, der in Sachen Performance mit Rust-Programmen vergleichbar ist.

Hier besteht allerdings die Gefahr von „ClassNotFoundExceptions“ zur Laufzeit, wenn Klassen per Reflection dynamisch nachgeladen werden. Der AOT-Compiler führt eine statische Analyse durch und kompiliert nur diejenigen Teile des Codes, die laut Analyseergebnis auch wirklich zur Laufzeit ausgeführt werden (sog. „Tree Shaking[12]). Daher muss zusätzlicher Aufwand betrieben werden, um sicherzustellen, dass der Compiler auch diejenigen Codeteile inkudiert, die dynamisch nachgeladen werden. Dieses Problem gibt es bei Rust nicht. Im Gegensatz zu Java gibt es hier keinen dynamisch geladenen Code. Die Macro-Attribute könnte man naiv durchaus mit Java-Annotationen vergleichen, wie sie z. B. in Spring genutzt werden, weil sie oberflächlich betrachtet ähnliche Effekte bewirken (die von Entwicklern gerne mal als „Magie“ bezeichnet werden). Doch unter der Haube funktionieren sie komplett unterschiedlich, denn in Rust wird expliziter Code erzeugt, der im ausführbaren Programm dann auch tatsächlich vorhanden ist.

 

Fazit

Rust und Java haben jeweils ihre eigenen Einsatzbereiche, in denen sie jeweils glänzen: Rust in der Systemprogrammierung als Alternative zu C/C++, für robustere Software mit nahezu gleicher Performance, sowie für Serverless Functions mit schneller Startup-Zeit.

Java dagegen ist stark im Bereich Entwicklung von Unternehmenssoftware und Mobiler Apps.

Doch auch in anderen Einsatzszenarien machen die Sprachen jeweils keine schlechte Figur. Wem beispielsweise eine robuste, fehlerarme Software wichtiger ist als die Laufzeitoptimierung eines JIT-Compilers, für den kann Rust durchaus als Sprache für Microservices infrage kommen.

Java dagegen schlägt sich trotz schlechterer Startup-Zeiten wacker im „Serverless“-Bereich.

Letztlich hängt es von den Bedingungen im jeweiligen Projekt ab, welche Technologie eingesetzt werden sollte. Zu den Faktoren, die in entsprechende Technologie-Entscheidungen berücksichtigt werden sollten, gehören beispielsweise das vorhandene Knowhow, und die Bereitschaft im Team, sich neue Technologien anzueignen. Denn aktuell ist die Zahl an verfügbaren Java-Entwicklern am Markt um ein Vielfaches größer als Rust-Experten. Doch die Chancen stehen gut, dass sich das in Zukunft ändert. Insbesondere bei Studenten und Hochschulabsolventen ist Rust sehr beliebt. Und wenn sich Rust im Kontext von Web Assembly[13] durchsetzt, das sich derzeit als neuer Standard für das Web etabliert, könnte der Bedarf für Rust-Entwickler bald stark anwachsen.

[Update 02.02.2024: Im Original-Beitrag hatte ich geschrieben, dass „es nicht unbedingt gegeben ist, dass ein Rust-Programm auf der Plattform kompiliert wird, auf der sie später auch laufen wird“. Ein Leser wies mich darauf hin, dass es möglich ist, Rust-Binaries auf bestimmte CPU-Architekturen zu optimieren, und dass es zudem möglich ist, Profile zur Laufzeit zu erstellen, mit denen entsprechend optimierte Binaries kompiliert werden können. Der Beitrag wurde von mir entsprechend angepasst.]

 


[1] https://www.heise.de/news/Rust-Code-im-Linux-Kernel-Merge-steht-laut-Linus-Torvalds-ab-Linux-5-20-bevor-7154453.html

[2] https://www.drwindows.de/news/rust-microsoft-baut-anteil-der-sicheren-programmiersprache-im-windows-kernel-aus

[3] https://blog.doubleslash.de/ein-microservice-mit-rust

[4] https://github.com/awslabs/aws-lambda-rust-runtime

[5] https://docs.aws.amazon.com/lambda/latest/dg/lambda-rust.html

[6] https://learn.microsoft.com/en-us/azure/azure-functions/create-first-function-vs-code-other?tabs=rust

[7] https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-service-other-languages

[8] https://docs.aws.amazon.com/lambda/latest/dg/lambda-java.html

[9] https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption

[10] https://cloud.google.com/functions/docs/concepts/java-runtime

[11] https://docs.aws.amazon.com/lambda/latest/dg/provisioned-concurrency.html

[12] https://en.wikipedia.org/wiki/Tree_shaking

[13] https://rustwasm.github.io/book/

Zurück zur Übersicht

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*Pflichtfelder

*