Go 1.13: xerrors(crawshaw.io) |
Go 1.13: xerrors(crawshaw.io) |
Standardising error unwrapping is a great idea IMHO and I think that this has a lot of merit.
I don't like the `fmt.Errorf("more description: %w", err)` though for several reasons.
Firstly it is a lot more opaque than Dave Cheney's original mechanism errors.Wrap(err, "more description"). You've got to check the format string for a `%w` to see if it is wrapping an error or not.
Secondly why is this really important functionality in `fmt` and not in `error`?
And finally we've been encouraged to write `fmt.Errorf("more description: %v", err)` (note `%v` not `%w`), so I think there will be a lot of unwrapped errors.
...
I'm not sure enough has been thought about the backwards incompatibility. With rclone I try to maintain compatibility with the current go release and a few previous ones so that rclone can remain running with distro go versions and gccgo both of which are a bit behind. For something as common as error handling this will cause lots of libraries to suddenly be no longer usable with anything less than go1.13.
IMHO I think this would be better staying as a non standard library package for the time being while more kinks are worked out.
Yuck.
`fmt` already depends on `errors`, and Go does not allow cyclical dependencies, so `errors` would have to reimplement string formatting.
Which already exists incidentally, internal/errinternal was added so errors.New and fmt.Errorf could return the same type.
It does make errors pull the entire formatting machinery, but chances are you're probably using string formatting long before errors so meh…
That's an argument for putting it in the language layer or putting it in its own place.
The parallel reddit thread seems to agree too: https://www.reddit.com/r/golang/comments/biexq0/go_113_xerro...
I suspect that the overwhelming majority of Go developers has no idea this is in the works. It was covered once on the blog last August in the Draft Design summary, when there wasn't any code behind it. It was not mentioned in Go 2 Here We Come[2], nor at the start of the below golang-dev thread. It was mentioned on golang-dev when I posted a link to the issue tracker in late January, but my posts would see a fraction of the attention vs those by Rob, Robert, Russ, Ian, et al.
There are outstanding issues with the current draft, specifically its performance[3] and API[4].
If it lands in 1.13, please give it Experimental status, with a build or env flag to disable changes to existing APIs, and perhaps a way to re-enable them on a per-function or per-package basis.
[1] https://github.com/golang/go/issues/29934
[2] https://blog.golang.org/go2-here-we-come
[3] https://github.com/golang/go/issues/29934#issuecomment-48650...
[4] https://github.com/golang/go/issues/29934#issuecomment-48350...
.
[Originally posted on golang-dev, in response to "Last call for feedback on Go 1.13..."]
https://groups.google.com/d/topic/golang-dev/jPY0RYXSvCU/dis...
> If the last argument is an error and the format string ends with ": %w", ...
This seems like a magic-string kinda hack to me. I like the idea of wrapping errors so that you keep the stack and full context, especially since you may need additional structured data from all errors (e.g. DB error, access error, ...) to produce user facing messages, so IMHO the wrapping should be more explicit than just %w.
(I'm not defending it so much as explaining it. I'm not sure how I feel about it myself.)
I missed that. How else do you wrap an error with xerrors?
I expect we will get used to it pretty quickly.
Fill in the blank "Well basically what happened is ___________"
For example i've found this has helped me to go from "Invalid phone number" to "The phone number needs to be 10 digits"
More on the topic, I'm really happy to see this coming in so far ahead of 2.0. I want to get ready for 2.0 because of the gains I hope to see in idiomatic go in 2.0.
I'm not sure I understand why this is something you need to do. "Figure out a way to stop storing what you don't need." Who cares if you don't need it, or at least don't need it now, storage is cheap.
Looking forward to shit storm once they introduce generics in near future. I am sure people will still complain that this is not how parametric polymorphism should be implemented.
Having been writing software over the 30 years that you mention, it's clear to me that Go has not been isolated from language design, it's just made a choice to stay simple. Java, C++ and Python have exceptions to keep you from littering your code with error handling, and code with exceptions is hard to read. The line of code you are looking at any point in time may instantly jump to another place, effectively, having "goto"'s evil cousin, the "comefrom". Somewhere, in your code, is a "comefrom" statement which runs immediately after the statement you are looking at, you're not aware of its existence. My Java/Python for production code ends up looking a lot like Go in terms of error handling, because you have the most context about an error at the point where it happens, so that is the best place to deal with it. I believe that Go 1.x will have the staying power of C.
Rust and Swift both manage to make error handling easy while keeping this nice "errors are just values" property. Go could easily have added Rust-like or Swift-like error handling backwards compatibly with their existing idioms without falling back on weird special cases of format strings, but have chosen not too.
This does not make the language simple (even though in other ways it is).
It's the same in golang, any line can panic.
Lisp is simple. Go is half-simple, but in its take on simplicity, it also takes away some constructs that make useful, high-leverage expressiveness impossible.
Some of its other benefits are appealing to me, but that's not one of them. It seems simple on its face only.
Exceptions and return values are both sub-optimal. Exceptions encourage drastic actions for non-drastic events (exit the program if an HTTP server is transitently slow). Return values encourage ignoring the error value, and then wondering why your program broke. Special types that wrap error values or exceptions cause the same problem; when you want to defer something, your code becomes contaminated with the error type (f(x) returns an error, g(x) calls f(x) but doesn't feel like dealing with the erro, so now g(x) returns an error... and all the way it goes up to the top level.)
Overall, I don't see a grand unified solution to this problem. We should make it possible for functions to declare everything that goes wrong so that recovery can be more easily tested. No language does this; they often merge vastly different errors into the same type, so the programmer is powerless to understand the possibilities. Consider two database errors; "syntax error" and "transaction aborted, retry it". Typed error systems typically condense that to a "database error", but how your program should handle the two cases are vastly different.
Anyway, I'm happy with the way go works. If I don't explicitly know how to handle an error, I wrap it with a tag and return it. When I look at the alert / error logs, I know which codepath caused the problem and can investigate. For cases where I know how to handle an error, I can explicitly deal with it (yes, often with strings.HasSuffix to find the one I know how to handle). That is all I really need. If it were an exception, the code would be basically the same. So I think it's a red herring to complain about values vs. exceptions. Neither system prevents you or encourages you to write correct, robust code. If we want to do that, we need completely new tools.
_But_ Rust also includes some really nice syntax for passing errors through so I can write this:
use failure::Error;
fn foo() -> Result<String, Error> {
may_return_an_error()?;
might_return_a_different_error()?;
if some_condition {
return Err(MyPackageError { detail: "..." })?;
}
Ok("foo")
}
The `failure::Error` type automagically wraps any error. That means I can go through many levels of my stack returning `failure::Error` and at any point in the call chain I can decide to examine the error type instead of writing `some_func()?;`, which would pass the error back.The only part I don't like is that I need to add a `?` when I return my own errors. I don't totally understand this but from what I sort of understand the reason is that `?` invokes `.into()` on the returned object, which is what lets me return a (wrapping) `failure::Error` instead of a `MyPackageError`.
The upshot of all this is that "ignoring" errors is quite easy, as long as _somewhere_ in my call chain I actually do handle the error. The type system will enforce this for me, as attempting to treat a `Result<String, Error>` as just a String will cause a compile time error. I have to unwrap it and either ignore the error (which would lead to a runtime panic if there _was_ an error) or do the right thing, which is to explicitly handle both the ok and error cases.
I'm working on a CLI program using this system, and I can bubble all the errors up to the main entry point of the CLI. At that point I can turn errors into a print to stderr and an appropriate exit code.
This is exactly opposite my experience. In 90% of cases there is no way to recover from an error locally and I should just fail at the highest level, possibly retrying a few times.
https://www.theatlantic.com/ideas/archive/2019/04/why-accide...
> Accidents happen. What he meant is that they must happen. Worse, according to Perrow, a humbling cautionary tale lurks in complicated systems: Our very attempts to stave off disaster by introducing safety systems ultimately increase the overall complexity of the systems, ensuring that some unpredictable outcome will rear its ugly head no matter what.
If you don't like it, don't present pale echoes of criticisms we've already seen (not listening to 30 years of language design...), tell us what you would do instead, because the answer is non-obvious and there is no one 'right thing'.
Over the decades as a programmer I have seen a lot of language come and go. A lot of languages suffer from "features". Where either a language is designed with advanced concepts getting either directly in the way of writing simple programs, or even worse, getting bolted on later on, creating something very different from the initial language.
I love the dependable simplicity of Go. It contains everything I need for a surprisingly large part of my work and not offering more than that is an asset. Few things in Go feel like they had been thrown in without proper consideration.
That's clearly not true - they've made plenty of mistakes.
If you like Go, you'll love the simplicity of Brainf-ck, a clean design which is easy to learn, simple syntax, and supported in almost all computing platforms.
So then why do you use it extensively?
There was a precursor to golang that the some of the same authors worked on before they were at Google. It didn't go anywhere, precisely because it didn't have Google's name behind it.
Source: I worked for Microsoft on C# projects where GC was constantly the bottleneck. Reducing number of exceptions (especially those used for control flow) helped.
It's better in many ways.
Firstly, rust has sum types, so it's possible to have exhaustive matches and know you've handled every case. This isn't possible in go. For comparison:
// go
val, err := doSomething()
switch err.(type) {
case *SomeErrorType:
// handle
default:
panic("inexhaustive error check (at runtime, only if that error type is hit)")
}
// rust
let (val, err) = do_something()
match err {
Err::SomeErrType(inner) => { /* handle */ },
}
// won't compile if the match isn't exhaustive
Note as well that you have to return the error interface, not some more specific type, in go because of the "nil struct is not a nil interface" gotcha. Juggling around structs that are returned as errors is basically impossible to do safely, so everyone returns the error interface. This is another way the language causes error handling to be worse.Next, generics in rust allow for nicer chaining of computation in the presence of errors. Let me show you two examples. Again, the go and rust code is as identical as I can make it:
// go
val1, err := computation1()
if err != nil {
return nil, err
}
val2, err := computation2(val1)
if err != nil {
return nil, err
}
return computation3(val2)
// equivalent rust
computation1().and_then(computation2).and_then(computation3)
// also equivalent rust
let val1 = computation1()?;
let val2 = computation2(val1)?;
computation3(val2)
The ability to have a generic 'Result<T, E>' type to chain computation allows for code to be more readable, while still having all the benefits of errors being values.The ability to have a generic 'Option<T>' instead of 'nil' also is very helpful, but enough has already been written about null pointers that I don't wish to rehash it here.
Finally, in practice, rust's higher level features (namely macros) allows libraries to create very powerful developer abstractions around errors, like those offered in the 'failures' crate, all without having any runtime overhead.
In practice, all of these things also combine to result in libraries offering better error types and allowing callers to handle errors well.
Of course, any humans are making mistakes in the process nevertheless, but overall I think they got it pretty much right.
Having said that, I would be curious what you have in mind when saying that they made plenty of mistakes?
But frankly, just the design of append() alone should be a warning sign. If it were a low-level facility, fine, it's only needed to be understood by whoever's writing a library to wrap it for general consumption. But it's the idiomatic Go way of maintaining a sequential mutable collection! How is such a basic operation is so non-obvious that people still write articles to explain it, and so verbose that you must reference the collection twice to make it work right?
(Indeed, the language itself kinda acknowledges how clumsy it is, by making it an error to ignore the return value of append(). But, as usual with Go, it's hardcoded - i.e. you can't do the same for your own function. And IIRC, it wasn't there from the get go, but got added somewhere along the line.)
What is your issue with GOPATH, that you call it a big one?
I'm an experienced C developer.
>which keeps a lot of virtues of C
No, C doesn't have many virtues. It's major virtue was getting close to being a macro assembler, and Go removes that completely. Go tries to be a higher-level-than-C language with good execution speed, but that goal was already achieved perfectly by Object Pascal, D, Ada, and Common Lisp.
My point is, a lot of good error handling is built at the lowest level, close to the point of failure. That code knows what the problems are and how to fix them. Errors are not the exception, they are the rule. If you handle it well, people won't even know how unreliable the underlying system is. (NAND flash? The first time I used raw NAND flash I was blown away at how unreliable it was. How do computers even work at all like this, I thought. Then I realized... that's why so much money is poured into things like SSD controllers. To hide that fact from the user and make them happy, even if the raw technology it's built from doesn't offer perfect reliability.)
This is very true for some types of errors and for some types of programs but not true at all for others, which is why this debate has been going on and on for decades.
An extremely common example for the latter is programs that touch a DBMS or file system on every other line. You don't want to see error handling code for "database is gone" or "local disk is gone" events all over the place.
The information required to handle these errors just doesn't exist in the local context. So the only reasonable question is how to let those errors bubble up to some central location where you can handle them or decide to abort.
In my opinion there are good arguments for and against both exceptions and error returns. But if error returns are used, then there must be some reasonably ergonomic way to do it, i.e not what Go does right now.
Look at all the monad tutorials for Haskell. Nobody knows how to use the special syntax for Maybe and Either. They read hundreds of articles and still don't get it. Meanwhile, everyone understands how "if err != nil" works, perhaps too well, which is why they complain about it.
Do you ever see golang code that handles errors returned from fmt.Println?
It’s very easy and not very useful to come up with vague criticisms based on generalities, it’s much harder, but infinitely more fertile and interesting, to come up with a coherent thesis as to what should be done. The OP posited a right way, I’m curious as to which of the right ways they mean.
Also discussion requires some content, not empty complaints without any backup. For example propose a different mechanism for reporting errors, like optional types or exceptions (both have potential problems). The OP would be more interesting if it actually tried to engage with the problem.
return errors.Wrap(err, "read failed")Personally, I don't care for "string magic"
Edit: Instead of making another post, I'll add this here. It also feels odd that concerns about xerrors.As possibly panicking at runtime are considered addressed by the addition of a go vet check: https://github.com/golang/go/issues/29934#issuecomment-46252...
I am not opposed to go vet, to be clear, but one of the aspects of the go compiler is that it does not warn, only errors out if compilation can't proceed. But the design of xerrors.As is fine because go vet acts like a compiler warning might? This is an aesthetic complaint more than anything else, but it still doesn't sit right with me.
I suppose it won't take long for a component of one of the gometalinter or golang-ci or whatever to develop a "Errorf used without %w in the final position" warning, which will be good enough for me, but it would be better to have something like Wrap officially, IMHO.
Re: exceptions, there are a lot of people, myself included, who do not view exceptions as "advancements", but as setbacks. Magically and suddenly subverting the normal control flow and unwinding the stack in highly concurrent programs (as is expected in Go) is an awful way to do error handling, and you end up having to bypass that mechanism and pass the errors as values across call stacks anyway, so what are you really gaining for all the extra complexity, "implicitness", and cognitive load of doing that?
Except that you cannot easily chain calls that return errors, or it isn't really that hard to accidentally ignore errors because of shadowing or overwriting the variable you're storing your errors in. Or the fact that using a union type to represent errors is a strictly superior way, both in terms of usability, as well as correctness. People should just face the fact that returning errors as a product type is a mistake.
> Magically and suddenly subverting the normal control flow and unwinding the stack in highly concurrent programs (as is expected in Go)...
You may want to see what approaches Akka or Erlang takes here. golang authors just decided to ignore established practices and use clunky approaches to problems that have already been solved.
These are still essentially the same. Errors are returned as values, with no major difference in runtime semantics. Whether the language supports union types is orthogonal to that.
> You may want to see what approaches Akka or Erlang takes here. golang authors just decided to ignore established practices and use clunky approaches to problems that have already been solved.
There's not one solution, there are multiple solutions with various tradeoffs. The tradeoffs Go made are congruent with its tenets as a language that values simplicity and low cognitive overhead.
Not really, it's very annoying to chain calls which can error. Also, as another commenter mentioned, functions which can potentially fail should return sum types and not product types.
Compare:
do first <- computeFirst
second <- computeSecond
return $ f first second
and first, err := computeFirst()
if err != nil {
return nil, err
}
second, err := computeSecond()
if err != nil {
return nil, err
}
return f(first, second), nil
Even without do-notation the Haskell is considerably shorter: computeFirst >>= (\first ->
computeSecond >>= (\second ->
return $ f first second))
Agreed about exceptions, though; I dislike them as well.This is a great example, not to your point, but to the methodology of Go. You give a verbose Go example, that I expect the vast majority of readers here could understand, even if they've never written a single line of Go, followed by a terse but syntax-heavy Haskell example that I expect relatively few could.
I'm also assuming that `f first second` will return (an error monad?) if either of `first` or `second` are (an error monad?) - is that right?
It becomes even sillier when you have to also signal "not found" with something like `(a, bool, error)` instead of `Found a | NotFound | Error error`
The Go team's "Draft Design" (with check/handle keywords) somehow omitted a requirements section...
https://gist.github.com/networkimprov/961c9caa2631ad3b95413f...
For example its CSP concurrency model, similar to Go, was hardly pertinent when common processors were 150 MHz Pentiums with 1 core. Completely different scenarios.
Not to mention Bell Labs had enormous influence on Computer Science at the time so it's not like Limbo had no strong backing either.
There was always demand for having servers that processed high numbers of requests (e.g. C10K). Just because single core processing was common does not mean that there wasn't need for high concurrency.
> Not to mention Bell Labs had enormous influence on Computer Science at the time so it's not like Limbo had no strong backing either.
In those days there was less fad driven development compared to what we see today. So the effect wasn't as pronounced.
Demand is not binary. In 1995 the demand for concurrency was a fraction what it is today. Not only multicore processing was an extremely rare sighting in comparison to what we have today but also:
1. Internet was accessed by 10% of the population vs today's 80%+
2. Capable smartphones? First iPhone came only 12 years later. There was no such thing as internet during commute.
3. C10K for example was coined only in 1999.
The 20 years between 1995 and 2015 did change the IT landscape wildly regardless of your beliefs.
return is a bit of a misnomer in Haskell -- it really means "wrap this value in the monad supplied by the context." So as other commenters have mentioned, f(...) cannot fail. Non-monadic Haskell functions never use return.
If f could fail and you indeed wanted to propagate its error if it did fail, you could write the code as
do first <- computeFirst
second <- computeSecond
f first secondDart struggled for years to find a killer app, though Flutter now seems to be a good bet.
Originally the idea was to replace JavaScript and everyone except TypeScript pretty much failed at that. (I mean failed from a popularity point of view, they often succeeded technically.)
I'll also point out that coming from Google is kind of a mixed blessing these days, from a popularity standpoint.
Also since Go is "hypocritically Object Oriented", people can claim it's not an object oriented language and kind of write Go like classic structured programming such as C. But Go is OO. You can't write go without using interfaces for I/O.
You can and people do that all the time. You don't have to use its standard library I/O APIs, even its syscall package.
I'm not disagreeing that Go is OO though. Its ecosystem is dominated by OO. But it's more like Perl in this regard, where you can spend years without writing a single line of OO code yourself even if you have to use other people's OO code.
f(computeFirst(), computeSecond())
which is what a maintainer needs to focus on. We know everything can fail, we don't need to be incessantly reminded of that.If completely inexperienced people can read idiomatic code, that means the idioms don't capture anything tricky that had to be learned the hard way. There's no payoff for getting better with the language.
Just because multicore wasn't common doesn't mean that concurrency wasn't important. Event loops have practically always been there on widely used OS's.
It's not just about runtime semantics. If it were, then exceptions should perform better than returned error values in the non-error case (which should be the majority of the time anyway). It's also about how code gets written, and more importantly, how code is read.
> There's not one solution, there are multiple solutions with various tradeoffs. The tradeoffs Go made are congruent with its tenets as a language that values simplicity and low cognitive overhead.
Which in practice, doesn't really show. Simplicity at the language level manifests as longer, more complicated code in real designs, because real life is complicated. It's pushing the load from the language and compiler implementors on to the end user.
And I have to say, the debate around Go error handling does remind me a fair bit of some of the arguments I've read while researching that ancient debate about structured programming - needless abstraction that we're not even sure is right, it's clearer when it's explicit, language is simpler etc.
This distinction is growing popular in general, including languages that have exceptions (e.g. FailFast in C#) and error types (e.g. panic in Rust).
I don't know what to say about native binaries, when a Go's "hello world" app is as big as an entire os[1].
Perhaps I'll upset some, but IMO Go would be another obscure language that no one cared about if it didn't come from Google.
Sure, opinions differ, the point is that it's far from clear that Java etc. are superior in every way, certainly for some use cases.
> I don't know what to say about native binaries, when a Go's "hello world" app is as big as an entire os
Is that OS written in Java or C#, because that's what the discussion was about.
Also, CSP is not the only way of doing concurrency in Go. The standard shared variable model with mutexes is supported as well, just not preffered.
Also, size seems like an odd complaint to me in today's day of cheap disk space. On the other hand, simple deployment, no VM startup time and fast compilation speed do offer real advantages for some.
Once Java gets fibers, its concurrency offerings will be a strict superset of golang's. golang doesn't even offer event based async concurrency.
> Also, CSP is not the only way of doing concurrency in Go. The standard shared variable model with mutexes is supported as well, just not preffered.
golang doesn't even have concurrent data structures. So it's either CSP or mutexes, both not ideal for high performance code.
Java application servers have been offering this for a long time now. Just drop in a .war file and your code is running. No need to restart, and the .war file has a small size.
The people who worked on golang also worked on another similar language called limbo before they were at google. You can guess where that one ended up.
That being said, both Java and C# can compile to native binaries. Java is getting Fibers, basically JVM managed lightweight threads, and has a strictly superior concurrency library in the form of `java.util.concurrent`. Not to mention libraries like Vertx and Akka for concurrency and actor systems.
- memory consumption, never wondered why you never see Kubernetes sidecar / daemonset in Java / C#? because they use 5-10x the Go memory, no thank you using -xmx -xms 512MB for a simple API server.
- the billions GC settings that you need to try to make something work at scale ( hello Elasticsearch )
- Don't need 200MB of library to open a file or create a REST server
- maven / graddle build system that are completely bloated, in Go if you have your vendor folder checked in ( and you should ) you just do go build . and you have your single binary
- 50 line stack trace that tells nothing
- observability imo is better in Go, it's getting better with Oracle adding stuff into OpenJDK, but it was a pain before without paying ( jvisual vm, mission control ect .. )
I worked with Java for many years and I can tell you that Go is a breeze of fresh air, it's not perfect but it's good for what it was designed.
Another strawman. There are many other frameworks other than Spring that are simple and performant and easy to deal with.
> - memory consumption, never wondered why you never see Kubernetes sidecar / daemonset in Java / C#? because they use 5-10x the Go memory, no thank you using -xmx -xms 512MB for a simple API server.
No one is requiring you to use -Xmx 512MB. The JVM will take up as much memory as you give it. And newer GCs will release unnused memory back to the OS much more aggressively.
> - the billions GC settings that you need to try to make something work at scale ( hello Elasticsearch )
golang barely has any settings, which completely limits how it can be used. If you want to tune your code for throughput instead of latency, well you can't. The JVM gives you this option, and even moreso with ZGC and Shanendoah (TBs of heaps with ~1ms max latency). golang's gc can't even approach that.
> - Don't need 200MB of library to open a file or create a REST server
That's quite ridiculous. You don't even need any dependencies to open a file. And not all JVM web frameworks are Spring. There are many light weight alternatives.
> - maven / graddle build system that are completely bloated, in Go if you have your vendor folder checked in ( and you should ) you just do go build . and you have your single binary
It depends on how you configure them. golang's dependency management is non-existent, so no real comparison there.
> - 50 line stack trace that tells nothing
On the contrary, this is one of the biggest advantages of using the JVM or .NET. You get a stack trace telling you exactly where things happened. Compare that to golang where you get a single string and you have no idea which code path was taken to reach that error.
> - observability imo is better in Go, it's getting better with Oracle adding stuff into OpenJDK, but it was a pain before without paying ( jvisual vm, mission control ect .. )
The JVM is the most superior platform for metrics, measurement, and monitoring. This is an established fact, and anyone claiming otherwise shows they don't have experience in this area.
that is decision of the project developer, it seems really nice at first when project is small, then it becomes a nightmare to manage, but spring is not really part of a language, but a framework. Go is still fairly young so it doesn't have "enterprisy" tolls :)
> - memory consumption, never wondered why you never see Kubernetes sidecar / daemonset in Java / C#? because they use 5-10x the Go memory, no thank you using -xmx -xms 512MB for a simple API server.
fair point, memory consumption is a huge problem but I believe it's because people over years are adding various dependencies and build on top of them. I remember when for one project I wanted to graph a dependency try using IntelliJ, and it was just crashing the IDE each time. I didn't work with C# and don't know how much of this applies to it, but I think major reason for not being used is that it primarily targets Windows platform. Also you see a lot of Go code around Kubernetes, because Kubernetes also originates from Google.
As for "-xmx -xms 512MB" this is because that the code runs on a VM, if you would compile it to a native language this shouldn't be needed AFAIK.
> - the billions GC settings that you need to try to make something work at scale ( hello Elasticsearch )
that's because of the VM
> - Don't need 200MB of library to open a file or create a REST server
This is false, you also don't need a 200MB library, that functionality is included in the language, it's just that maybe that library is nicer to use. Go is younger so it had opportunity to learn from other language's mistakes and designed its library based on lessons learned, it also did not have need to evolve together with the protocol.
> - maven / graddle build system that are completely bloated, in Go if you have your vendor folder checked in ( and you should ) you just do go build . and you have your single binary
that's because it is young, it doesn't really have any real dependency tracking yet (well... starting with 1.11 modules were introduced) but that's still rudimentary
> - 50 line stack trace that tells nothing
it's different error handling, in Go on the other hand you might not get any error just SIGSEGV, creating a reliable stack trace requires work from the developer
> - observability imo is better in Go, it's getting better with Oracle adding stuff into OpenJDK, but it was a pain before without paying ( jvisual vm, mission control ect .. )
I'm not sure what tooling are you talking about?
> > - the billions GC settings that you need to try to make something work at scale ( hello Elasticsearch )
> that's because of the VM
It's not even that. Python has a VM. However, the JVM (at least up to version 8) was tuned for throughput at the expense of latency by default. Starting with Java 9 I believe, the G1GC has become default, and there are new GCs being worked on right now for even lower latencies for huge heaps (in the TB range). golang would not even fair close in such applications because of its limited gc.
Kotlin does that today and I like Kotlin, however the fact is that the class-everywhere Java approach just doesn't sync with me. Go's and Rust approach to OO with value-based types (structs) suits me much better.
> golang doesn't even have concurrent data structures
Not strictly true. It does have sync.Map There's also 3rd party packages offering this.
Kotlin's coroutines are still not like fibers (it's still affected by: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...)
Java is getting record types as well. That being said, one is free to use whatever JVM language they like and still get the huge benefits of the JVM, regardless of the language.
> Not strictly true. It does have sync.Map There's also 3rd party packages offering this.
Which still uses locks behind the scenes. Java's concurrent structures are lockless in general (lockless maps, lockless queues, channels, etc.). Not to mention casting to and from interface{} which is error prone and very tedious and verbose.
Agreed, but Java's generics are not the best either. When I am looking for an advanced type system in this space I look at Rust, not Java.
And generics constitute just one part of a type system. You have languages like Scala if you're looking for a language with a more advanced type system than Rust's, and it also runs on the JVM.