Don't write Rust like it's Java (2023)(jgayfer.com)
jgayfer.com
Don't write Rust like it's Java (2023)
https://jgayfer.com/dont-write-rust-like-java/
44 comments
Of course this is on point, you shouldn't write Rust like it's Java, however looking at Java, you shouldn't write anything like it's Java. Java has brought forth the biggest madness of tacking on OOP to everything that is still making 3 billion software projects unreadable.
Even in Java, the interfaces everywhere style was never great. Somehow Java got picked up by Architecture Astronauts that love writing enterprise pattern layer cake. Nothing in the language makes this style necessary, it's perfectly possible to write short and sweet code, and yet the Java culture got infected with this overcomplicate everything culture.
Java got picked up by the enterprise software crowd. (Maybe blame Sun for that - the enterprise people were going to buy a lot of servers.) The enterprise software culture is where you had "software analysts", which was a different job description from "programmer". That culture took over Java and made it what we now think of as "Java culture", but it wasn't really Java. It was really the enterprise software culture.
That's my opinion, anyway.
That's my opinion, anyway.
I kinda prefer the interface everywhere to the inheritance everywhere crowd.
But I've mostly used/debugged groovy code so maybe I'm partial.
But I've mostly used/debugged groovy code so maybe I'm partial.
[deleted]
[deleted]
> Java has brought forth the biggest madness of tacking on OOP to everything
It was C++, if we are being historically accurate. It’s actually a positive in Java’s case that it managed to make these systems even run.
It was C++, if we are being historically accurate. It’s actually a positive in Java’s case that it managed to make these systems even run.
I love how everyone likes to blame Java for OOP, when most of the stuff that eventually ended up in enterprise OOP patterns, predate Java for 10 years.
Including those stupid "coding Java in C++" remarks, when Java OOP model is based on a mix of C++ frameworks, and Objective-C features.
Including those stupid "coding Java in C++" remarks, when Java OOP model is based on a mix of C++ frameworks, and Objective-C features.
And Objective-C is just Smalltalk with C syntax.
Yep, "With enough thrust, pigs fly just fine" - Java's addition of GC was that brute force solution to the tangle of mutually mutating randomly interconnected objects that in C++ tended to collapse under its own weight without making it to production.
Totally. The amount of indirection and badly written code with hundreds and thousands of classes which take a simple data and combine it with magic methods which you have to open the class to understand is crazy.
A whole beach of sand,every grain a unique object,influenced by water,milled from a clifffactory.No other way to model a holiday..
This made my morning, thanks.
Even other JVM languages aren't safe from this, since you still need to interface with Java
Nah, the architecture space astronauts just changed from Smalltalk, C++ into Java.
Generally good advice is: Don't program Rust like nearly any other programming languages you know.
If you try to program Rust like C you are going to fight and lose.
If you try to program Rudt like and Object Oriented Language you will eventually go mad.
If you like a Zen master let all previous knowledge and pre-conceived notions of what beautiful code has to be out of the window and do it the Rust way, suddenly things flow and stop being a pain.
And that experience will help you be a better programmer, period.
If you try to program Rust like C you are going to fight and lose.
If you try to program Rudt like and Object Oriented Language you will eventually go mad.
If you like a Zen master let all previous knowledge and pre-conceived notions of what beautiful code has to be out of the window and do it the Rust way, suddenly things flow and stop being a pain.
And that experience will help you be a better programmer, period.
Also applies to: Lisp, Haskell, Smalltalk, APL, Forth, even C. Maybe you can write Kotlin like C#, or Lua like JS, but style is not portable in general.
> Remember that great feature of Rust being memory safe? It comes at the cost of not being able to easily “inject” something that implements a trait.
This is not at all the reason. The real reason is that Rust chooses to make the runtime overhead required for dynamic typing and heap allocation explicit, not anything to do with memory safety.
This is not at all the reason. The real reason is that Rust chooses to make the runtime overhead required for dynamic typing and heap allocation explicit, not anything to do with memory safety.
Well, java won’t have any overhead in many cases (e.g. only single implementation), as the JIT compiler will do its job.
Everybody worth their salt should know that those languages have completely different working models in mind.
Java puts everything into the heap and creates easy expressability by having pointers everywhere. In contrast, Rust often avoids pointers, prefers the stack and is very stingy with memory access.
These languages are completely orthogonal to each other and it has consequences in their usage. Not that a good chunk of people wouldn’t butcher the code they are given in any codebase…
Java puts everything into the heap and creates easy expressability by having pointers everywhere. In contrast, Rust often avoids pointers, prefers the stack and is very stingy with memory access.
These languages are completely orthogonal to each other and it has consequences in their usage. Not that a good chunk of people wouldn’t butcher the code they are given in any codebase…
Primitive types are placed on the stack, as are those classes proven to not escape current scope, thus not everything.
When Valhala is finally merged, then value class and value record will also not be on the heap by default.
Finally, even without Valhala, Unsafe and Panama allow for off heap allocation, thus again, not everything.
When Valhala is finally merged, then value class and value record will also not be on the heap by default.
Finally, even without Valhala, Unsafe and Panama allow for off heap allocation, thus again, not everything.
You are correct, I was oversimplifying to get the point across. Valhalla is one of the most promising upcoming technologies I am anticipating, but I don't expect it to change the approach to writing most Java programs.
What makes Java so good is that it is a language written for creating high quality libraries and frameworks. Sure, it will be nice to define your own zero-cost classes but the largest benefit will stem from improved an standard library as well as an enabler for high-performance programming and GPU access.
What makes Java so good is that it is a language written for creating high quality libraries and frameworks. Sure, it will be nice to define your own zero-cost classes but the largest benefit will stem from improved an standard library as well as an enabler for high-performance programming and GPU access.
I’ve also had some success writing Java more like as if it’s rust, using Java Records instead of classes to create immutable types.
I’m not sure if I would recommend it if you’re working as part of a team though, other java devs will hate your code lol.
I’m not sure if I would recommend it if you’re working as part of a team though, other java devs will hate your code lol.
Hasn't the use of behaviourless value objects (essentially structs) been commonplace in both the JVM and the related .NET-world for the better part of a decade now, maybe more?
From my experience, "anemic DDD inspired architecture" is fairly common in enterprise .NET codebases. These do heavily use all kinds of value object, DTO and DTO-like objects with the logic that processes them being defined separately.
Unfortunately, most of them still suffer from premature and very unnecessary abstraction bloat. Luckily, the trend for terseness and minimalism is gaining more and more momentum each year, with just a single 100 LOC Program.cs microservices with asp.net core's minimal API no longer raising eyebrows and causing arguments in the teams as much as it used to. Extensive support for pattern matching, records, closures (since a long time ago) and other ways to define strict and terse data structures is also helping to preserve functional nature of this approach.
Note that JVM does not have structs and has a long way to go until project Valhalla becomes a reality. FWIW it has been less of an issue thanks to quite strong escape analysis as implemented by OpenJDK and GraalVM, but it does not afford the same guarantees and degree of control the structs do (not to mention lack of monomorphized struct generics as in Rust and .NET). In the "average codebase", structs in .NET have not been as popular historically, but with heavy focus on "close to the metal" features in both compiler optimizations and syntax, and increasing awareness of C# being a viable and effective systems programming language, this also is changing.
Unfortunately, most of them still suffer from premature and very unnecessary abstraction bloat. Luckily, the trend for terseness and minimalism is gaining more and more momentum each year, with just a single 100 LOC Program.cs microservices with asp.net core's minimal API no longer raising eyebrows and causing arguments in the teams as much as it used to. Extensive support for pattern matching, records, closures (since a long time ago) and other ways to define strict and terse data structures is also helping to preserve functional nature of this approach.
Note that JVM does not have structs and has a long way to go until project Valhalla becomes a reality. FWIW it has been less of an issue thanks to quite strong escape analysis as implemented by OpenJDK and GraalVM, but it does not afford the same guarantees and degree of control the structs do (not to mention lack of monomorphized struct generics as in Rust and .NET). In the "average codebase", structs in .NET have not been as popular historically, but with heavy focus on "close to the metal" features in both compiler optimizations and syntax, and increasing awareness of C# being a viable and effective systems programming language, this also is changing.
I'm not familiar with the .NET ecosystem at all, but in general I think the problem with the "just write everything in the framework's layer" for most companies is your whole application becomes tightly coupled to that framework, and more specifically that version of the framework. That's likely a much bigger problem for monoliths which I would imagine is still the majority of apps, even in 2024.
Most developers likely don't stick around long enough to see the pains of doing major upgrades on large codebases.
I've been using a layered architecture with great success for a large fintech application. We've even migrated from one web framework to a completely dissimilar web framework trivially as the majority of our code in the business layer needed no changes.
Most developers likely don't stick around long enough to see the pains of doing major upgrades on large codebases.
I've been using a layered architecture with great success for a large fintech application. We've even migrated from one web framework to a completely dissimilar web framework trivially as the majority of our code in the business layer needed no changes.
I find myself defining my own traits very rarely these days. Traits enable polymorphic actions on data. Most of the time, you simply don't need that.
An enum often suffices to express the different combinations.
Of course, from a pattern perspective, that is "not extensible" but it often ends up being easier to maintain and in Rust, often compiles down to more efficient code.
An enum often suffices to express the different combinations.
Of course, from a pattern perspective, that is "not extensible" but it often ends up being easier to maintain and in Rust, often compiles down to more efficient code.
I feel like this axiom is true in general.
Don't write [insert lang here] like it's Java.
I've seen this in Ruby code bases, TS code bases, even Erlang code bases (using gen servers like an OO primative.)
Don't write [insert lang here] like it's Java.
I've seen this in Ruby code bases, TS code bases, even Erlang code bases (using gen servers like an OO primative.)
That's funny. Writing Ruby with Sorbet works fairly well if you just pretend it's Java most of the time. Have to keep in mind that the static typechecking is broken, so can't go refactoring wildly and get it on the first go. Limiting use of metaprogramming to what amounts to being annotations is also friendly to coworkers.
Of course what's being picked upon is a specific kind of Java code. It's better to be specific than technically incorrect, as all written Java is Java regardless of how you write it. The article goes a good job (comments here less so), except for this bit:
> While not entirely accurate, there’s some truth to the trope that Java developers need everything to be an interface (I am one such developer).
That's not just Java, that's Dogma.
What does work for me is writing in most languages (Java included) as if they're functional ones without lots of variable reassignment or data mutation.
Of course what's being picked upon is a specific kind of Java code. It's better to be specific than technically incorrect, as all written Java is Java regardless of how you write it. The article goes a good job (comments here less so), except for this bit:
> While not entirely accurate, there’s some truth to the trope that Java developers need everything to be an interface (I am one such developer).
That's not just Java, that's Dogma.
What does work for me is writing in most languages (Java included) as if they're functional ones without lots of variable reassignment or data mutation.
I feel like when people write Ruby like its Java as opposed to say, smalltalk, a lot of what makes ruby wonderful is lost.
This makes me immediately think of NestJS which copies everything from classes, annotations & ORMs to service locators. It still managed to grow to 3M weekly downloads.
Maybe there's a certain safety in doing thing how they've always been done - even if that seems backwards to me.
Maybe there's a certain safety in doing thing how they've always been done - even if that seems backwards to me.
Sometimes it is easier to arc dyn your way out of things especially in the async world and if you need to decouple two parts of the software. You get a sort of slower ref count gc similar to swift but it makes your life more enjoyable, always depends on your usecase.
I look at Rust more as an FP lang than an OO lang. By FP I mean: you have data and logic; never a combination of both (classes/objects).
Java makes this king of separation between data and logic really hard.
Kotlin came to the rescue for us. It allows us to move our Java/OO codebase in a slightly more FP direction: uncouple data and logic, immutability is preferred, no (or very little: yes looking a you shitty "platform types" in Kotlin) implicit nulls.
Java makes this king of separation between data and logic really hard.
Kotlin came to the rescue for us. It allows us to move our Java/OO codebase in a slightly more FP direction: uncouple data and logic, immutability is preferred, no (or very little: yes looking a you shitty "platform types" in Kotlin) implicit nulls.
Don’t even write Java like it’s Java!
Discussing curriculum and textbooks for a Java class today, someone recommended a book that was most recently updated for Java 7. When I pointed that out, the response was:
“The level we teach at, the new Java features won’t make much of a difference.”
Java is a culture just as much as a language, and the culture is stuck somewhere in the past in a lot of places, especially in education.
Discussing curriculum and textbooks for a Java class today, someone recommended a book that was most recently updated for Java 7. When I pointed that out, the response was:
“The level we teach at, the new Java features won’t make much of a difference.”
Java is a culture just as much as a language, and the culture is stuck somewhere in the past in a lot of places, especially in education.
I think ownership and especially lifetimes are a lot harder to reason about than the trait system tbh, even traits implementing traits, GATs, impl trait, and so on and so forth.
While we're at it, don't write Ruby like its Java, either (Not to name names, but I'm looking at you, OpenTelemetry gems).
Yoo, congratulations to the author for going from this blog post dated to October 2023 to shipping an actual rust crate (bevy_light_2d)!
I suspect the author might be reading this post back and cringe a bit. That last code listing with handle_session_completed triggers my inner clippy. Why clone session.customer_id to then pass it as a reference? Why are those CheckoutSession fields Options?
The other advice I would give is to identify what the author calls "Service" as what is called "view type" or "newtypes" in rust. A newtype wraps another type, to give it a different set of methods. For example, a counter could wrap an i32 and only allow incrementing and reading the value. Or put together a set of references to fields/slices of a struct.
It can be useful, but the way it's used by the author here is not very rusty, and the final suggestion of using a function is a descent alternative. Although I think defining handle_session_completed as a method on CheckoutSession or UserRepo might be better.
I suspect the author might be reading this post back and cringe a bit. That last code listing with handle_session_completed triggers my inner clippy. Why clone session.customer_id to then pass it as a reference? Why are those CheckoutSession fields Options?
The other advice I would give is to identify what the author calls "Service" as what is called "view type" or "newtypes" in rust. A newtype wraps another type, to give it a different set of methods. For example, a counter could wrap an i32 and only allow incrementing and reading the value. Or put together a set of references to fields/slices of a struct.
It can be useful, but the way it's used by the author here is not very rusty, and the final suggestion of using a function is a descent alternative. Although I think defining handle_session_completed as a method on CheckoutSession or UserRepo might be better.
[deleted]
[deleted]
[deleted]