This (it looks like to me) is an attempt to pull in the best bits of Dave Cheney's errors package (which I love) into the standard library: https://github.com/pkg/errors
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.
Also note that the library checks for `: %w` at the end of the format string to enable this wrapping feature. If you use "%w" anywhere else in the format string it won't work.
The %w (or %v) identifier must be at the end for the magic to work, but you can still meaningfully format things in front of the wrapped error, e.g. fmt.Errorf("Write(%s): %w", filename, err)
+1 on all of this. I really prefer that https://github.com/pkg/errors is outside of the standard library so I don't need to use the fmt.Errorf abomination.
IMHO they should either just adopt Dave Cheney's errors package (https://github.com/pkg/errors) into the standard library or leave it as it is becoming the de-facto standard.
A bit offtopic how does pkg/errors compare to https://github.com/juju/errors/? I see the former is much more popular but the later seem to be nicer, I especially like the deferred annotations, since it's easy to use them to include function arguments in the stack trace.
It seems like golang designers are totally isolated from what's been happening in the last 30 years in languages design. They still insist on their weird way of error handling just like they were stubborn for years and years on the lack of package management and eventually a very weird and rudimentary way of it. It's sad because I use this language extensively but its weirdly mediocre design is totally unfathomable. It's like they are very stubborn to do anything but the right thing.
I've been using Go heavily over the last five years, starting with 1.2. Whatever warts the language has in its design, the error handling being one, it's still an excellent language for getting work done quickly, and correctly. I'm far more productive in it than Java, C++, or Python, and I'm old enough to have done lots of Pascal, Lisp, and more scripting dialects than I can count.
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.
> 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,
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).
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.
Aren't you supposed to check all the possible errors in golang anyway? How's that any different than java code being littered with error handling code?
Except it's possible to miss checking errors in golang (accidentally either by not assigning the return value, or by overwriting a previously assigned error). Whereas in a language with exceptions, this is not possible unless by explicitly adding code to ignore exceptions.
Do you ever see golang code that handles errors returned from fmt.Println?
I think I agree with you - I'm just trying to understand the other perspective from someone that seems to think not having exceptions is an improvement. I've mostly worked using java and go and to be honest I think I prefer exceptions because you should (almost) always check error codes in go anyway, and if you forget, you just made debugging much more difficult.
I keep checking in on Go, trying to decide if I want to add it to my toolkit, but this argument usually bugs me.
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.
My takeaway from the last 30 years in computer science is that errors are not exceptional. They occur often and should be accounted for near the code that generates the errors.
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.
Rust's type system + the failure crate (https://crates.io/crates/failure) is the nicest I've seen. It's similar to Haskell in that errors are part of the return value of a function, and the type system enforces handling of this.
_But_ Rust also includes some really nice syntax for passing errors through so I can write this:
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.
The two are closely related concepts. You can model checked exceptions as a secondary return type, except without the same first-order representation (e.g. inability to specify checked exceptions in generic terms).
> My takeaway from the last 30 years in computer science is that errors are not exceptional. They occur often and should be accounted for near the code that generates the errors.
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.
If that's correct, then our systems are built on false assumptions. SSDs should not see an ECC error and write the data to a new block; that error should kill your program. TCP should not detect packet loss and retransmit, it should kill your program?
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.)
>My point is, a lot of good error handling is built at the lowest level, close to the point of failure.
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.
I just don't agree with adding more syntax to the language for what basically amounts to an if statement. If it's a conditional, which it is, just use an if statement.
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.
> My takeaway from the last 30 years in computer science is that errors are not exceptional. They occur often and should be accounted for near the code that generates the errors... We should make it possible for functions to declare everything that goes wrong so that recovery can be more easily tested.
> 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'.
What a ridiculous way to think about language design. The users of a language are best suited to define what parts of the language do not feel good to them - why are they expected to come up with solutions for the language designers? In your mind, are you only allowed to present criticism if you're also able to define solutions? Doesn't that limit the people that are allowed to criticize to the language designers themselves? That seems like an excellent way to get tools nobody wanted, no?
Language design has nothing to do with discussions on HN (at least I hope not!).
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.
There are different kinds of discussion, I don't think a discussion site is the right forum for design, do you?
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.
And I am so happy that they won't do anything but the 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.
My statement was obviously inverting the statement the poster made I answered to and was meant abstractly. They did very hard try to only do the right thing and did not include a feature just for the sake of having that feature.
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?
No immutability. Nullability by default, and weird corner cases like nil slices and interfaces. Language semantics requiring weird stack discipline that is incompatible with normal ABIs, and causes overhead when you need to interop.
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.)
Besides never being an issue for me, GOPATH isn't strictly speaking a langauge issue, but an issue of the build environment, which will no longer be the default with Go 1.13.
What is your issue with GOPATH, that you call it a big one?
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.
Sorry, I don't see how your comment contributes to the discussion. You have heard of C? A language which most of the Unix infrastructure is built upon?
It turns out, Go is a language, which keeps a lot of virtues of C, adding just enough to it to fix some of the shortcomings of C, adding more safety and convenience.
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.
The brainfuck quip is a bit more potent when the Go advocate in question has made it clear that they think language simplicity and code simplicity are equivalent. That didn't happen here, but it's tempting to assume that's what the advocate is thinking when they praise language simplicity so much.
Go's error handling is essentially the same as Error/Either/StatusOr (returning errors as values).
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?
> Go's error handling is essentially the same as Error/Either/StatusOr (returning errors as values).
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.
> 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.
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.
> 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.
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.
They're essentially the same, in a sense that if/goto is essentially the same as a loop. In practice, there's a big pragmatic difference.
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.
golang just brought these arguments back, and they're ending up reinventing most of what's been done, but in a subpar way (e.g. code gen instead of generics, verbose error handling + panics instead of exceptions, etc.).
FWIW I don't think panics are a bad idea necessarily, they're just very different from errors. Errors are part of the API contract (whether enforced by the language or not). Panics are for when the contract is broken on either side, or expected invariants suddenly don't hold - the reason being that if your basic guarantees about process state are broken, you can't really guarantee that you'll be able to handle the error either, and trying to do so regardless might result in a security issue.
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).
> Go's error handling is essentially the same as Error/Either/StatusOr (returning errors as values).
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
> Even without do-notation the Haskell is considerably shorter
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.
The Go version is 78% useless noise trying to hide
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.
Apologies, I know very little Haskell - does that example mean that `computeSecond` will always be called, even if `computeFirst` failed? If it does, it's not the same as the Go code which will not call `computeSecond` in that case (which might be a requirement - who knows?)
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?
In the error monad above, computeSecond will not be called if computeFirst failed. But in other monads (which have the same syntax) computeSecond MAY be called depending on the rules of composition.
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 second
It will not, because code inside a do-block is sequenced. The value `first` in the line `first <-computeFirst` is a non-error value. If computeFirst fails, the value does not exist (because computeFirst must return either an error or a non-error value), and so the whole computation fails.
This version of f can't fail, so "return" wraps its value in the same monad. Normally the "do" block would end by getting a monad from f, just as the Go version would normally let f return an error.
That's a stawman fallacy. There is no perfect language. However, there are languages that almost are strictly superior to others. Java and C# in this case are almost strictly superior to golang in almost every front.
I wouldn't necessarily call Go's concurrency model superior, it seem like that initially but after all hype died out a bit it has issues. It essentially just offers one way to do concurrency. That might fit really well for some problems, not so much for others.
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.
> I wouldn't necessarily call Go's concurrency model superior
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.
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.
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.
It's not an either-or situation. Java's generics have their advantages (I'm assuming you're referring to type erasure). Just look at the number of languages implemented on top of the JVM to see what I'm talking about. Type erasure made inter-op much easier between languages implemented on the JVM. That being said, there are upcoming improvements to generics in Java and the JVM (e.g. JEP 218).
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.
> On the other hand, simple deployment, no VM startup time and fast compilation speed do offer real advantages for some.
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.
> but IMO Go would be another obscure language that no one cared about if it didn't come from Google.
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.
They're not in the same market. Dart faced competition from TypeScript (same designer as C#, and also backed by Microsoft), and the latter seems to have won so far.
Compiling to a native binary is not a strict advantage, there are many advantages for running in a managed environment, and it can be argued that for critical systems it is the superior route in fact (given that you have the resources to run said VMs, which unless you're doing embedded or resource constrained systems, is a non-issue).
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.
- simplicity, never worked on those projects using layers of layers with a lot of magic ( like Spring )
- 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.
> - simplicity, never worked on those projects using layers of layers with a lot of magic ( like Spring )
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.
> - simplicity, never worked on those projects using layers of layers with a lot of magic ( like Spring )
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 .. )
It's a misconception that you need a lot of memory to run a Java app. Just for example:h ttps://news.ycombinator.com/item?id=19728329
> > - 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.
I mostly write Haskell professionally, but I still reach for Go depending on the project. Sometimes it just has the libraries I need and I can tell up front that I'm willing to deal with the tedium the language forces on me. Cost/benefit analysis.
I've heard great points and experienced myself how utterly legible go is. But nobody on earth has ever praised anything about the language design itself. I wonder if go came from somewhere not google- how would it fare.
It's kind of irrelevant since it's a different team with a different software niche and design aesthetic, but since you ask:
Dart 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.
Dart was born "for the wrong reasons" AKA replacing Javascript, it failed at it. The language itself is quite good, better than Go IMHO, but Go has the advantage of not requiring a separate virtual machine. The problem with languages is whether they get enough momentum so that a community can be built around them. A language without an extensive ecosystem is nothing.
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'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.
Except that TypeScript's designer had strong and established experience in language design, and he made a lot of correct choices when implementing TypeScript. Can't say that about golang.
I'm not sure if you're casting aspersions on Rob Pike and Ken Thompson.
But they are truly giants of computer science giants and they are behind golang.
Can't say that about typescript (or any other language I can think of).
You could argue that there's a bit of a network effect involved. i.e. it doesn't get much love on HN, Reddit, etc. (just look at the comments on any Flutter thread).
> I wonder if go came from somewhere not google- how would it fare.
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.
Ever considered that in 1995 some aspects of Limbo weren't as appealing as they are today and that this played a great deal in its adoption?
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.
> 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.
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.
> There was always demand for having servers that processed high numbers of requests (e.g. C10K)
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.
When did Erlang come out? There clearly was interest in concurrency, otherwise, why make a new programming language (limbo or otherwise) just for the sake of it?
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.
From what I have seen, the Go 2 Error Values plan[1] has not received enough exposure to generate the level of feedback necessary to support a go/no-go decision re a major change in 1.13 -- one we cannot opt-out of, at that.
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.
As others have stated, this seems incredibly odd to me:
> 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.
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.
My apologies, I seem to have confused the two packages. I don't see it in the godoc either. In which case I echo your desire to have an explicit method of formatting and wrapping.
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.
Yes, it's a magic string hack. On the other hand, it's concise and easily learned. This is kind of the opposite of the "if err!=nil" dance that many people complain about.
One of the things I picked up from Dave Winer around 20 years ago is that errors messages should be of the form "Can't X because Y", and while I don't quite always use that exact grammar form, my errors pretty much all take that form when there isn't some other overriding local standard for errors. It's not a bad template. Most error messages will give you some hint about the Y, but the difference between "Error: file system full" and "Error: couldn't open log file '/var/log/myapp.log' because: file system full" is pretty substantial, especially if system state has mutated in the meantime and by the time you get to the system, none of the filesystems are full anymore.
Descriptive errors that match the business logic are ideal in many cases. Clearly, one shouldn't don't divulge "secret" validators.
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.
This is the general strategy when developing "2.0": do as much as possible in a backwards compatible way, and if we never need to make a breaking change, never call a release 2.0. The new features process that was introduced recently may result in a 2.0 release, or it may result in 1.999, so you will continue to see new features in each release for a while regardless of what the release is called.
The biggest issue I have with most error messages is that they say things like you have here but don't reflect back the data that caused the error, i.e. show me the phone number you think I sent you.
I think this context is really hard to carry around, and if you aren’t extra super careful you can end up leaking passwords and other secret bits of memory here to logs. Better to just give me a place to put a debugger breakpoint IMO.
I agree with the feeling, but I also see this being a debate between an opt-in approach (choose to use an error value with gradually more information enclosed) vs opt-out (throw an Exception, a la C#/Java, that by default stores everything you may or may not need, then maybe figure out a way to stop storing what you don't need). The use cases are not going to magically go away though, which explains why we need the solution to be somewhere in the middle.
> then maybe figure out a way to stop storing what you don't need
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.
Exactly. Plus, code that uses exceptions but does not encounter any throws should be faster than code that uses error checks. In the latter, there's always a cost even if there aren't any errors returned. However, the former can optimize for the (hopefully more common path) of no exceptions being thrown and avoid checks altogether.
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.
Weirdly enough this perspective might suggest that the problem is not actually with Go, but with object oriented programming. Which wasn't considered thoroughly by original authors, because they were not OO programmers.
No other paradigm appears to be able to handle the complexity required to build the systems of today. Objects are very natural way to organize complexity. It's what let UNIX succeed where Multics failed.
Not sure why people are bashing Go because of it's choices.
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.
I think this is a positive development and should be cheered by Go programmers. Slowly but surely they progress on the road to implementing Exceptions. (ducks and runs like a scaredy cat to avoid the wrath of Go Fans)
What I'm really looking for is being able to debug any production Go binary by inspecting the stack trace behind an error. Could be gated by an environment variable.
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.