For some reason, this is missing from the article. If there was any feature that would sway existing Golang developers to switch to Java, it would be this. It would perhaps also convince the haters of the reactive-style concurrency patterns.
For some reason, this is missing from the article. If there was any feature that would sway existing Golang developers to switch to Java, it would be this. It would perhaps also convince the haters of the reactive-style concurrency patterns.
I worked with Java for 10 years and switched to Go and I will never go back.
This is mostly because applications and libraries are so hard to reason about and understand due to inheritance, packaging, OOP, build tools ect compared to Go.
Go is simple. It's easy to understand, read, and maintain. The packaging is like how you would package files on your computer in single folders. The tooling is built into the language. You don't need a IDE like IntelliJ just to make it feel reasonable to work with.
Maybe all of this has changed, but most of the libraries I see in Java today still look like this.
You are just at the early phase of the project.
> Go is simple. It's easy to understand, read, and maintain
My opinion is that Go is too simple, to the point that it hinders understanding and readability. 4 nested loops with random mutability is much worse than a clear Stream api "pipeline". The 31st if err check will not be actually handling the underlying problem, while you can't forget about Exceptions -- even if they do bubble up to the main app, you will clearly get an actionable stacktrace.
> The tooling is built into the language
This has benefits, I will give you that. At the same time, I think all these new build tools are anemic and can't be used for anything more complex than interacting with the same language's standard dependencies. Gradle is not sexy, but is actually capable of doing any kind of build.
Unless by "incomplete" you mean it could have more features. But C++ and Rust (and Java) have been piling on features for years, with no signs that they will ever slow down or stop, proving that they're also "incomplete" by this definition. IMO this is really just a result of there being too many cooks in the kitchen, and a lack of leadership committed to saying "no" to feature requests.
This is also why using Rust or C++ requires every organization to agree on a subset of the language they will utilize. Rust isn't as far along this path, but it's heading in the same direction as C++.
But with Go, every organization can use the complete language. That's how simple it is.
Go has been used by many organizations to build stable, large, and scalable systems that have been operating successfully in production for many years now. That's how complete it is.
Lately, I am seeing very few codebases getting supported more than 3 years in companies offering software products. Every 2-4 years services are getting rewritten, what's the point of having tool intended for 15 years, when services are deprecated in 4 years
The syntax is easy enough you can start coding just after having looked at basic examples.
Personally I actually like Go because it's so boring.
I tend to see languages more like tools rather than valuable knowledge I must learn, so a simple yet powerful enough language is a perfect fit for me.
If you want to express your soul in your code, do that on the weekend using whatever language you want. Trying to mix these motivations is a recipe for disaster.
Successful by what metric? Achieving business objectives? I don't necessarily disagree with the point, but this seems like a truism.
Nah.
99% of the code written out there doesn't need layers of indirection, responsibility tossing around, Code splitting across classes, design patterns, inheritance and the class jungle that is common in Java.
Rise of languages like Go is simply majority of the people realising when they want X, they are better of writing just X. You don't have to write a generic version of X that needs to work in a dozen situations. This is for a simple reason. Most of the times, there are no dozen situations. Most, not all the times.
Most of the code I write, doesn't change all that much. If you are writing code that needs to run for decades in an industry where grifting and job hunting is a daily affair you are doing it wrong.
After 20 years of building all sorts of software, I know that this a fact, but the challenge is to make others aware of it.
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.
With valhalla (value types), it might just come!
I do not know about go ecosystem, but java and spring have mature solutions that cover the most advanced use cases.
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.
Just the other day I ran across an issue where spring was wiring things up correctly on Linux but not Windows.
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.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.
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.
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 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.
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.
C# isn't flawless. Java isn't flawless.
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.
(if you want to take a look at good structured concurrency, you might be interested in Swift implementation)
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:
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).
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.
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.
Also, async-await is not a good thing — virtual threads are superior in every way in case of a managed language.
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.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.
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.
> 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
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.
Many uncancellable threads + mutability + goroutine panic kill it all are serious issues for golang.
Adopting async isn't impossible at all, there is very little demand for it.
Having to deal with errors and forcing the developer to do proper error handling is a good thing.
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.
Frankly, Id rather have a result and optional/nullable type like in rust/kotlin than deal with exceptions in any capacity.
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.
You know that Go has generics since a couple of years now?
Matter of taste I guess, to me Go code looks ugly, it's too verbose with all that error handling every other line which hurts readability. Also the docs are often so cryptic and unhelpful, one needs to rely on examples elsewhere. I do use it though, when I need something fast in a single binary.
Context that isn't documented with every usage is context you need to remember. Trade off, but I'd much rather not have to remember multiple contexts for the same name every few hundred lines.
> Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex.
https://github.com/golang/go/wiki/CodeReviewComments#variabl...
lineCount is literally perfect meanwhile sliceIndex could be anything.
If anything I think the cases where you can use Go successfully are only o fraction of the cases where you can use Java successfully. It's not necessarily Java itself but the ubiquity of the JVM and its ecosystem (see Kotlin, see Scala as examplea that have leveraged this ecosystem successfully)
Old languages force you to understand really core issues because the stack is 1m+ lines of code and you need an operating model for all that magic.
New languages do the same thing, but it's because half the really good stuff is <experimental>
Python and JS are in the current sweetspot, go is up next, and after that, Rust.
could you elaborate why do you think so, and maybe give examples of cases where Go can't compete with Java?..
Otoh, Java has a huge ecosystem. Huge. This plus dependency managemnt make it the first choice in most cases. Not even going to go into the massive innertia given that Java has been around for decades (who is going to rewrite everything in go?)
Today most of the time I pick Kotlin which is sort of whatbJava could have been (or maybe will become) with proper investment and care.
Today though, with languages like js being the most popular one, I would be surprised if inflow of mediocre developers into Java world would be even the same, not to mention higher, then with those more popular languages (Go included)
The language itself might be fine but I'll never touch it because of that.
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. - Rob Pike
who produce mediocre code based on the wrong ideas of object oriented programming
This overlooks this history of OOP. Each decade, lots of new ideas have emerged so that skilled programmers continue to use the same languages (Java, C#, C++, C), but change how they use them. C++ in 1996? Let's fight about diamond inheritance! C++ in 2006? C++ in 2016? C++ in 2026? Repeat for all four languages that I mentioned. The story will look similar. Anyone good writing new code in these four languages isn't using many levels of inheritance. It is gone. Sure, it exists in the language for historical reasons, but it is hardly used in new code. "Prefer composition over inheritance."That will be reality for most companies, no matter the language. And if Java projects still got delivered in such conditions - it might also be a mediocre language but it is also just good enough.
Wait until your code base grows, your team grows to >10 developers, and you will understand what I mean.
Java (and preferably Kotlin) are a lot more serious about making sure your code is robust before it compiles.
98k results...
No Option type, so you can still use results of a function if it returns an error.
You are making great points for Rust, Ocaml and Haskell.
Thanks to Turing, anything that is Turing-complete can duplicate any other thing that is Turing-complete. So, yes, you can do all of Haskell's types in Java. That is not a flex.
The flex is doing them in a non-horrific way.
"turn X upside down" are different words for "use X contrary to its original intention and design".
You will find very few people willing to let go what people undestand as Java so as to be able to do Haskell-in-Java.
It’s not like Java hasn’t been going in the same direction, see records, sum types, pattern matching.
People still hate it because it's immutable, therefore you can't do hashmap.add(), and it's hard because they can't randomly return nulls, and it's slow because the hashmap now takes o(log n) always instead of o(1) sometimes and o(n) other times.
People hate it because it's different to the Java they learnt 20 years ago, that they claim is exactly the same as today.
sealed interface Option<T> permits Some<T>, None<T> {
record Some<T>(T value) {}
record None<T>() {}
}
Sure, you have written 3 words more than Rust, and?Python, Node, Java all have to pre-install lots of dependencies before you can use them, fine for developers, not so great if you want to distribute software for people who are not software savvy, who typically just wants to download one file, install and start using it.
c and c++ can also do one executable, but, it is not as portable as Golang, and not as static-link friendly as Golang.
I was planning to write a small side-project to generate GitHub Actions boilerplate. And this time, I intentionally chose Go for that exact reason. https://github.com/ashishb/gabo
Also DNS uses dynamic linking unless configured otherwise, while being OS specific, then that are the libraries that rely on cgo.
Let me tell you the most common scenario. I have a fairly popular FOSS CLI tool written in Python. I cannot use the features from the latest version of Python or else I will alienate ~50% of the userbase. This problem does not exist with Rust or Go.
This problem would have existed for Java on Android except Google took the burden of "desugaring" the Java 17 code -> Java 8 compatible bytecode for the old devices.
i think it's a flaw that wasn't considered properly in the standard java toolchain to not produce an embedded java runtime into a final packaged artifact that is self-executable.
You end up with third party tooling like: https://www.ej-technologies.com/resources/install4j/help/doc... (and https://launch4j.sourceforge.net/ too).
If oracle bundled this tool into the JDK, it would've not been an issue at all.
Having AOT compilers as commercial offerings, was seen as one way to capture value in the Java market, in a culture where most compilers were still commercial, GCC being the exception.
In an anemic way, with mostly third party offerings few people know or care about, and various degrees of pain and shortcoming to their use. It should be a first class feature, and as simple to use as is in Go (including for cross compiling).
In general, if something exists in "some form" for 30+ years in a language, and only a handful of people use it, whereas in another it's used all the time and people from other languages are jealous of how well it works, then the formers "some form" is not a good one.
These days there's also GraalVM native image which does produce Go-like results. But with everyone using Docker on the server anyway it doesn't matter anymore. People who talk about single binaries are confusing to me. What are you doing where shipping one file is so much simpler than shipping a container?
Desktop applications.
Java would be a more popular desktop application platform if it weren't for the difficulty in this area (which, to be fair, isn't the only difficulty - cross platform is difficult inherently).
Not really.
You can make an Electron app with JavaScript and ship the binary (or installer) on any platform. It's not a single executable file, but the user experience is the same.
I don't think there's an equivalent in Go that allows you build a desktop app like that (with frontend and backend both written in Go)?
Otherwise Qt has already conquered the desktop GUI world, it did not for a reason: the license mess.
And then Electron happened.
Which is worse for users, but better for developer productivity (probably nicer for developers too: C++ is not exactly fun).
I am still hoping that JVM Desktop apps come back at some point, maybe with Kotlin Multiplatform?
Sorry for the snark, but users will undoubtedly be very grateful to them for not considering Electron.
Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.
>Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.
Like Slack having 1000x the memory and CPU use for 1/10th of the features a 200K app like ICQ used to have 25 years ago?
I've used Electron as a developer, too. While it may have been fast & easy at one time, I think that argument is losing merit. Electron had a lot of security holes and patching them has made common tasks harder. Security with Electron is a problem in general given its massive surface area.
Users definitely notice the performance issues. It's certainly not just devs complaining about abstract issues. Yes, people use these tools, but that's mostly out of necessity, not preference. Moreover, big players like VS Code have had to write swathes of the app in C++ for performance reasons.
I look forward to a return to optimizing for the user, not developer velocity. There will likely always be attempts at cross-platform toolkits and there may be a good solution in that space. I hope Electron isn't the best we can do.
I'm new to Electron development. I've read the docs recommending hardened runtime. Would it be sufficient for security? Can you give examples of such security issues?
I'm not suggesting every Electron app is a giant bag of vulnerabilities, just that you have a lot more to keep on than you would writing with GTK or UIKit. And, since everything is bundled with the application, you can't rely on OS updates to fix things for you. You need to cut a new release and distribute it.
If you follow the Electron recommendations on security you'll be on the right path. You'll just find common tasks have become harder than they were back when Electron was attracting people with its ease-of-use. I found trying to do type-safe IPC to be an exercise in frustration. If you read the old docs, tutorials, or books, you'll find IPC used to be considerably more free-wheeling. Locking it down is the right trade-off, I think. But there's been an accumulation of many small changes like that. As a result, I don't think the framework is nearly as easy to work with as when Slack or VS Code adopted it.
But I found out that there is this new Preload module in electron that lets you use Node.js very easily via normal export and import. I'm using a popular starter template and it works great so far.
> Yes it's bulky and sometimes slow, but it offers a lot of features and allow developers to ship features and updates really fast, while only needing one codebase for all platform.
So you are saying that it is bad for the user (bulky and slow), but good for the developer productivity. I really don't see how the users could like the fact that the developers are being more productive while making a worse app.
https://conveyor.hydraulic.dev/
It can be used to ship servers, we use it that way for our own servers. It sets up systemd so you don't need to use Docker. But where it shines is desktop apps.
Windows users get a little 400kb EXE that installs/updates your app and then immediately starts it without user interaction required, so it's effectively a single EXE. Mac users get a bundle. Linux, well, Linux users get a deb that sets up the app+its apt repo, or a tarball. Maybe in future a FlatPak or Snap or something else.
It knows how to bundle and minimize a JDK along with your app as part of the release process. Works great for desktop apps.
It's for Electron too, same deal, easier than Forge in my humble and very biased opinion.
a little memory heavy, but no more than electron apps while being less janky
1. Start IntelliJ and check it out. It's Swing but it feels modern and fresh.
2. Go to https://www.jfx-central.com/ ... JavaFX was introduced years ago to add far better support for modern visuals. JFX Central is a website written with JavaFX itself - it runs server side and streams drawing instructions to the client (implemented using a mix of divs, svg tags and css). You can do this because JavaFX implements the CSS2 drawing model. The website looks modern, like any slick website would today, but you can also download and run it locally. It uses Conveyor to do that in fact. The desktop version looks exactly the same as the web version.
But yeah, arguably they should be unified.
You can even go above and beyond with cosmopolitan libc v2, which makes c/c++ build-once run-anywhere: https://github.com/jart/cosmopolitan/releases/tag/2.0
There seems to be some work getting cosmopolitan libc support in Go, but it is not ready like it is for c/c++: https://github.com/golang/go/issues/51900
Edit: There can be some problems with using static, if you are using your system package manager for library dependencies. I would recommend compiling dependencies from source using CMake and FetchContent, this has solved pretty much all problems I have had with c++ dependency management.
So in Go you make available the library's source code, and the end-user will download it at build time, or vendor it in the source tree.
uh-huh
but, in all seriousness: I wonder why that is? I struggle to think of a technical reason that go would be unable to load and invoke a .dll even if one had to name it .so https://github.com/golang/go/blob/go1.21.1/src/plugin/plugin...
Are .war files no longer a thing? ;)
You can ship one compiled binary in Java too if you want it. https://www.graalvm.org/22.0/reference-manual/native-image/
> Go is simple. It's easy to understand, read, and maintain
Go involves a lot of code repetition which makes it difficult to human-scan and maintain. Worked on both large scale Go and Java projects and I found Java projects easier and more comfortable to maintain. Had to pay a lot more attention to Go. Go is easier to write though thanks to its well-designed and extensive standard library which is possibly the best in the world, but the maintenance angle still tilts to Java. There are also more Gotchas in Go compared to Java.
However, the Go code bases I’ve encountered have been less abstract and to the point(even if it means repetition of some little things). I also found it to be free of boilerplate except for the if err != nil part.
I guess those that have spent decades in such abstractions must have learnt a skill to navigate such a spaghetti. But, I still hate it every time.
No matter the amount of abstraction though, one rarely runs into the sort of issues in Java that Go code tend to run into frequently - causing multi-million dollar mistakes frequently even for experienced Go programmers. For loop semantics https://bugzilla.mozilla.org/show_bug.cgi?id=1619047, Unintended variable shadowing, common mistakes in slice appends/copies, slices and memory leaks, defers inside loops, nil interface vs nil, panics in go-routines. There are loads of bugs in OSS projects wrt to these usually repeated again and again.
Go is very simple to learn and very hard to master writing bug-free code. Looking forward to seeing how languages like Rust perform if/when adopted by enterprise.
Even if you’re shipping a jar. You can ship one artifact by using jlink or you ship the runtime in the docker image.
This has been a solved issue for ages.
At least in the past you could also make a single .exe out of it via 3rd party if you wanted, but I didnt use that for 20+ years so maybe its not valid anymore.
1. Compiles to native code with a single and very reasonable sized binary on pretty much any platform.
2. Compile to WASM (coming this year) if that’s your thing.
3. Excellent concurrency support with lightweight and simple mental models
4. Variables are not nullable by default thus simplifying tedious checking in your codebase.
5. Syntactically it’s the best parts of Java and JavaScript combined without all the foot guns and verbosity.
6. Full support for both OOP and functional code styles
7. Existing interop support with C, C++, Rust, Java and JavaScript and in the future WASI.
8. Fully static / compile time metaprogramming capabilities coming this year.
9. Also have maybe one of the best teams working on it that I’ve ever seen in an open source project anywhere. They put in a stupid amount of detail and care to try and keep everything pointing in the right direction at a macro level and have really strong levels of transparency around how the language is developed https://github.com/dart-lang/language
Honestly I think it’s critically under-rated and under-used. Most of its common criticisms I see about it are many years out of date.
And its null handling and checking is the worst.
I absolutely hate it. Java over Dart any day.
I’ve never heard this criticism before and have no idea what you’re even referring to here but I am genuinely curious..
Same with your second point. I made a big point to explicitly say I think that right now Dart is very underused and has a huge potential outside of Flutter. Quoting that back to me as though it was something I hadn’t considered is equally as confusing.
Then I moved to Kotlin + Jetpack Compose, and I don't see the point of Dart anymore.
while for go you'll find yourself deep in the mud of choosing what 3rd part library to use for logging and how to make it work with the rest of custom stuff you have to write
Other follow fashion and if some dick looking for fame declares A as obsolete and B as the new and shiny one (to be replaced soon anyways) then the lemmings would follow and sing the gospel.
I am not touching Go, other than on the projects I have some customer or higher up telling me to do so.
If they did one thing exactly right on the language level, it's the [lack of] OOP: interfaces in their implementations, and no inheritance, overridden methods, covariance / contravariance games, etc.
You can of course write in Java in that style: only extend interfaces, never inherit classes. But many libraries, including the standard library, actively refuse to cooperate.
Go is easy, but it's surface level easy. Building and maintaining large applications in Go if you don't have a huge team is a giant pain.
JVM (especially paired with Kotlin) for me atleast has meant regaining the expressivity I was missing from Ruby and Python when I moved to Go whilst retaining (well actually usually beating) it on performance and scalability.
I lost absolutely nothing to go to JVM but gained so many things.
I agree the Go lang compiler/runtime needs "a Kotlin", with more null-safey build in, proper sum types, a std lib that makes heavy use of null-safety and sum types, better story for polymorphism, better ergonomics for writing stream pipelines, and additional compilation targets (like JS, WASM). This all while proving stellar interop with all Go code, obviously.
Kotlin is quite a sweet deal.
> What’s a better story for polymorphism than duck typing?
Something that give compiletime guarantees and has not runtime overhead.
> What would (better?) null-safety look like in Go?
https://kotlinlang.org/docs/java-to-kotlin-nullability-guide...
Go is a high velocity language. Code reviews on language/style issues are non existent, it’s GC is blazing fast (not our experience with JVM) and it’s really easy to read. I’ve watched many new engineers ramp up on both systems, as both operated side by side for a while, Go was inevitably faster for engineers.
What made maintaining Go hard for you?
Lack of a "default" stack for nearly anything. The stdlib is great but the ecosystem isn't. Which web framework? Does it come with a logger? If not which logger? Do the third party libraries I want to use work with my logger/have a mechanism to provide one via an interface or am I shit out of luck? This goes for so many more things though, cache libraries, data structures (because Go stdlib collections are a joke). Contrast to Java here you have a relatively minimal set of very long-lived deps for all of these "basic" things. Ecosystems like Spring, the slf4 facade, Apache etc provide the foundation that most Java programs sit on and that has no alternative in Go land.
Go module transition was also hot garbage. It's better after sure but going through it was worse than Java 8-9 or to Java 11+ both of which were "difficult" transitions for Java but vastly less disruptive for me personally at least. Then take all of this stuff and multiply it by the number of teams you have if you don't have a central team doing library choices and laying down architecture guidelines - which we eventually got but not before all the Go codebases had turned into by and large unmaintainable messes.
IMO Go is high velocity only in the simplest sense, it's very easy to pump out shit tons of code. With a big team of mediocre developers this is even more true. The problems all come later. Big change in requirements? Good luck with that. Had a team try to go crazy functional with Go and now they have immutable data everywhere and allocations are completely destroying the throughput of the Go GC? Good luck fixing that. etc.
The velocity eventually moves from it's strength to it's achilles heel once the codebases are big and bad they are really hard to fix.
I get that most of these problems are "big company" problems and maybe in smaller teams with a stronger hiring bar you won't run into these or maybe not at the same severity but they severely impacted my view of how well the language scales to large teams and codebases.
>IMO Go is high velocity only in the simplest sense, it's very easy to pump out shit tons of code. With a big team of mediocre developers this is even more true. The problems all come later. Big change in requirements? Good luck with that. Had a team try to go crazy functional with Go and now they have immutable data everywhere and allocations are completely destroying the throughput of the Go GC? Good luck fixing that. etc.
I doublt things would go better for teams trying to go full OO in haskell, or full functional in Java.
> I doublt things would go better for teams trying to go full OO in haskell, or full functional in Java.
That is the beauty of Java, no one does this.
They don't feel like they need, should or will get approval to.
Go is pretty much the wild west because "the std lib is enough" attitude permeates everything. Build your own datastructures, build your own anything really.
I can understand why many people find that attractive and hate Java as a result, sometimes Java feels more like Lego and less like programming but it does create predictable reliably constructed software that is generally easy to clean up even if it's done a bit poorly.
Which is something I value because I often end up in the code janitor role and/or being air-dropped in to get a project back on schedule or drastically cut down the defects etc.
I’ve never had a logging issue in large systems. Explicit error return (as you know, on every function) allows you to log in your code, not lean on only libraries that support your interface.
Modules rollout was part of growing up. But, you won’t get Go 1->2 upgrade issues as we have 100% backward compatibility on version upgrades. Moving to the latest version of Go is trivial and simply unlocks new features.
Too many allocations for Go is going to be too many allocations for JVM too. This seems like an problem isolated to that team.
I’ve done Go at both a three engineer startup and at Google and I can’t help but notice none of these are really the type of problem that crop up later.
Though I still don't see a standard web stack. "stdlib is enough" is only true for people that don't need their hand held and/or can organise good standards across teams.
If it was just me or people I know are good writing the software that works. At Google it probably works because of aforementioned hiring bar. At the average large-ish company? Not something I like leaving to chance because I have usually ended up disappointed.
No, Go’s GC is a toy compared to the JVM’s. It is lowerish in latency by actually stopping the application threads when under high contention.
Java doesn’t slow down the allocation rate, it tries to keep up with the churn.
In the course of optimizing I came to know the various JVM GC algos (concurrent mark/sweep, parallel old, etc) by the corresponding memory graph alone. I never, ever had to debug similar latency in the Go stack.
Both of those are only picked for low sized heaps with few cores, probably within a container. Were these micro services?
G1 is the default for larger heaps and multiple cores, and ZGC and Shenandoah (low latency GCs) have to be manually turned on AFAIK.
OP said:
>Java doesn’t slow down the allocation rate, it tries to keep up with the churn.
This is incorrect. ZGC will block a thread when it cannot give a thread any memory, because it can't collect and free memory at the pace needed. Google "allocation stall" for this. ZGC can achieve very low latencies akin to Go's GC, I don't know if the throughput is higher or not. Multiple cores and some GiB of heap space is when ZGC will shine.
The GC monoculture is great from a simplicity and out of the box experience but there is a very good reason to a) want multiple different GCs tuned for different workload types and b) have competition so that the best designs can be found rather than having to fight to be the only implementation.
Java's GC improvement is relentless because Java's applications are relentless in memory allocation.
Being on endless Java memory/perf issue prod calls I can say Java GC improvement, performance tuning is cottage industry in itself. Meanwhile end users keep suffering but at least Java devs get to tune GC to their heart's content.
How does that vary from Java?
There’s a lot that I really like about the JVM and it’s tooling, I just don’t like writing Java code anymore. JRuby kinda gives the best of both worlds.
Here’s the talk. Virtual thread demo is around the 45 minute mark.
IIRC Truffle is dramatically faster in some benchmarks but slower in others. And there are some usability aspects of the language that are negatively impacted.
But TruffleRuby is something different. It is another Ruby implementation (just like JRuby is a Ruby implementation) using the Truffle framework. And Truffle framework requires to be run on GraalVM.
This one is a couple years old now but you get the idea:
https://eregon.me/blog/2022/01/06/benchmarking-cruby-mjit-yj...
I don't know where to find more up to date benchmarks.
Not sure about this, but a trajectory question: why does the Go community have so few concurrent containers but Java community does? I mean, even `sync.Map` is specialized for two specific use cases instead of like Java's ConcurrentMap that is of general purpose. And In Java there are concurrent sets, queues, barriers, phasers, fork-join pools, and etc. I'd assume that even with Go's go routines, we can find great use of such containers, right? At least fork-join is not that trivial to implement. And using mutex everywhere seems just so... low level.
I understand that there are 3rd-party implementations, but concurrency is so tricky to get right that I would be really hesitant to adopt a 3rd-party package, unless it is as mature as Java's JCTools or Google Guava, both of which are also backed by a large number of users and developers.
edit: Sorry, it's actually:
try (var executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(...)
)
instead of `go`But you often want the concurrently running threads to return some results, that try-with-resources is part of the structured concurrency that helps you with closing off a branching point, similarly to how we use while loops instead of gotos. `go` in itself corresponds to `goto` basically, with many of the same negatives.
Thread.ofVirtual().start(() -> System.out.println("Hello"))Doesn’t sound like a fair comparison thou the Java version is missing things
The Java version would require all of those in the exact same way though.
fun <T> go(body: ExecutorService.() -> T): T =
Executors
.newVirtualThreadPerTaskExecutor()
.use(body)
Now you can write: val result = go {
val result1 = submit { slowOp() }
val result2 = submit { blockingOp() }
result1.get() + result2.get()
}
or words to that effect. You can also reduce it a lot in Java too, as mentioned by another commenter. class Shortcuts {
static <R> R go(Function<ExecutorService, R> task) throws RuntimeException {
try (....) { return task.apply(service) }
}
}
and then you can write: var result = go(service -> {
var result1 = service.submit(() -> slowOp());
var result2 = service.submit(() -> blockingOp());
result1.get() + result2.get();
});
using static imports.Now if you're making a cultural point then sure, Java APIs tend to be named quite explicitly. It has advantages when searching or auto-completing, and it's easy to wrap with aliases if you want to abstract a boilerplate pattern away.
Is there a good honest writeup on why is this interesting? Very curious.
The earliest JVMs had this, green threads. Performance was terrible so it was eventually dropped.
I want to fully utilize every CPU and every core I have, why do I want green/virtual threads again?
So you get the benefit of async frameworks, with none of the negatives: the blocking model is trivial to reason about, read, maintain, and works perfectly with tooling (you won’t get an exception in some WhateveScheduler, but at the exact line where it actually happened. Also, can step through with a debugger).
I used to be heavily involved in java-based server performance and scalability work back in the 90s and 00s but have been working on other things the last decade. But it would be fun to learn more about where things stand.
Green threads are for a cleaner code structure. async/await comes quite close, but requires function coloring, creating high coupling. The threads have to have low overhead, so that I don't have to think about whether I should create a new one or not.
Kernel threads are for parallelism and scaling across cores.
Later HotSpot became thread safe (and fast - a rare achievement), so started using real OS threads so it could go multi-core.
Virtual threads are M:N threading. There are multiple native threads so you can exploit all the cores, and also user-space runtime managed stacks so you can pack way more threads into a process.
In the 90s we didn't have multiple cores but we had multiple CPUs. I started using java in '96 on a 2 CPU SPARC and the lack of real thread support was limiting. When green threads was dropped in favor of real (OS) thread support there was much rejoicing. I worked primarily in server performance back in those days.
> Virtual threads are M:N threading.
Solaris had M:N threads early on but it also was dropped.
If your task is CPU bound, then virtual threads don't gain you anything.
On the other hand, if your task is IO bound, e.g. for a webserver, virtual threads make it trivial to spin up a new thread per request, for massive workloads.
Honestly, its way too early. Virtual Threads will need a a "killer app" (in this case a killer framework) to pull in devs.
I'm thinking something that would be a clear differentiater such as a multithreaded GUI framework.
It only ever runs tasks that actually have to be run, has integration with javac, can work in parallel, and even has cross-company build caches if needed.
Also don't forget that Java can do hot reloads with the debugger, or with tools like JRebel. Certain frameworks support it 100% and it will be much much faster than whatever go does.
Why isn't it fast out of the box? Why does the simple "provide a list of deps, provide a list of paths, build" take so darn long? Because Gradle is a great tool or something?
Don't forget that it's also already at version 8, where each change is mostly incompatible, bizarre and inexplicable breakages between versions, often due to meaningless option renames. Imagine if they spent all that effort on actually making it a great tool.
Also, was that a clean build?
For me bigger feature is pattern matching for switch and records.
Go, Rust, Ruby. These three cover all cases.