Live data from Hacker News

Jodd – The Unbearable Lightness of Java

jodd.org

221–230 of 239 posts

Re: Jodd – The Unbearable Lightness of Java

#221

Earlier quoted context omitted.

> I did a fair bit of work in Go at Pivotal. I found Go anything but readable - a comical amount of boilerplate (especially around error handling), incredibly wordy constructs for simple tasks like making http requests, and the language is almost overtly hostile to functional programming (no generics!). Are you saying that Java is better about any of that?

Yes, absolutely. Java has had a competent implementation of generics since 2004 (Java 5) and really embraced functional programming in 2014 (Java 8). Any application of significance will require more LoC in Go than Java, hands down. Just compare Java streams with Go container classes. Go's aren't typesafe (though that will hopefully change when generics are officially released) and almost every operation requires imp…

Fair points. I haven't worked with Go in a few years, and I remember hating it when I did, but I feel like I remember hating Java more. It's possible that part of the Java hate is not from the language itself, but from the ecosystem.

Can you elaborate on Java streams vs Go's containers? I assume you mean things like List and Heap in Go? I'm not sure why you'd compare those to Java's stream API rather than Java's collections. In any case, I do agree that Java's standard library has WAY better collections than Go does, and Go doesn't have the excuse of wanting a minimal standard library.

However, I'll push back a bit on the complaint that working with Go's containers/collections/whatever requires imperative code for everything. Now, I'll remind myself that one of your original points was that Go was "actively hostile toward functional programming" and I retorted to imply that Java was just as bad at all of the things you mentioned. I'll concede that Java isn't actually quite as hostile toward functional programming as Go. But, I'll move the goalposts a bit and claim that supporting some few functional programming patterns isn't inherently good and doesn't automatically make a language better.

> And endless `if err != nil return err` every time you want to call a function - which actually destroys useful stack information.

I agree and disagree. I'm one of the few people who still thinks that checked exceptions are a good idea for a language. I have my complaints about how they're implemented in Java, but I think the concept is still a good one and I honestly think that even the Java implementation of checked exceptions is mostly fine. The issue, IMO, is with training and explaining when to use checked vs. unchecked exceptions and how do design good error type hierarchies.

Go's idiomatic error handling is mostly stupid because Go doesn't have sum types. But, I'd argue that if you are wanting stack information, it means that you shouldn't be returning error values at all- you should be panicking. Error values are for expected failures, a.k.a. domain errors. You can and should attach domain-relevant information to error values when possible, but generally, there shouldn't be a need for call-stack information. A bug should be a panic.

Re: Jodd – The Unbearable Lightness of Java

#222
post #188

Earlier quoted context omitted.

Spring Data JDBC is, by default I believe, backed by a full on ORM, that being Hibernate. I'm open to different opinions on this, but I dislike Hibernate because of the complexity and the pains it causes when trying to do simple things. Hibernate, and Spring's use of it, is a leaky abstraction. When running into bugs, just trying to use a flow like, read sql row to POJO -> update POJO -> Save POJO to DB, using Spring…

This is not correct. You're thinking Spring Data JPA [1]. Spring Data JDBC [2] does not use any Hibernate nonsense. [1] https://docs.spring.io/spring-data/jpa/docs/current/referenc... [2] https://spring.io/projects/spring-data-jdbc

Ah, I see. Thank you!

Re: Jodd – The Unbearable Lightness of Java

#223
post #196

Earlier quoted context omitted.

> I disagree here. Having GC, VM, streams, big stdlib, makes is quite highlevel. I used to think that, too. I probably wont't convince you otherwise, and it really doesn't matter how you or I categorize the language, but I think a solid argument can be made that Java's abstraction power is almost zero, especially if you consider versions older than two or three years (before records, switch expressions, sealed classe…

Agreed with many points. So Java is then somewhat in the middle. OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly differe…

> OTOH lets consider Rust. It is in my book a low-level lang, close to the metal (hence Rust?). It has a muuuuuuch better feature set compared to Java (IMHO). But it is geared at low-level, so no VM and certainly no GC out-of-the-box... In your def Rust'd be a high level lang: which is cool. I like your def :) But I still def'd high level slightly different: more in terms of the ability program close to the machine, or more in abstractions.

I'm not a super clever person, but I once made a quip that I was pretty proud of, and I've repeated it online a few times:

"Rust is the highest level low-level language I've ever used. Java is the lowest level high-level language I've ever used."

Of course, the hardest part of all of these discussions is agreeing on what the words we're using actually mean. So what does "high level" and "low level" mean when it comes to programming languages? Are they mutually exclusive, or can a language be both? Is there such a thing as a "middle level"?

I don't have a great objective definition. Basically, I see "low level" approximately meaning "I have to think a lot about computery shit" and "high level" as "My code mostly looks like domain logic". There's a lot of wiggle room in there, for sure.

But I'm curious to challenge you more on what you mean by "close to the metal." Is being close to the metal somehow about abstraction-ability of the language, or is it a euphemism for some languages just being inefficient with computing resources? And, specifically, the things that make Rust closer to the metal than Java. I think the "obvious" answer is that Java runs in a virtual machine and has garbage collection, whereas Rust has neither of those. But I'm going to push back on those "obvious" high-level features.

First of all, Java-the-language has no idea that it's running in a virtual machine. I could, hypothetically, write a compiler for Rust that spits out JVM bytecode- would that make it a high-level language? Probably not.

As for garbage collection, I'd agree that compared to manually allocating and de-allocating memory space, garbage collection certainly allows us to think in a higher level of abstraction by letting us ignore details about how and when our data come to exist in our program. But (safe) Rust's approach to memory allocation is pretty far from manually allocating and de-allocating blocks of memory from the OS (which is basically a VM, itself, isn't it?). Rust largely allows me to ignore how much memory I might need for a String or a vector of data.

Now, it would be crazy for me to claim that Rust's memory model is as high-level as something with a garbage collector. After all, in Rust we have to think about borrows, Sized vs. unsized types, and sometimes have to actively think about lifetimes.

But, I will claim that Rust's memory management is still a big step up the ladder of abstraction from C. So, if being "close to the metal" is about needing to think less about nitty-gritty computer stuff, then Rust isn't as close to the metal as our gut instinct might say it is.

On the other hand, in Java, I still need to think about specific computery stuff when choosing between boxed and unboxed primitive types. I need to think about bits and bytes when choosing Short vs Int vs Long, rather than having a default Integer type that can be arbitrarily big or small. I need to think about mutexes and threads and thread-pools for concurrency/parallelization. I need to worry about stack overflows. That's all true of Rust, too, of course, but my point is that both of them require putting a lot of thought into non-domain concepts while programming.

Java did recently get records and sum types, so its abstraction ability has gone up substantially from where it was just a few years ago.

Rust has async/await for concurrency that can even be used in single-threaded contexts. Java doesn't even have that yet.

Rust has type classes. Java does not.

Rust has easy-to-implement newtypes. Java does not.

Rust has (im)mutability as a language concept. Java does not.

Rust has data copying as a language concept that actually works. Java has Clone.

Rust has hygienic macros that can be used to extend the language, create DSLs, and reduce boilerplate. Java has annotations that can be used to reduce boilerplate- mostly with a runtime cost and runtime errors.

So, which language is more capable of higher levels of abstraction? Honestly, it's probably Rust. Which language requires you to think more about memory stuff? Rust- but I think it's less of a lead over Java than most people would guess.

Which is closer to the metal? I don't know. Rust runs faster.

> Traditionally not often found in OO, but otherwise verrry much compatible with OO. > I think this is more about tradition than "trueness". [snip] I'd say OO is compatible with sum types.

We'll probably have to agree to disagree. Of course sum types can exist in a language that touts itself as OO, but using them extensively is just not OO. It's literally inside-out from OO. If you look at a language like SmallTalk, even True and False are objects, and there are no if-statements. Rather, True and False are both sub-types of Boolean, and Boolean requires the methods ifTrue and ifFalse. True implements ifTrue to perform any action that was sent as a parameter, and implements ifFalse as a no-op. You can imagine False's implementations. So, some object sends you a Boolean and you call the ifTrue method of the Boolean with an action to be performed if the Boolean feels like it (it feels like it when it's a True :p). In true/extreme/hardcore/pure OOP, you wouldn't even have or use if-statements when implementing logic- it's polymorphism all the way down.

Is that useful or practical? I don't think so. But that's why I claim that sum types are not OO. I also claim that if-statements aren't really OO. And boolean is a sum-type.

> I cannot return an Either from Java. That sucks. Many have used Exceptions to fix it, but that suck even more.

Yes you can. Java now has sum-types anyway, but you could always implement a generic class that could be in either one of two states with whatever methods you need to check its state and extract the data. It's awkward and janky, but this is Java- what isn't awkward and janky?

> I think OO and FP bite eachother. You cannot have both. See Scala. It becomes way too big as a language, and lack idiomatic ways of doing things. But one can have a lot of FP in an otherwise OO lang (see Kotlin for instance).

I agree with the first premise, but I disagree that Kotlin has successfully added FP stuff to an OO language. I think that Scala has done a much better job of being FP and OO, actually.

Re: Jodd – The Unbearable Lightness of Java

#224

Earlier quoted context omitted.

Yes, absolutely. Java has had a competent implementation of generics since 2004 (Java 5) and really embraced functional programming in 2014 (Java 8). Any application of significance will require more LoC in Go than Java, hands down. Just compare Java streams with Go container classes. Go's aren't typesafe (though that will hopefully change when generics are officially released) and almost every operation requires imp…

Fair points. I haven't worked with Go in a few years, and I remember hating it when I did, but I feel like I remember hating Java more. It's possible that part of the Java hate is not from the language itself, but from the ecosystem. Can you elaborate on Java streams vs Go's containers? I assume you mean things like List and Heap in Go? I'm not sure why you'd compare those to Java's stream API rather than Java's coll…

Here's a Java example that sums the populations of a list of Countries:

    int population = countries.stream().mapToInt(Country::getPopulation).sum();
The Go implementation:

    var population = 0
    for _, country := range countries {
        population += country.Population
    }
It gets more perverse if you need to flatMap, or transmute components of map types, etc. If you want even more power, take a look at https://github.com/amaembo/streamex. This sort of container manipulation is bread and butter for business processing. I use it every day, sometimes with a dozen operations. This (with liberal use of `final` values) makes for some pretty functional-looking code.

I'll grant you the Kotlin or Scala version is slightly more compact. But not fundamentally different, like the Go version.

I (and the pretty much every language designer in the post-Java era) disagree with you about checked exceptions, but that's a whole different thread...

Re: Jodd – The Unbearable Lightness of Java

#225
post #203

Earlier quoted context omitted.

> I don’t think that it is any easier in other languages either — object serialization, conversion between language’s object/json/xml/etc, and database access with object relational mapping is just complex. You can make the trivial way trivial, but you have to expose the hard ways as well and that will not be pretty either way. I agree that these things are just complex. But, what's interesting to me is that I fully…

Thanks for the non-flame-baity answer! Hopefully I wasn’t too emotional in my previous reply, because it unfortunately does happen from time to time. Regarding Jackson and JPA the only thing I can tell about these is that their age shows, and they come from a domain and age where the (in my opinion, bad) POJO and Java Beans conventions originate. So I fully agree that things could be much better, and hopefully there…

> Thanks for the non-flame-baity answer! Hopefully I wasn’t too emotional in my previous reply, because it unfortunately does happen from time to time.

I didn't pick up any high emotions, but I get it. For some reason, I get fiery about this stuff, too. I don't know if it's that I get equally worked up no matter what I'm arguing about, or if it's worse because I'm passionate about computers and programming.

> Regarding ORMs, have you by chance tried JOOQ? You may prefer it over JPA.

I haven't used it, but I've read their docs and API. It looks great, albeit very large. It also preserves some of the... conventions... from JDBC and JPA that I find egregious, like converting null to actual values when mapping query results (https://www.jooq.org/doc/3.15/manual/sql-execution/fetching/...). At this point I have to assume that Java devs actually prefer this behavior, but I think it's crazy- if I expected a non-null int and I read out a null, I want to crash- not pretend like I got a valid int...

> Regarding Jackson and JPA the only thing I can tell about these is that their age shows, and they come from a domain and age where the (in my opinion, bad) POJO and Java Beans conventions originate. So I fully agree that things could be much better, and hopefully there will come a renaissance replacing these tools with modern java equivalents, that don’t rely on runtime magic as much, and will use the modern datetime APIs by default, etc. Serialization is especially in need of a huge revamp, hopefully records will make it much better.

> Also, just a small note on Rust - I find it to be an excellent language, but I really don’t think it fights in the same domain as Java. Systems programming is fundamentally different. So writing a huge business application in Rust (or in C++, equivalently) is a suicide mission in my opinion — initial write time may indeed be low for an experienced Rust dev, but with the often changing client requirements that mandate quick changes touching everything, the low level details that leak into the high level view of the app will slow one down (now you also have to change the memory model because this lifetime has to be extended, etc). But I only mention that as an explanation for why Rust is not a replacement for the huge, ever-living business app domain (at least for me)

At the risk of coming off as a combative asshole, I'm going to pick on you for second because it's relevant to the part about Rust.

In your previous comment, you asserted that serialization and ORM/data-access is just as difficult in every other language as it is in Java. You also asserted that Java has a "really high quality ecosystem for all these things".

But, here you're acknowledging that serialization and data mapping are "showing their age", follow "bad" conventions, "could be much better", shouldn't rely on runtime magic, and are in need of a "renaissance".

Even though I wasn't advocating for Rust as a great fit for enterprise web apps, I will say this: I think you're rationalizing. You've already made up your mind that Java is good for application development and has a good ecosystem. But I think I can make a solid case that Java is a primitive, unexpressive, bug-prone, language with a large-but-mediocre ecosystem (where the most widely used parts need a "renaissance").

Even after I argued (convincingly, I assume, since you changed your expressed opinion about serialization and data-mapping in Java) that Rust is better at both serialization and data-mapping, you're asserting that Rust would be a suicide mission for a large scale business app. Well, it's apparently better at two of the most fundamental parts of any web app, so I think it's looking pretty good as far as suicide missions go.

Hell- Java doesn't even have single-thread concurrency!

Rust's type system allows us to express more elaborate abstractions with less code than Java (enums vs sealed classes, type classes vs adapters and decorators)

Rust makes concurrent code safe. No need to remember to use mutexes or to make inefficient copies/clones of "immutable" classes- if you write code that would cause a data race, it just won't compile. In Java, you'll just get bugs and corrupt data.

In Rust we'll never get NPEs.

So, I don't agree with your assessment at all. Writing a large enterprisey business app in Rust will likely run faster, have fewer bugs, use less memory, and even scale out better. If you're hosting your app on a cloud provider, it'll cost you less money to operate as well.

I think that you just want to believe that Rust would be worse than Java, and I think the cargo cult agrees with you. But, having done significant work in both languages, I think that's incorrect. My Rust code is generally an order of magnitude less maintenance than my Java and Kotlin code have been. Furthermore, it took me LESS time to become truly proficient at Rust than it has to become proficient (as in writing relatively few bugs the first time) in Java and Kotlin.

Some day, Valhalla will land and Java will get some things that other languages have had for a while. And eventually, Java may even become a solid language for its major use-case. But today is not that day. And today we have languages that are already better. Literally everything you listed (sum types, records, pattern matching, and non-blocking) already exists in Rust and Swift (and Scala, and Kotlin).

Re: Jodd – The Unbearable Lightness of Java

#226

Earlier quoted context omitted.

Fair points. I haven't worked with Go in a few years, and I remember hating it when I did, but I feel like I remember hating Java more. It's possible that part of the Java hate is not from the language itself, but from the ecosystem. Can you elaborate on Java streams vs Go's containers? I assume you mean things like List and Heap in Go? I'm not sure why you'd compare those to Java's stream API rather than Java's coll…

Here's a Java example that sums the populations of a list of Countries: int population = countries.stream().mapToInt(Country::getPopulation).sum(); The Go implementation: var population = 0 for _, country := range countries { population += country.Population } It gets more perverse if you need to flatMap, or transmute components of map types, etc. If you want even more power, take a look at https://github.com/amaembo…

The go version looks perfectly fine to me (saying this as someone who uses clojure every day) ;)

Something else to consider is performance, in most implementations the for loop is going to be more efficient.

Re: Jodd – The Unbearable Lightness of Java

#227
post #203

Earlier quoted context omitted.

Thanks for the non-flame-baity answer! Hopefully I wasn’t too emotional in my previous reply, because it unfortunately does happen from time to time. Regarding Jackson and JPA the only thing I can tell about these is that their age shows, and they come from a domain and age where the (in my opinion, bad) POJO and Java Beans conventions originate. So I fully agree that things could be much better, and hopefully there…

> Thanks for the non-flame-baity answer! Hopefully I wasn’t too emotional in my previous reply, because it unfortunately does happen from time to time. I didn't pick up any high emotions, but I get it. For some reason, I get fiery about this stuff, too. I don't know if it's that I get equally worked up no matter what I'm arguing about, or if it's worse because I'm passionate about computers and programming. > Regardi…

So, I don't agree with your assessment at all. Writing a large enterprisey business app in Rust will likely run faster, have fewer bugs, use less memory, and even scale out better.

That's true but which is more flexible for the "ever changing living business app domain" the GP is alluding to? You seem to keep ignoring this part, flexibility matters. In rust is easy to code yourself into a corner and spent lots of time rewriting stuff over and over.

Re: Jodd – The Unbearable Lightness of Java

#228

Earlier quoted context omitted.

Fair points. I haven't worked with Go in a few years, and I remember hating it when I did, but I feel like I remember hating Java more. It's possible that part of the Java hate is not from the language itself, but from the ecosystem. Can you elaborate on Java streams vs Go's containers? I assume you mean things like List and Heap in Go? I'm not sure why you'd compare those to Java's stream API rather than Java's coll…

Here's a Java example that sums the populations of a list of Countries: int population = countries.stream().mapToInt(Country::getPopulation).sum(); The Go implementation: var population = 0 for _, country := range countries { population += country.Population } It gets more perverse if you need to flatMap, or transmute components of map types, etc. If you want even more power, take a look at https://github.com/amaembo…

Ah. You know what? I forgot that the Java implementation of these concepts isn't stupid like it is in some other languages (except what the heck is mapToInt? Some optimized version that makes a primitive array, I guess? Yucky- I wish the compiler could just figure that out).

So, I concede that Java's addition of the stream API is a legitimately good example of adding an aspect of functional programming to an otherwise very non-FP language.

But, let me go off on my tangent, anyway. ;)

It's not that you need to convince me that functional programming is great. It's just that I find that consistent and coherent designs tend to work well and that kitchen-sink or be-everything-to-everybody approaches tend to be good at nothing and mediocre-to-bad at everything.

MOST languages that have tacked on the low-hanging fruit of FP (map, filter, etc combinators on collections) have done it in a really sub-optimal way.

JavaScript, for example. JavaScript has eager, mutable, non-persistent, arrays as the default collection data structure. When they added map, reduce, filter, etc to Array, they added them in the most naive possible way, which means that doing something like your example above (map-then-sum), would create an entire extra array with the same number of elements as the original, and would end up looping both arrays once. So we have ~2N memory usage and 2N iterations where we really should just have an extra 8 bytes to hold the sum and iterate over the array once (N iterations).

Same thing with other languages like Swift and Kotlin.

Kotlin maybe should have an asterisk because it has Sequence, which will mostly work like Java's streams. However, there are two issues: it still offers them on eager iterables, instead of forcing us to use a sequence/stream to access them, and with suspend functions you have to be careful with Sequences. In you Java example, we're theoretically allocating a new Stream object with every combinator call, BUT we "know" that the compiler is smart enough to avoid those allocations and the result code will be about as fast as writing a for-loop. With Kotlin's suspend functions, we can very easily thwart the compiler's ability to do that. If you use a Sequence chain inside a suspend function and call another suspend function as part of that chain, then that's a yield point and the compiler can no longer optimize away the allocation of the intermediate Sequence object(s).

So, my point is that designing a language with some initial philosophy and then trying to borrow from, frankly, incompatible other philosophies usually leads to sub-optimal implementations and/or APIs. Again, though, Java's streams are a good counter example to my claim.

> I (and the pretty much every language designer in the post-Java era) disagree with you about checked exceptions, but that's a whole different thread...

Indeed it is! :) I'm willing to be the black sheep, and die on that hill, though (too many metaphors?). And, honestly, I don't think it's as unanimous as some people claim. I see returning monadic error values as isomorphic to checked exceptions, and several languages have gone that route since Java: Scala, Swift, and Rust, to name a few. Kotlin's lead dude, Roman, simultaneously claims that checked exceptions were a terrible mistake, but then also advocates for using sealed classes for return values when failure is expected or in the domain, which sounds a lot like what checked exceptions are supposed to be used for. TypeScript can't have monadic error handling because of its design philosophy of being a thin layer over JavaScript, but many in that community have embraced using union types for return values instead of throwing Errors.

Cheers!

Re: Jodd – The Unbearable Lightness of Java

#229
post #226

Earlier quoted context omitted.

Here's a Java example that sums the populations of a list of Countries: int population = countries.stream().mapToInt(Country::getPopulation).sum(); The Go implementation: var population = 0 for _, country := range countries { population += country.Population } It gets more perverse if you need to flatMap, or transmute components of map types, etc. If you want even more power, take a look at https://github.com/amaembo…

The go version looks perfectly fine to me (saying this as someone who uses clojure every day) ;) Something else to consider is performance, in most implementations the for loop is going to be more efficient.

That's exactly my complaint- most languages have eager, mutable, non-persistent, collections because they were not designed with functional programming in mind.

Then FP became the hot new shit, so they all added some of the lowest hanging fruit so that people can say absolutely weird things like "I do FP in C#". The problem is that the majority of these implementations just eagerly iterate the collection and make full copies every time. So, you're much better off with a for-loop.

To be fair to GP, though, Java has legit engineering behind it, and the way they did it was to introduce the Stream API, which is lazy sequences, and they made the compiler smart enough to avoid actually allocating a new Stream object per method call (which is what the code nominally does, IIRC- each method wraps the original Stream in a new Stream object that holds on to the closure argument and applies on each iteration).

Re: Jodd – The Unbearable Lightness of Java

#230
post #227

Earlier quoted context omitted.

> Thanks for the non-flame-baity answer! Hopefully I wasn’t too emotional in my previous reply, because it unfortunately does happen from time to time. I didn't pick up any high emotions, but I get it. For some reason, I get fiery about this stuff, too. I don't know if it's that I get equally worked up no matter what I'm arguing about, or if it's worse because I'm passionate about computers and programming. > Regardi…

So, I don't agree with your assessment at all. Writing a large enterprisey business app in Rust will likely run faster, have fewer bugs, use less memory, and even scale out better. That's true but which is more flexible for the "ever changing living business app domain" the GP is alluding to? You seem to keep ignoring this part, flexibility matters. In rust is easy to code yourself into a corner and spent lots of tim…

Fair. You're right that I didn't address that concern.

I guess the problem is that I don't know what we mean by "flexible". The GP did mention lifetimes around the same part of their comment, so I assume that there's some concern about business requirements changing in some way, and that Rust's lifetimes would get in the way of adapting to code to meet the new requirement.

Is this also what you mean by "code yourself into a corner"? Or are you thinking of a superset of that?

When we say "flexible" are we talking about the language being opinionated about the style of code we write or are we talking about the language making it harder to be agile in the face of requirement changes? It sounds like we're talking about the latter.

First, let me repeat myself that I don't believe Rust is the ideal enterprisey web app language. There's almost no reason that a web-app benefits from a language not having garbage collection or automatic ref-counting (like Swift).

But, I'm not backing down from my assertion that Rust is still probably a better web app language than Java, if we're willing to ignore Java's ecosystem's 30 year and billions of dollar head-start for niche, vendor-specific, libraries. Or, phrased another way, just because FooCorp gave you a jar file to connect to their smart sex swing, that doesn't make Java a better language in a fundamental sense, even if it does force your hand from a business and engineering perspective.

So, since I don't actually know what we're talking about with "flexibility", I'll just ramble about a few things.

First, lifetimes. Lifetimes are scary. But, I honestly don't see how or why lifetimes should be an issue in a high-level application, like a web app. If you have any specific scenarios, examples, or lived experiences, please share. Let me explain some of my experience with writing a couple of web services in Rust, with respect to lifetimes.

I've been using an http server called Actix-Web when I do Rust web stuff. It uses the same architecture style as Vert.x: it spins up N reactors (where N = number of CPUs by default) and each reactor is single-threaded and concurrent, which means that once a Request is routed to an available reactor, it never leaves that thread. This means that there are no complex lifetime issues with handling a Request- all of your logic can be single-threaded and treats the Request as having "static" lifetime (the Request outlives your handler function, so as far as your function knows, the Request lives forever. The caveat is that you borrow its content such as headers, uri, etc and would need to take copies if you wanted to send them elsewhere). I've never had non-trivial issues with lifetimes when it comes to the basics of request processing.

The SQL query builder I referred to in a previous comment gives us a transaction object with a legit lifetime, because it uses RAII to close the transactions and to return connections to the connection pool. The only time this has caused me grief was when I was trying to be clever by implementing a type class around transactions for some reason that I don't even remember now. I don't see how or why a changed business requirement would require us to extend a transaction's lifetime explicitly.

A further point: it's fairly easy to leak resources in Java because you can't do RAII except with the try-with-resources stuff. But, you can easily forget to try-with-resources and leak. Or, on the opposite side, since an object can still be referenced after you close it, you could pass an already-closed connection around and cause an error far from where the connection was first obtained and/or closed. In Rust, such mistakes would never compile.

Really, in an idiomatic Rust app, I would expect that the only place where you'd see explicit lifetimes is from RAII. Everything else is either going to be plain-old-data or some kind of ever-living actor/service. I'd be surprised to know that a high-level app is actively managing lifetimes of pretty much anything.

I'm not saying that it's impossible to end up with some ugly function signatures because of lifetimes. I can imagine writing a function that takes two parameters with independent lifetimes. But, I don't know why it would limit your agility.

Moving away from lifetimes.

Rust does preclude certain designs and architectures. You can't really do self-referencing structs (easily/simply/whatever), so you're not going to see a complex web of sibling objects referencing grand-parent objects, referencing the town they live in, referencing the grand-child objects. In this sense, yes, Rust is less flexible, and if you try to write Java style code in Rust, it's going to be painful. But does this make a Rust app less agile? In my opinion, no. Sure, you need to write your code in a different style than you would with a different language. And, sure, there's a learning curve to writing "good" Rust code, but are you willing to tell me that there isn't a learning curve to writing enterprise Java app style? The millions of pages printed and watts burned by people teaching and learning Gang Of Four design patterns, and Domain Driven Design, and Clean Architecture would suggest otherwise. Then the millions of watts burned on StackOverflow posts about == vs .equals(), and how static methods work with inheritance, and how to implement a generic interface for multiple types (you don't), and what the difference is between DAOs and Repositories and Services, etc, would also suggest otherwise.

In fact, here are some things that have made my Rust code MORE agile:

* You know how people praise static typing as allowing more confidence in refactoring? The idea of doing a big refactor of a Python or JavaScript code base makes me break into a cold sweat. Rust's type system is way stricter than Java's and I'm much more confident that when I refactor Rust code, I won't accidentally introduce a race condition or resource leak.

* If I want to extend a type with new functionality, I don't even have to own that type. Or, if I do, I don't even have to change the original file. I can define a new trait *and* write the implementation of that trait near the code that uses it. How do you do it in Java? You write your new interface and then write a wrapper class that delegates to the original class. Except now, you can't use that wrapper class in place of the original- you have to convert back and forth. Not so in Rust. Much more "flexible", IMO.

* modules > packages for namespacing and visibility.

* traits allow me to define/require "static" methods on implementing types.

* If you have two interfaces, Foo and Bar, in Java, and you want to write some code that does something special for a type that is both Foo and Bar, what do you do? It's been a while, but if I remember correctly, you have to define a new interface called FooBar that extends Foo and Bar and you have to go find every class that implements both Foo and Bar, and change them to implement FooBar, instead. In Rust, I can just write a function: `fn do_stuff(o: T)`. Done. Didn't have to define a new type, didn't have to touch old stable code, etc.

* I can implement a generic trait for multiple type parameters (eat that, Comparable!).

All of the above have allowed me to add or change functionality with minimal added code and minimal regressions.

Java being flexible is a truism, IMO. It's not flexible. We've just mastered it to the point that we don't even try to do things that we know are impossible, but are totally reasonable to want to do. We've gotten so used to its restrictions and limitations that we don't even see them anymore, or we just pretend like it's actually better this way.

Post reply on HN