It's a shame imo that it's not seen as a "cool" option for startups, because at this point, the productivity gap compared to other languages is small, if nonexistent.
It's a shame imo that it's not seen as a "cool" option for startups, because at this point, the productivity gap compared to other languages is small, if nonexistent.
Rust feels like walking on a minefield, praying to never meet any lifetime problem that's going to ruin your afternoon productivity ( recently lost an afternoon on something that could very well be a known compiler bug, but on a method with such a horrible signature that i never can be sure. in the end i recoded the thing with macros instead).
The feeling of typesafety is satisfying , i agree. But calling the overall experience a "joy" ?
Of all the languages I've had to work with trying to get to know unfamiliar code-bases, it's the Go codebases I've been quickest to grok, and yielded the fewest surprises since as the code I'm looking for is almost always where I expect it to be.
> recently lost an afternoon on something that could very well be a known compiler bug
With respect, at two months, you're still in the throes of the learning curve, and it seems highly unlikely you've found a compiler bug. Most folks (myself included) struggled for a few months before we hit the 'joyful' part of Rust.
Also, as open-source folks say, "rewrite is always better". It also serves as a good security review. But companies typically don't have resources to do complete rewrites every so often, I saw it only in Google.
Rust has a horrid learning curve
I've programmed for decades in many languages, and I felt the same as you
Persevere.
Surrender! to compile
Weather the ferocious storm
You will find, true bliss
But nobody seems to talk about or care about C# except for Unity. Microsoft really missed the boat on getting mindshare for it back in the day.
Java kept growing and wound up everywhere. It played nice with Linux. Enterprise Mac developers didn't have trouble writing it with IntelliJ. It spread faster because it was open.
Satya Nadella fixed a lot of Microsoft's ills, but it was too late for C# to rise to prominence. It's now the Github / TypeScript / AI era, and Satya is killing it.
The one good thing to say about Ballmer is that he kicked off Azure. Microsoft grew from strength to strength after that.
Simply using axum with code using multiple layers of async was enough.
But then again, it looked like this bug (the error message is the same), however at this point i'm really unsure if it's exactly the same. The error message and the method signature was so atrocious that i just gave up and found a simpler design using macros that dodged the bullet.
Simple example, JAX-RS running on top of Java SE. I agree, JAX-RS is not what one might call "simple". It IS complex, or I should say, it CAN be complex. But Happy Path, staying in the middle of the road, it's pretty sweet for knocking out HTTP backed services. The Jersey reference implementation will do most anything you need (including stuff not "included" in raw JAX-RS). No need for a container, no need for a lot that stuff and all that hanger-on. The core runtime is pretty broad and powerful.
Consider my current project, it uses the built in Java HTTP server. Which works! It's fast, it's functional, it's free. (Yes, it's in a com.sun.net... package, but it's not going anywhere.) It's awkward to use. It's aggravatingly raw. It follows the tenet "why be difficult, when, with just a little effort, you can be impossible."
So, I wrote a simple "servlet-esque-ish" inspired layer for response and request handling, a better path based regex-y router, and a nicer query parser for queries and forms. 500 lines. Add on a JSON library and I can process JSON-y web request/response, easily. (I also wrote my own Multipart processor -- that was another 500 lines, boy that was fun, but most folks don't need that.)
A little bit of code and the built in server is MUCH easier to use. No tomcat, no deploys, zip. ...and no dependencies (save the JSON library).
Something all of these cool frameworks and such have shown me is what's really nice to have, but at the same time, just what isn't really necessary to get work done. I mean, CDI is really neat. Very cool. But, whoo boy. So I have a single singleton to handle application life cycle and global services. It works great with tests. I have a 50 line Event Bus. I have a 100 line "Workflow Engine". 150 line java.util.Logger wrapper (which is mostly, you know, wrapper). I wrote that way back whenever they introduced varargs to java (Java 5? 6?). The modern Java logging landscape is just...oh boy. I'm content with JUL -- I can make it work.
My current project is "magic free". I think @Overide in the single annotation anywhere in it. But it's comfortable to use, the cognitive load is quite load (outside of the actual application itself, which is NOT low -- sheesh). No swearing at frameworks. It's all my fault :).
Anyway, the point is that "simple Java" lurks in there. It needs a bit of uplifting, but not a lot.
However, at the moment i still feel i'm using a huge amount of layers upon layer of complex type definitions in order to get anything done. Just using an object's reference across async calls in a safe manner leads to insane types and type constraints, which read like ancient egyptian scripture. And at every layer, i feel like changing anything could blow everything up with lifetimes.
The language has this very special feel of something both advanced and extremely raw and low-level at the same time. It's very unique.
The main area they get excessively lengthy is in certain frameworks and testing tools that can add like 100 lines to the trace.
Also, it’s worth saying, you probably don’t need async.
However, at some point you have to ask yourself why you're accepting to face all those challenges. Is it worth it ? When was the last time i faced a race condition when developping a backend ?
The reason i started with rust was for a very specific need on building a cross-platform library (including wasm), and that was a good justification, and i'm happy that i did. However now that i'm using it for the server as well and face the same kind of challenges, i seriously question whether this is a wise choice.
Then you want to declare an async function that takes an async closure over that dependency. And you end up with a total garbage of a method signature.
As for async, the ecosystem for server-side is totally filled with async everywhere now. I don't think it's realistic to hope escaping those issues anyway in any real-world project. i thought i might as well learn to get comfortable with async.
Go felt the same way (but with a much lower order of magnitude) : you feel like bumping into language limitations, but once you learn to do it "simply" in go, your style will have changed into something much more elegant.
As for the bug in question, it has been quite "popular" for about 5 years now, and is actively tracked : https://github.com/rust-lang/rust/issues/110338. Nothing really weird. Just async hitting the limits of the current rust design.
Nevertheless, as a platform, the JVM and JDK were fantastic and miles ahead most alternatives during the late 1990s and 2000s. The only platform for large development that offered some compelling advantages was Erlang, with BEAM and OTP.
See https://mckoder.medium.com/the-achilles-heel-of-c-why-its-ex...
Aside from early versions being rushed, I feel that Java's success and adoption were the bigger issue. While Microsoft could iterate quickly and break backwards compatibility with major versions of C# and the .NET runtime, Java was deliberately moving at a much slower pace.
https://www.artima.com/articles/the-trouble-with-checked-exc...
There was this guy Miguel de Icaza. From when I followed the open source ecosystem at the time, it seemed to be his personal mission to promote independent clones of a bunch of Microsoft technologies like C# on his own time even though they didn't ask him to do it.
I don't think I ever understood why someone would do this. It's like in the 2000s where people seemed to think you could solve all technical problems by inventing new kinds of XML.
Their belief was wrong. Microsoft now recommends against catching Exception.
The article you linked to is addressed at the bottom of this article: https://mckoder.medium.com/the-achilles-heel-of-c-why-its-ex...
That's so ignorant. Read the article please.
They also tend to be of higher quality and provide better performance.
For example, Go does not understand cgroups limits and needs an external package to solve this. .NET can read and accommodate those natively. It also ships with excellent epoll-based socket engine implementation. It's on par with Go (although I'm not sure which one is better, but .NET performs really well on high-throughput workloads).
Moreover, a lot of these libraries are well-supported to this day. For example, Hibernate (the best ORM in business) is 28 years old, and has just released a new version. I recently consulted my former client (from 15 years ago), and I still recognized most parts of the stack that I set up way back then.
- https://news.ycombinator.com/item?id=43226624
- https://news.ycombinator.com/item?id=43584056
- https://news.ycombinator.com/item?id=36736326
And more. I'm not sure what you found in (checked) exceptions. If you'd like explicit error handling, we have holy grail in the form of Rust which beautifully solves it with implicit returns, error type conversions and disambiguation between error and panic model. I'd prefer to use that one as it actually reduces boilerplate and improves correctness, the opposite to the outcome of using checked exceptions.
I could copy/paste the entire article here... but it would be easier if you could take a gander: https://mckoder.medium.com/the-achilles-heel-of-c-why-its-ex...
Summary:
Crashy code: You have no compiler-enforced way to know what exceptions might be thrown from a method or library.
More crashy code: If a method starts throwing a new exception, you might not realize you need to update your error handling.
Dead code: If a method stops throwing an exception, old catch blocks may linger, becoming dead code.
Your linked blog is pretty wild. Only throw RuntimeExceptions to crash? Why not just Exit if that's the proper thing to do?
If you treat all C# exceptions as RuntimeExceptions, then it satisfies the blog anyhow.
Because you won't get a stack trace.
It's an uphill battle to convince my co-workers to do things my way.
While operator overloading and infix functions aren't a Java anti-pattern, I also think the language would be improved by their removal.
which ones?
Reducing boilerplate is not a valuable goal in and of itself. The question is, does the boilerplate buy you something? I think that with checked exceptions it does. Having an explicit type signature for what errors a function can raise improves correctness a great deal because the compiler can enforce the contracts of those functions.
I agree that the Rust approach is good too, though I don't agree it has any strong advantages over the way Java does things. Both approaches are equally respectable in my view.
And macros are a part of that!
I found it hard taking over an existing Rails project - it felt frail to me, that any small change might have unexpected consequences.
Whereas when I've taken over Java projects - or come in late to an existing team - I felt quite confident getting started, even if it is a bit of a mess.
I'm still unsure if it's a good thing in general, because as a general rule meta programming is always harder to debug. But for simple macros it seems like a nice trick.
Using "fmt.Errorf" is lean and painless compared to defining custom errors.
In practice you have to use a combination of error wrapping and custom stack trace errors for your production logs to be useful on failure. The stdlib errors really should have stack traces.
https://www.theregister.com/2025/05/09/users_advised_to_revi...
You can easily just not use the Oracle JDK, though, unless you're running commercial software which requires running on the Oracle runtime to get technical support.
As others have said, the problem is not the runtime, but libraries: many major .NET libraries have been going fully commercial, you can't really trust the ecosystem anymore.
This is the whole story of Go, they pick something established and reimplement a heavily cut down version of it for "reasons", then slowly catch up to competition over the next decade or so.
Determinism.
With Rust lifetimes, you can statically prove when resources will be released. Garbage collectors provide no such guarantees. It has been shown that garbage-collected languages have a space-performance tradeoff: you need five times as much RAM to achieve the same performance, even with a "good" GC, as the same program with explicit memory management:
https://web.archive.org/web/20000815075927/http://www.helixc...
Miguel de Icaza has been stanning for Microsoft technologies, literally since the nineties.
Maybe you don't think of it like that, but please remember that, especially with some decisions made in the past months, more and more people are turning away from everything Microsoft.
Some Java developers may have, due mostly to misinformation from the .NET camp.
https://jessewarden.com/2021/07/why-functional-programmers-a...
While composing methods in stream style is convenient, methods that can throw exceptions warrant more careful coding, so convenience should not always be the priority.
The other one is thread safety, due to the compiler-enforced ownership semantics that prevent threads from accessing shared data unless they do so in a well-defined way.
Other languages use constructs like context managers or try-with-resources to capture this, but these constructs are very limited and make it very hard or impossible for these resource types to be put into a container and passed between threads. In Rust this is trivial and actually just works.
Garbage collectors usually give much weaker guarantees about when objects are freed, so destructors (which are sometimes not even available, like in JS) might only be called much later. You can't rely on a GC to unlock a muted for you. But in Rust it happens when the guard is dropped, always, immediately after it's last needed.
VSCode itself, for all the promotional materials about how it's open source, is officially "a distribution of the Code - OSS repository with Microsoft-specific customizations released under a traditional Microsoft product license".
But wait, you might say, it's just like Chrome vs Chromium - so long as we have the OSS edition, it's all good! But, unless you're writing JS or TS, you need extensions to do anything useful. Python is an extension. So is C#, and C++. And all of these are partially closed source - e.g. code completion for all three, or debugging for both C# and C++.
Worse yet, the licenses for those closed source parts specifically prohibit their installation and use in anything other than the official closed source VSCode distro. And this isn't just verbiage - there are actual runtime checks in all these products that block attempts to use them in VSCodium, Cursor etc.
The same goes for the official VSCode extension gallery / marketplace - you can't legally use it from anything other than the official VSCode. Enforcing that is trickier, but even here Microsoft managed to find a way to frustrate its users: it used to be possible to download a .vsix from the Marketplace, but that feature has been removed recently, precisely because people were using that in conjunction with Cursor etc.
Much open source, indeed.
It was really from 2007 on (.NET 3.5 / C# 3.0) that C# started to get major features at an ever increasing pace while Java significantly stagnated for quite a long time.
The problem is that you then need a way to capture exception specifications as generic type parameters to properly propagate contracts, which complicates the type system quite a bit. Which is why Java ultimately went with the much simpler proposal that didn't even try to tackle this.
So really, Sun and Oracle could have definitely moved faster around Java 6 and 7, the Java 8 release took a long time given the feature set.
I feel that records could have come quicker, their implementation isn't exactly ground breaking. Avoiding the async/await route was a smart call though, and Loom could probably not have happened much earlier.
Valhalla is another can of worms entirely
It also means the .NET ecosystem didn't need the inline+reified kludge that Kotlin came up with https://kotlinlang.org/docs/inline-functions.html#reified-ty...