Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Exception handling and generics are missing key pieces from go. But adding them will make go look like java.

I just wish java would add null safety in the type system in a first citizen way.

For enterprise use java has no competitors. You have c# which is microsoft trying to estabilish nash equilibrium fu*ing the developers. I am a bit worried about ever increasing complexity and a steep learning curve, but seems like this is a problem on all fronts.




Java is an inferior language to C#. To get a superior language you would likely have to go for Kotlin. There is no comparison, because Java gets features today that C# had for years. Not even mentioning having to retrofit green threads because adopting async/await (used by TS, Rust, Swift and other languages) is impossible at this point.


C# does not have virtual threads. What C# has is async/await. Which was nice for its time, but at the same time it's an error prone design, as computations should never start async by accident, blocking should be the default. C# also has no useful interruption model. Java's interruption model is error prone, but at least you can work with it.

Async/await also splits the standard library and the ecosystem in 2 (blocking vs non-blocking, or blue vs red), and it can't automatically update the behavior of old code.

The introduction of virtual threads in Java also works well with "structured concurrency", as seen with Kotlin's Coroutines. Kotlin's approach to concurrency is also superior to that of C#, actually. But what's interesting about Java is that its evolution is often one that involves runtime changes, lifting all boats. Java engineers preferred pushing more changes in the runtime, and somewhat ironically, the JVM ended up being the true multi-language runtime.

Java is a good case study of how languages should evolve. It has extreme backwards compatibility, and features being pushed are assessed for how they impact the whole ecosystem, including libraries or languages not in Oracle's control. Project Loom was developed in the open, compared to what Microsoft usually does.


Do give async/await in C# a try, it has all the structured concurrency features other languages have to invent APIs and special syntax for :)

(if you want to take a look at good structured concurrency, you might be interested in Swift implementation)


I worked with C# professionally.

The async/await syntax works with the language's other statements, but for a long time it had gotchas. It doesn't qualify as "structured concurrency", and it has the aforementioned issues — it's (accidentally) error-prone, it splits the ecosystem in two, and has no interruption model.

I am not familiar with Swift, but I think you can hardly beat Kotlin's implementation. This is a good introduction from Kotlin's lead:

https://www.youtube.com/watch?v=Mj5P47F6nJg


What do you mean by "it splits the ecosystem in two"? I never observed such split, certain methods intentionally offer sync and async variants.

Interruption is achieved through cancellation tokens and has to be handled by consuming methods. There is no way around it because interrupting execution at an arbitrary point would lead to all kinds of issues regardless of the language (unless it implements some form of transaction abstraction and rollbacks all uncommitted changes).


Those "cancellation tokens" from C# are a band-aid.

In Java, you don't need to initialize those "tokens" manually, because the interruption signal is baked into Threads. Moreover, a lot of the standard library cooperates with Java's interruption, which is why you see plenty of methods throwing `InterruptedException`; and it's also reflected in types such as `CancellableFuture` or the `Flow.Subscription` (reactive streams). Of course, user-level code that isn't well-behaved, can end up catching InterruptedException, or resetting the interruption signal, without actually interrupting. This makes Java's interruption model somewhat error-prone, but it's workable, and at least it's baked in.

Note that interruptions could also be preemptive, as you don't necessarily need cooperation. If you think of the call-stack, or flatMap/SelectMany in reactive APIs, the compiled code could check the interruption flag automatically and interrupt the call chain.

And resource leaks aren't necessarily a problem, if the interruption protocol is well-thought-out. In Java, try/finally still works in the presence of interruption, since at worst you get an `InterruptedException`. It's not ideal because you can interrupt the interruption process, and in truth the ideal would be for interruption to be its own communication channel, complementing that of exceptions. But it's totally doable, and here I am familiar with several libraries from Scala's ecosystem, namely Cats-Effect, Monix, and ZIO that show it (with limitations imposed by the runtime).

Either way, what C# provides is basically next to nothing. In fairness, some C# libraries tried fixing it, such as Rx.NET, but it's not enough. And the aggregate result in the .NET ecosystem is that interruption is not something people design for. Like what to interrupt a network socket? This ends up being a setting, presented as a timeout in case of inactivity, as a configuration of the connection, instead of a higher-level generic function that can be applied on the consumer side. And the probability for resource leaks goes up actually, because in the presence of concurrent races, you really need interruption.



Except when there is only one version and someone has to write the transition code.


> regardless of the language

That’s a choice, but it is unfortunate for the programmer. The truly right way to do it all is like Erlang does - where all processes are cancellable and nothing bad happens.


There shouldn't be a reason for a managed language to introduce function coloring. It's horrible.


But async/await is special syntax. I agree it's a nice improvement over using Task APIs directly.


And don't forget to bookmark David Fowler guidelines to avoid all the gotchas that everyone falls into while using async/await.



Java, prints 1:

    Set<File> s = new HashSet<File>();

    s.add( new File( "c:/temp" ) );
    s.add( new File( "c:/temp" ) );

    System.out.println( s.size() );
C#, prints 2:

    ISet<FileInfo> s = new HashSet<FileInfo>();

    s.Add( new FileInfo( "c:/temp" ) );
    s.Add( new FileInfo( "c:/temp" ) );

    Console.WriteLine( s.Count );
C#, test fails:

    string path = "/tmp/filename.txt";
    FileInfo f = new FileInfo( path );

    File.CreateText( path ).Dispose();
    Assert.True( f.Exists );

    f.Delete();
    Assert.False( f.Exists );
https://en.wikipedia.org/wiki/Principle_of_least_astonishmen...

Languages have their strengths and weaknesses.


FilInfo doesn’t override Equal operator in C#. HashSet will use it for comparison and those are two different object references so idk what the confusion is here.


A set is a collection of objects in which order has no significance, and multiplicity is generally also ignored. Observing the code alone, it isn't obvious that two FileInfo instances having the same path are themselves not equal. As has been mentioned elsewhere, FileInfo should not be hashable to prevent declaring it as a Set, avoiding the situation entirely.

C# isn't flawless. Java isn't flawless.


You can’t decide language superiority by the number of features. While C# indeed has many cool features, I do think it has already went into C++ territory where all the different, independently cool features have very non-trivial interactions that make it very hard to reason about.

Also, async-await is not a good thing — virtual threads are superior in every way in case of a managed language.


Why async/await is not a good thing? What makes green threads better? Do you know the difference, the issues async/await addresses that green threads simply do not?


This is code with async/await:

  async fn read_file(filename):
    f = await os.open(filename)
    let data = f.read()
    await f.close()
    return data
this is equivalent code with green threads:

  fn read_file(filename):
    f = os.open(filename)
    let data = f.read()
    f.close()
    return data
As you can see, async/await is pure syntactic noise, it doesn't convey any important meaning.


Async enables clear contract for a type that represents a delayed result.

Better implementations offer eager execution and allow to easily interleave multiple concurrent futures/tasks, like C#. Green threads on the other hand are a workaround to deal with blocking for the most trivial case of cooperative multi-tasking, offering little beyond that.


> Async enables clear contract for a type that represents a delayed result.

It's not really a type, otherwise it would be something like Future<T> in Java and plenty other languages. It is usually implemented as a transformation to a state machine.

Also, Loom is M:N and calling them green threads doesn't give you the whole picture at all. Not exactly sure what you mean by easily interleave -- functionality wise the two is more or less equivalent. You just get to keep your simpler mental model (and tooling) with virtual threads.


with green threads, every result is "delayed" and available exactly when you want it (i.e. as indicated by naive reading of the control flow in code)

> eager execution, interleave multiple concurrent tasks

green threads give you that for free

> deal with blocking

blocking is an implementation detail; both async/await and green threads can be implemented on top of either traditional blocking IO, or callback/non-blocking IO

I'd be even happier to discuss concrete code blocks / examples, that's where the superiority of green threads truly shines


Because they were created to manually run multiple threads on one physical thread, which is the kind of decoupling that a VM/OS is supposed to do for you.



Read David Fowler's guidelines.


Do you have link to any specific article of his (on virtual threads vs async)?



C# is turning into C++, sadly.

It appears every six months there must be new language features being added.

The last one, for declaring fixed size arrays in structs, with an annotation instead of proper grammar change is getting ridiculous.


F# is far superior to C# as a language. If you are going to jump to the .NET runtime that's the tool to go for.


apples and oranges


C# is a better language, but the JVM is a significantly better ecosystem. Java will always be behind (and type erasure for generics is annoying enough as someone who started in C# that I don't think it will ever catch up) but between hotspot and being the preferred language of 2/3rds of the clouds I think it clearly wins.


Type erasure is why there's such a rich ecosystem of 3rd party languages that run on the jvm.


Meaning that it would be harder for those languages on the clr because of generics? Would typing all generics as Object not be essentially equivalent?


   type erasure for generics is annoying
I too have spent significant time programming in both C# and Java. This complaint about type erasure in Java: When does it affect your daily life? There are so many craft workarounds available now that it hardly comes up anymore.

Also, would your opinion of Java significantly change if type erasure was removed or never existed?


I have much more experience in C# then Java FWIW, so you're welcome to take my opinions with a grain of salt. I've had a couple of really annoying errors with Beam serializers that took me way longer to debug then I thought was worthwhile.

I think it is a worse choice, it's too ingrained in the language and ecosystem to change. That and a couple of other nits would likely make me pick Java over C# or Go if I was starting a new project going forward.


If you like jvm, use closure instead.


Sorry you’re down voted. Closure might not be what the author is looking for.

There are a lot of languages you can use with the JVM, Closure is one of them, Groovy, Scala and Kotlin are some more. Kotlin is gaining a lot of traction, especially since it’s backed by JetBrains.


Clojure*


Java 21 doesn't retrofit green threads though. Quasar [0] is a library that implemented fibers for Java and the main developer pron has joined the OpenJDK development team. All that was necessary for first party support is to make the JDK libraries yield when blocking.

Adopting async isn't impossible at all, there is very little demand for it.

[0] https://docs.paralleluniverse.co/quasar/


Async/await is an inferior product compared to threads, for so many reasons (function coloring and meaningful stack traces come to my mind). Keeping the same interface as native thread, plus structured concurrency, are best of breed.

Many uncancellable threads + mutability + goroutine panic kill it all are serious issues for golang.


> I just wish java would add null safety in the type system in a first citizen way

With valhalla (value types), it might just come!


Afaik, no language as great of exceptional handling as Java. I prefer Go but I have to admit, Java's checked exception handling is amazing.


That is an extremely debatable topic. Which is why kotlin completely ignores checked exceptions.

Frankly, Id rather have a result and optional/nullable type like in rust/kotlin than deal with exceptions in any capacity.


Kotlin’s decision to make every exception runtime is the main reason I don’t use Kotlin. It’s especially baffling that they realized the issue with implicit nullability and got rid of it (though not with Java methods, which opens another can of worms), then went and intoduced implicit “exceptionality”.

The correct way to deal with Java’s checked exceptions would have been introducing a Result type, or, preferably, type algebra, like in TypeScript, so something like:

  fun openFile(fileName: String): File | FileNotFoundException {…}
Then you could handle it similarly to null:

  val content: String | FileNotFoundException = openFile(“myfile.txt”).?read()
  …then you have to do a check before you use content as a String…
or

  val content: String = openFile(“myfile.txt”)?.read() ?: “File not found”
(There could also be other possible ways of handling the exception, like return on exception, jump to a handling block, and so on.)

In fact, null should be treated as an exceptional value the same way all exceptions should be.


This would be my preference. I like `Either`or some other container type, but a simple union that makes exceptions explicit works also.


Checked exceptions are gross, they get in the way when you're prototyping, and you end up just ignoring them anyway (since you're prototyping)


The ergonomics of checked exceptions may be debatable but compared to golangs explicit error handling at essentially each function call is definitely worse.


Yeah, I've got a lot of Java experience and a wee bit of Go language experience and I agree with you. I like Go in almost every way except for it's error handling. It's just wrong to have to check every goddam function one by one


I guess that's the reason why most Java programs I use cannot do proper user-facing error messages. Because it is so easy to just ignore error handling. The exception will be caught by the top-level, right? This is how almost every Java cli tool prints a stacktrace on even most trivial user errors like file not found.

Having to deal with errors and forcing the developer to do proper error handling is a good thing.


I don't mind Go's errors. I do mind the complete lack of hierarchy in that. Java's exceptions are hierarchical. I can create an exception that specializes from `IOException` and that I feel is really powerful. Go added this half-baked and much later. So, most FOSS libraries don't support it yet.


Both Java checked and unchecked exceptions are inferior to signaling errors by return values like Go/Rust/Haskell do.

Exceptions are not composable, cannot be generic, and it is not visible in the source code which lines can throw, so every line is a potential branching point.


I don't think the jury will ever return on this topic.


Go has had generics for more than a year now


I agree, Go has had generics for a little while (of course they are different from Java's generics because Go's type system is different from Java's type system) and I don't think the parent comment writer was aware of that.


Not even in the same league as Java.


What is “enterprise” use? Plenty of enterprises use Go.


By enterprise I mean a lot of features related to data consistency, scalability and integrations.

I do not know about go ecosystem, but java and spring have mature solutions that cover the most advanced use cases.


Kubernetes is what I would call very advanced use case.

But that's the thing with Java, yes there are libraries for everything and one could see that as a problem actually.

Spring was mentioned before, but it's the perfect example of an overengineered/ heavy library that does a lot of black magic.


Java is strange because it can be both verbose and magical at the same time.

Just the other day I ran across an issue where spring was wiring things up correctly on Linux but not Windows.


I too dislike dependency injection frameworks. I try to always wire manually myself. I'm tired of debugging old SpringFramework apps where changing the JVM by a patch level or changing one JAR dependency breaks the wiring. I have seen it too many times to count.


Originally prototyped in Java, rewritten in Go when a couple of advocates joined the team.


If all you have is spring boot, everything looks like a spring boot problem.


Plus Spring is honestly quite badly programmed.

The pagination object is bulky and unnecessarily complex, where a simple offset/limit is enough (and a nextUrl for cursor-based access).

When we looked into cluster locks, they’re not even released if one node goes down. I mean, who would need a lock implementation that just stores a line in a DB? And the doc doesn’t even warn about it.

Apache contributors were much better-skilled.


    Apache contributors were much better-skilled.
That is quite a broad statement. Two negative points about Apache Java libraries I can think of: The original "lang" libraries have not aged well at all. Also: The HTTP client libs are a fiasco. Very challenging APIs and weak documentation.


Not really. Software first enterprises do. But most enterprises that have a non-software focus generally prefer languages that have been around for a while and are known by lots of people.


I’ve written go code for large corporates in Australia who absolutely did not have a tech or software focus. One was a notoriously bureaucratic company which used to be a government owned enterprise. For the niches it fills well, it’s become almost ubiquitous.

With regard to this:

> But most enterprises that have a non-software focus generally prefer languages that have been around for a while and are known by lots of people.

I know developers who work for large blue chip financial sector and other traditional sectors. They’re on the same React/Vue/Webpack/whatever treadmill as all the frontend devs devs working at web dev agencies. And they’re often compiling it down from TypeScript, which has not been around for a very long while at all and is not known by lots of people (relative to the size of the JS community.

Swinging a bit of Go for a service in an environment like that isn’t really that hard.


By enterprise he means non-webshit industry.


Wow


The programmers don't make that call. The CTO does, so they can hire anyone.


This is definitely one of those. “Tell me you don’t know .NET with out telling me you don’t know .NET” moments.


> Exception handling and generics are missing key pieces from go. But adding them will make go look like java.

You know that Go has generics since a couple of years now?




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: