Hacker News new | past | comments | ask | show | jobs | submit login

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.




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 <T: Foo + Bar> 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<T>!).

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.


Rust is great for reliable production systems, for sure, but for 'let's quickly prototype this new feature', it’s too strict. Imagine figuring out a perfect algorithm and spending a few hours implementing it just to be told by the borrow checker it won’t let it pass.

When this new features start to queue up I’m happy to have leaks as long as I get to try out ideas quickly (later you hardened them). And it’s hard to convince Rust to let it go.

Maybe it's my inexperience with Rust, definitely need to give it a second try for more than three weeks but haven't had a good reason to do so.

I don't like Java at all and prefer Clojure when on the JVM, but as you said, the Java ecosystem(libraries get the job done) and the GC are definitely good reasons to pick it up for a webapps.


> Rust is great for reliable production systems, for sure, but for 'let's quickly prototype this new feature', it’s too strict. Imagine figuring out a perfect algorithm and spending a few hours implementing it just to be told by the borrow checker it won’t let it pass. > When this new features start to queue up I’m happy to have leaks as long as I get to try out ideas quickly (later you hardened them). And it’s hard to convince Rust to let it go.

I don't know. This is starting to feel like moving the goalposts.

The first person I replied to claimed that Java's ecosystem is high quality and that serialization and data-mapping is not only good in Java, but that it's not better in any other language.

I showed that both of those claims are false.

Then they claimed that Rust, being a systems language, was not suited for enterprise apps with evolving requirements. And you kept me honest about addressing the evolving requirements part.

I explained how Rust apps will run better, scale better, be more robust and bug-free, AND allow us to better adapt to changing requirements than Java.

And now, of course, it's some other reason that Rust can never work.

I'm sorry for the snark, but it always seems to be the EXACT same common talking points over and over again when it comes to Rust nay-saying, and after having worked with Rust on-and-off over the last 4-5 years it just gets exhausting hearing about all of these hypothetical things that don't happen in real life, from people who haven't actually used Rust in a real project, but somehow seem to know what it's good and bad at (and what a coincidence! It's bad at the exact thing they're working on, and the language they chose for the task was definitely the best choice! I'm happy for them, but it's discouraging to know that I'm the only person who ever makes the wrong choice sometimes.).

I mean, it's literally always even the same words. It's always "prototype" and "quick and dirty" and "that darn borrow checker!". You'd think that everyone on HN and Reddit were Thomas Edison with all of the "prototypes" they're writing.

I mean, what exactly do you think is going to happen in your quick prototype Rust code? A reference to a piece of data is going to come out of nowhere and sink your experiment? Hell no. At worst, you're going to type `.clone()` and `.unwrap()` a bunch of times to take copies instead of references and crash on any errors, and call it a day. If this is experimental-prototype-whatever, then what the heck do you think you're doing writing a bunch of fancy lifetimes and cross-thread mutable data sharing?

This shit doesn't happen.

You know what does happen when I try to "prototype" in a "good" prototyping language like Python or JavaScript? I spend a bunch of time re-running the same code over and over and over until I stop forgetting or misspelling what keys are on the dictionary I passed to the function, or accidentally passing arguments in the wrong order.

When I "prototype" in Java, I can't figure out if my algorithm sucks or not because I accidentally wrote `o1 == o2` instead of `o1.equals(o2)` or I forgot that getting a `null` from a Map could mean that the key has no entry in the Map *or* that an actual `null` value was inserted into the map for that key. Or, I get an NPE because I accidentally mixed up `int` and `Integer` somewhere.

I've already spent too much time on this. If you decide to give Rust another shot some day, that's great. If not, that's fine, too. There are probably other languages that will give better bang for your buck, too, like OCaml or Scala (3). I like Clojure as well, even though I prefer my static types. Clojure is at least a well-designed and consistent language. Java is a hot-mess.

EDIT: Also, if the borrow checker won't let your algorithm pass, it almost definitely means your algorithm has a race condition that you might not have realized. "You're welcome for not letting that crap eventually end up in prod" - Borrow Checker


Fair, I'll shut up until I have more real world experience with Rust. One thing that I remember well that really annoy me were the extremely slow compile times (these was two years ago, maybe it's much better now) since I value fast feedback. I understand for some that compile time cost is worth it and doesn't bother them.


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 <T: Foo + Bar> do_stuff(o: T)`. Done. Didn't have to define a new type, didn't have to touch old stable code, etc.

Since Java 8 there is static and default methods in interfaces.

Curious, what rust sql query builder are you refer to?


> Since Java 8 there is static and default methods in interfaces.

That's not relevant to the part of my post that you quoted...

I was describing a hypothetical situation where you already have two interfaces, but would like to have functionality that only makes sense for an object that implements BOTH of those interfaces. The only way to do that in Java is to write a THIRD interface that combines the two and then go through and change your implementations to implement that new interface instead of the two separate ones.

However, in the bullet point above, I mentioned static methods on traits in Rust, which is different than what static methods on interface in Java are. In Java, a static method on an interface is just a function on the interface, itself. In Rust a trait can declare that an implementing type must have a static method matching the signature. This is because Rust traits are type classes, while Java interfaces are just object interfaces and cannot constrain the implementing type, itself.

> Curious, what rust sql query builder are you refer to?

I have been mostly using mysql_async (https://docs.rs/mysql_async/latest/mysql_async/), but recently started playing with sqlx (https://github.com/launchbadge/sqlx). I guess "query builder" isn't the right way to describe them, but I'm not sure what else to call them...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: