242 comments

[ 4.7 ms ] story [ 256 ms ] thread
Given the now two interfaces, Unwrap() error and Unwrap() []error I really wish they had just done only the latter since the beginning. Continuing to have and support the prior feels like a mistake somewhat permanently baked into the language.

I know it is easy to armchair second guess but one of the very first things I tried when they added wrapping a couple versions ago was wrapping multiple errors. I am reasonably certain the majority of the pre-native library error wrapping packages supported multiple errors as well. The desire seems reasonably obvious.

Something about needing to use fmt.Errorf to JOIN errors if you want custom string formatting rubs me the wrong way. I had to read that section twice because it completely went over my head it acting as a join.. So unexpected for fmt to be doing error biz like that.
Especially given the current landscape of log handling as the author points out.

I am all in on structured logging most of the time and I don't want to have to deal with a newline causing multiple records in my log aggregation.

It's also not true the interface for Unwrap() doesn't exist; behold https://cs.opensource.google/go/go/+/refs/tags/go1.19.4:src/... ...

Yeah, it's not exported but...

In the blogpost I specifically wrote "There is no (named) interface in the standard library..." because I was aware of the function you point out. Rather than unexported interface, I would probably call that an "anonymous interface".

Anyway, now that I read that, I should probably skip this fact and just keep the example interface. It is not uncommon practice in Go to make "copies" of interfaces to avoid extra dependency (or cyclic dependency).

Sorry, that shot right past me. The article sounded, to me anyway, impressed the interface didn't exist. Seeing the cast/check against the anonymous interface left me unimpressed haha.

> It is not uncommon practice in Go to make "copies" of interfaces to avoid extra dependency (or cyclic dependency).

I hadn't really noticed but my exposure to the source is spotty. See what they are doing there and can imagine why, it just irks me.

There are now implicit, "standard" shapes for Error that don't have named, exported interfaces and presumably anyone wanting to write code around both Unwrap shapes will need to reach for the same techniques to suss out what they are dealing with..

> needing to use fmt.Errorf to JOIN errors if you want custom string formatting

You don't need to, you can implement Unwrap yourself and format the error however you want, just like singular errors today.

> unexpected for fmt to be doing error biz like that.

IIRC, this has to do with avoid circular package dependencies. fmt can depend on errors (and quite a lot of other stuff) but errors can't depend on fmt, so either a) errors needs to implement its own format string processing, b) a wholly new errfmt package is introduced, c) fmt.Errorf.

> You don't need to, you can implement Unwrap yourself and format the error however you want, just like singular errors today.

Sure, but when discussing the language stdlib design being able to reimplement myself isn't particularly germane. It's what is provided that's being criticized.

> IIRC, this has to do with avoid circular package..

The journey to the bad design is interesting, but here we are haha.

IMHO it's weird (poor design) to have fmt as the place to join errors if we want to customize the string.

The article tries to make the case that errors ARE just strings, but I don't buy what it's selling. The type checks in Is and interface casting in Unwrap are my counter arguments.

Years and years of discussions about Go's poor and verbose error handling, with multiple sensible proposals being rejected, and this is the improvement that finally makes it into a release?

What Go demonstrates above all else is how successful a poorly designed language can be if it has a great compiler, an amazing standard library, and high-quality official tooling. Many "features" of Go are borderline insane (date formatting, lack of enums, implicit interfaces, ...) and Go repeats design mistakes that were recognized as mistakes decades ago (nil pointers), but all of that is happily overlooked in practice because huge codebases compile in three seconds, you don't have to argue about which formatter configuration to use, and everything from TLS to CSV is rolled into std without ever having to import a third-party repository.

I’m glad it’s not just me who is perplexed at some of the decisions made with golang.

This was a golden opportunity to implement some typical form of exception handling. Instead you have to bundle your errors together and then unwrap them (often using multiple methods to handle the cases of both a single error and a bundle of errors) and then still implement even more logic to walk the tree you made?

Exception handling sucks, stop asking for it.

Did you worked in Java? Did you worked with async and exceptions?

Where did the poster ask for exceptions?

And golang has them in the form of panics and they suck in goroutines.

> This was a golden opportunity to implement some typical form of exception handling.
I work in Java every day and I'll take exceptions any day over... that. I have a lot of respect for the engineering work the Go team put into the compiler, toolchain and stdlib, it's truly commendable. But the language itself is frankly a step back form everything else on the market today.

Java, C#, hell, even python are better designed languages than Go.

When it comes to error handling I rank things along a spectrum:

Enumerated Types > Checked Exceptions > Error values > Unchecked Exceptions.

The spectrum I'm ranking on is how much assistance the compiler gives me in ensuring I am aware of the error. Java has support for the second best option but culturally avoids it like the plague. Go has support for the third best option and culturally leans hard into it. C# and Python are ranked strictly at the bottom.

The preference in many language communities for an error handling path that encourages little awareness of the errors that can occur reeks of laziness to me. Laziness that I will, without a doubt, fall victim too without superhuman amounts of diligence on my part to avoid.

Any problem that requires superhuman diligence just screams out for a robot to handle in my experience. Java, C#, Python and a myriad of other languages fail hard here. Go gets a little better culturally by focusing on checkers that are built into the compiler toolchain with a default to running culturally as well as having type a signature for return values clearly indicate the error. But the gold standard for error handling has to go to any language that has enumerated types. Rust, ML variants such as OCaml or Haskell as common examples.

The problem with GoLang is that they have invented something worse than exceptions.

If I want to read a json file, I have to have a named variable for the file, one for the byte string, and finally one for the parsed object (the only thing I care about).

Then for each statement I have to check if an error happened and return it if it did.

Whereas in something like Haskell I could use a Maybe and compose the functions to only forward the value at each step if it was present. Then at the end I would have a Maybe with the final value, which I could then return to the caller.

Golang makes the most common thing to do as verbose as the least common thing.

The biggest mystery to me is how poorly designed Go is considering its illustrious creators.

Thompson and Pike are living legends. They had absolutely nothing left to prove. Yet they came together for another major project, backed by the most powerful technology corporation in the world, and they produced... this?

Go feels like a random hodgepodge of ideas from three or four computer science undergraduates – not like the creation of engineering superstars with a century of experience between them.

It really does feel straight out of a compilers textbook... including the suggestion to think of and add new keywords and features just for the sake of exercise.
The mistake was designing around "simplicity" - a nebulous, unattainable goal. It's sort of like designing around "happiness" - who is to say what happiness is? Is happiness satisfaction? Ecstasy? Fulfillment? Does it require some sadness?

How nebulous it is is really well demonstrated in Pike's talk[0] where he somehow believes that a for loop is "simpler" than methods like "filter" and "map", and makes the very strange and incorrect claim that it's going to be faster as well (I suspect he was thinking "in Go" because those would be slower, given a lack of generics).

A lot of Go's design philosophy is, very bluntly, directed to bad programmers. For example, Pike talks about how a developer using a language with many features might spend 30 minutes just iterating on a few lines of code, because there are so many features available to use within that code. That is a very junior level problem - junior level programmers aren't very good at understanding that there's low return on investment for that sort of work so they coo over fancy code that works exactly as well as boring code.

Similarly, junior developers have a hard time learning features, so they have a hard time reading code that uses them and Go puts readability above all else. A senior developer should surely hear "filter and map are complicated?" and scoff but a junior developer would go "I have no idea what those are".

And there are significant constraints because of that lack of features - years of not having generics because it's too complex, for example.

We need junior developers to be able to approach new code but we also need to give powerful tools to the engineers who understand how to use them properly, especially when they're needed most.

This is why I think that Go is a bad language, but not just because it's missing some feature X or Y or Z, but because it is effectively an attempt to create simplicity by saying "you can't handle complexity". I also think it fails in its goals right off the bat because of goroutines and having some basic footguns, but that's not worth getting into.

What I think is good is that we can watch how a language built with this philosophy progresses over time. For one thing, it inevitably takes on "complexity" (generics), which I think is notable. At some point those people who love that they can jump in and know everything do actually run into the cases where those features are higher ROI (ie: writing higher performance libraries with nice APIs requires generics) and there are enough of them that they can push for those changes.

It's also interesting to watch a language grow with such a custom toolchain. Go has its own compiler, which makes a lot of sense for some of their goals, but it has also meant that they've had to reimplement (or just have not implemented) lots of optimizations.

To be clear, in case I've somehow insulted someone who writes Go, if you like Go I'm not saying "you are a bad developer", I'm saying that Go was designed for bad developers. I know lots of very good developers who like Go.

[0] https://www.youtube.com/watch?v=rFejpH_tAHM

Even for bad/inexperiences/stressed/inattentive/... developers, Go is a minefield.

Just a trivial example: trying to use append(x, y) without re-assigning to x? Congrats, it will compile without warnings, work fine for a while, then at one point append will reallocate and bang, have fun letting the newbie debug that on a Friday evening.

I don't understand what you mean by this.

  [~] $ cat main.go 
  package main

  import "fmt"

  func main() {
   x := []int{1, 2}
   append(x, 3)
   fmt.Println(x)
  }
  [~] $ go run main.go 
  # command-line-arguments
  ./main.go:7:2: append(x, 3) (value of type []int) is not used
and if you assign the result of the append to something else, x is unchanged.
The parallels to Java are so fascinating on multiple levels. Did you know that Guy Steele was behind it too? At one point it was a revelation to me that the Java designers were not rookies, they just made a conscious decision to dumb things down.

And in hindsight and after 15 years of cleaning up the mess we know that they just needlessly made a generation of developers suffer through primitive abstractions.

I don’t see anything on how Guy Steele would be behind Go.

And Java is not a badly designed language at all, it did make several tradeoffs at places for various reasons, but it had a clear design goal and I think it did live up to it.

I didn't phrase it correctly. What I meant was that Mr Steele was part of the Java org in the early days. He is from a generation before my time but from what I gather a Scheme/Lisp luminary is someone who is competitive with the Golang designers and the PLT PhDs in general.

The most glaring issues were fixed in Java5 (similar to how golang just had their generics story improved). It took us all the way to Java8 to have a semi-pleasant language. I'm still waiting to see if Golang's generics are good enough to make abstractions/monadic operations similar to the Java's Streams widespread. If memory serves, basic pattern matching is coming in Java20, only 12+ years after Scala.

In contract to, say, virtual threads, a lot of these are language-level features. So they could have been done _much_ earlier. A generation of developers had their taste for abstractions skewed by the early days Java. Doing the job of the compiler in their heads. Now the next generation finds itself in the same situation with Golang.

The only difference is that Java was actually novel on the runtime side at the time (and still is), and “conservative, blue-collar” on the language side, deliberately. It is not hard to add many many features to a language in itself, what is hard is to balance only a few features whose interactions are sane and give enough expressivity for their bucks. I think plenty of otherwise great language suffers from this problem, most notably C++, but swift and c# are hitting that point for me as well. They have so many features that no developer can hold all of them in his/her head.

I think this slow moving is really an advantage for Java, such a widely used language can’t allow breaking changes willy-nilly so any language feature will effectively become written in stone. Also, surprisingly many things can be expressed purely by “objects with method” APIs (Steele’s growing a language)

A new language on the other hand could have actually iterated fast because relatively few programs are written for them in the early years, go is just not a good language.

They are living legends, but they are not good at language design. C is not a well-designed language either.
(comment deleted)
(comment deleted)
Go doesn't need exception handling for errors. It needs a proper sum type.
> This was a golden opportunity to implement some typical form of exception handling

No one wants that in Go, please stop trying to shove it down like it’s THE solution.

A lot of Go users are fine with the way error currently work, because they’re considered as values, and their handling don’t break the flow like exceptions do.

Sure, Results would have been better, but this ship has sailed. I’d settle for allowing inline « if » expressions, but besides that, error handling in Go is fine.

I feel a lot of that has to do with Googlers not daring to question the famous authors, and the same authors seem to have been asleep for the last three decades in terms of modern programming language developments. I geared up to learn Go one day, got the book and everything, and after just a few hours of trying to write and rewrite error-handling code, I was wondering how this ever got out of the Request for Proposal stage.

Oh, you have to manually chain your errors and no you don't get stack traces out of the box. If you want to to know where the error message is, I don't know, guess?

It's insane.

Exceptions happened precisely because manual error-handling was messy and did not scale, but suddenly it's The Way.

You do get stack traces out of the box? Exceptions trigger stack traces, or you can PrintStack
Coming from Python I appreciated that there were no exceptions.

I much prefer the explicit error returns instead of exceptions being used for flow control.

But thats the programmer’s fault. You shouldn’t use exceptions for flow control.
To an extent, sure, and that can apply arbitrarily to lots of things.

The Python community, by and large, embraces "easier to ask forgiveness than permission" and some features in the standard library use exceptions for their flow control. It's much more likely to run into overuse of exceptions in Python than not.

It’s not explicit error returns any more than C code with all those larger than 0 checks. You will leave it out or get angry at the line noise and will miss(handle) an important error case and will have a bad program and you won’t know about that until it’s too late.

A loud exception is way better.

Exceptions reflect the realization that the proper place to handle an error is generally not where the error itself occurs. The problem with exceptions (and error returns, for that matter) is that the best place to handle an error is also not the same as the best place to decide how to handle the error.

So far, the only programming environment I've seen that makes a three-way distinction between raising, recovering, and deciding how to recover from an error is Common Lisp with its condition system[1]. However, I've successfully implemented the idea in Java using exceptions as the non-local control flow primitive, and in C using setjmp/longjmp.

[1]: https://lispcookbook.github.io/cl-cookbook/error_handling.ht...

> the best place to handle an error is also not the same as the best place to decide how to handle the error.

I'm not sure I follow the distinction here, or at least why it would be important? i.e. if I parse "decide how to handle the error" as catching the exception, where I handle it is surely basically anywhere since I'm not restricted in calling into other code to do so.

Common Lisp exceptions are resumable which is the main difference between it and more common languages like Java and C++. e.g. "Decide" can also include deciding to retry the operation with different inputs.
The answer to error handling is ADT like Result<T, E> or Maybe<T>. The problem with old exception is that it is not part of the function signature. If a function says it accept a key and return a value; why should I expect it to return NullPointerException?
And what if an underlying library returns the exception? All the opposition to exceptions I have encountered are theoretical, and I have never seen it work that way in practice.

What signature do you get from "if err := myFunction()"?

It's still just a generic error object, possibly a string. You still have to write a handler for it. Why do it every time?

I have a web application. There is a SQL query that produced no results for an object. I throw an ObjectNotFoundException. It results in an HTTP 404, producing a web page or a JSON response, depending on the request location, and I handle it all once.

>What signature do you get from "if err := myFunction()"?

You provided an example from Go which is almost like an exception. And I think it is bad. You have a function that returns....something?? Instead, if you have something like Result<T,E>, it tells you exactly what you're getting.

```

value = myFunction();

match value {

Ok(v) => // whatever

Err(MalformedSQL) => // oh something with sql happened?

Err(UserNotFound) => // oh can't find user?

```

Then you get "BZZZZZ, compile error. You forgot to check for another variant of the Error (ThirdPartyLibraryError)" which is a million time better than catch-all exception handling because you are afraid something might get out of this function call. If you do want to have a catch-all exception handling, then that is also possible by using "_ => {}" which is explicit. So just glancing at a piece of code, you know you have handled ALL the possible error cases.

>I have a web application. There is a SQL query that produced no results for an object. I throw an ObjectNotFoundException. It results in an HTTP 404, producing a web page or a JSON response, depending on the request location, and I handle it all once.

Also possible in axum. You have an error enum and then you implement a trait that converts said enum into an HTTP response. So your code looks something like

```

value = myFunction()?;

```

and if there's any error it will automagically turned into the appropriate response. So I get all the ease of exception but the type signature is not lying to me. If I forgot to implement a conversion from some new error to HTTP I get compile time error.

Go with Rust-style tagged unions would be a delight (and that would improve error and null handling in many ways)
> everything from TLS to CSV is rolled into std without ever having to import a third-party repository

And yet the std library is missing some basic data structures like Set.

Because they can't be properly implemented without generics, and generics were only shoehorned into Go a decade after its initial release.
The go language has had generics since inception, just not user defined ones.

If they wanted a set data structure they could have had it like they do maps and slices.

Set is just a Map though where you don't care about the values.

var m[string]struct{} implements this for you.

What you're really missing is the usual suite of helper methods which make sets ergonomic - and for that you need proper generics.

Fwiw lots of folks dislike m[K]struct{}, because while it’s efficient checking for presence is horrible:

    _, present = m[k]
It looks weird and is not an expressions. Obviously you could paper over this with a dedicated builtin function but that’s not great.

A common alternative is m[string]bool, memory density is worse but contains/add/remove are easier to read and more convenient.

They could be implemented as builtins, like channels, slices, and maps were.

Though what's really missing is generic set functions, and a decision as to which "hashmap shape" is the blessed set (between map[T]struct{} and map[T]bool), a "set" builtin can just be an alias to that in the same way "any" is an alias for "interface{}".

set := map[settype]struct{}

This usually covers most cases I've seen for the use of sets

For all that you have the JVM ecosystem with multiple better languages (including Java itself)
3 second builds?
Yes, thanks to compile on save on IDEs.

Also Turbo Pascal, Modula-2, Delphi, VB 6 were already doing that in 1990's hardware.

Here Turbo Pascal 5.5 marketing material from 1989, 34 000 lines per minute.

https://archive.org/details/TurboPascal55

Go compile times are a reason to admire only for those that never used such compiler toolchains.

I think people are comparing Go’s compile times to the compile times of other languages that they have the option of using in the present day. Go is not the only language in history for which there exists a fast compiler, but its fast compile times are certainly salient when comparing it with a number of popular alternatives. OCaml springs to mind as a plausible alternative to Go that also has a fast compiler, but its ecosystem is vastly smaller.
I would hesitate to call Java a better language than Go, but Java is much older so the designers of Go should have learned from Java's mistakes, which they did not.

When Java was new, though, it was certainly a far better language compared to its contemporaries than Go was when it came out in 2009.

That and Java still continues to be both faster moving and more innovative than Golang despite all that baggage.
Its exceptions (even with the not perfect checked ones) are eons better than Go’s solution. Not sure how expressive Go has gotten with generics, but Java can solve plenty of problems very elegantly with streams. Oh, and did I mention that Java is goddamn more concise than Go? But for some reason verboseness is never brought up against the latter.
Isn't this line of argument somewhat self-defeating? It sounds as if these language design decisions that cause a vocal minority to complain loudly have only a marginal impact on actual productivity (or at least the perception of it). If so, that would be quite a good justification for not complicating the language by, e.g., adding enums, a non-nil pointer type, etc. etc.
I think that what you’re talking about might even be the killer feature that is often ignored: the forced simplicity. It’s a simple enough language that you can drop into a codebase and read without having to look up much syntax, assuming you have some familiarity with c-family languages. Yes, there are some weird bits that you have to learn, but the learning curve is very short compared to a lot of other languages.
Indeed, Go is reduced to the bare essentials!

That's why it has primitive types representing complex numbers, which maybe 1 in 10,000 projects will use.

It doesn't have enums though, because who needs those?

No need to be so salty. No-one is claiming that Go is an ultraminimalist language, just that it's relatively simple compared to most of the alternatives.
> the forced simplicity

I appreciate this about Go. I'm not a programming language wonk, and Go's forced simplicity makes it easier to read other people's code.

Ad absurdum that would mean that programming in assembly is the most readable.

Abstraction is the only reason we can write any complex program.

The bigger the toolbox is, the more becoming an expert pays off. I would much rather fully learn a language I’m using as a working professional, than have to review many home-grown solutions to a common problem the language ignored.
> Years and years of discussions about Go's poor and verbose error handling

...with majority of users saying that it's actually great and simple, unlike "usual" approaches...

> Many "features" of Go are borderline insane

...which start make sense as long as you use them and put some thought behind the reasons of the design, instead of sticking to "what I used to is the only right way" mentality...

> (date formatting)

Of course, mnemonics 1-2-3-4-5-6-7 (1st month, 2nd day, 3rd hour, 4th minute, 5th second, 6-th year, 7th timezone) is harder to remember than mmHHMMssSSYYyy crap that is different in different languages. Without knowing the language, guess what will be printed by "15:04 Jan 2 2006" and by "MM:ss mmm D YYYY"?

> were recognized as mistakes decades ago (nil pointers)

...by popular urban myth and people who love reductions of the complex ideas into simple black-and-white statements...

It's funny how people can believe that great compiler, an amazing standard library, and high-quality official tooling can be a result of a bad design :) Good luck in spending so much energy in communicating your lack of intent to understand Go design from fundamental principles instead of comparing to the mainstream languages.

The insane bit about date formatting is recognised to be insane by the Golang team themselves. To quote https://pkg.go.dev/time:

> It is a regrettable historic error that the date uses the American convention of putting the numerical month before the day.

Such a "historic error" is literally impossible if you use the "MMddyyyy crap": it's always clear from context what format is in use.

(I mean, in what world is it even remotely memorable that the canonical order is month, day, hour, minute, second, year, timezone? That decision is just… really bad!)

This clause is just about slight inconvenience for non-US citizens to remember the order of month and day, and not about not using letter-mapping approach, which in some languages is using the whole alphabet.

Are you intentionaly trying to misrepresent that clause of "regrettable error" or didn't read carefully?

Do you have a source for that statement? (For all I know, you are a member of the team who wrote it, so you may know what it means! I certainly had nothing to do with its writing.)

To me, the statement is obviously saying "yeah this order is just bizarre, why on earth did we put the two most significant digits around the outside of the rest, good luck".

I'm not a member of a Go team.

I can comment though as a non-US citizen. This "bizzare" US order (month-day-year) is SO COMMON in software that it's very familiar. Like most of the software historically was born in US, and it's just a very common case where localization is not respected and you see american style dates every now and then. It's getting better, but still, not unusual.

While it's different from european, it's definitely not "insane" and not "bizzare". Why would anyone even claim that?

It's not common in any software I've written or touched in my entire career.
Well, maybe subset of software you written or touched is much smaller than the whole set of software billions of other people wrote or touched.
Oh, without a doubt.

What I mean though: the US isn't the world, even though many people think it is.

And the US date format is among the weirdest I ever encountered.

Super happy I never had to support it. It was enough to support over a dozen different formats at one customer at one point.

> This "bizzare" US order (month-day-year) is SO COMMON in software that it's very familiar.

I've been using computers since 1986, and I've used many many software packages.

Which software package uses month-day-year outside of the US? Every package uses the locale, so the 94% of the population outside of the US literally never sees this format.

Here, quick example from today. Working with an R-script and needed to check date functions [1] and you got mm/dd/yyyy literally in the docs. Good luck figuring out what "%a" or "%D" means, btw.

Another recent example - I had to fight for two hours with yet another javascript date/time picker to present date in localizaiton of a browser. Turns out this popular picker didn't have proper support for localization and set US one by default.

The same issue I had with Flutter time picker, btw. Not sure if it's been fixed by now, but making proper localization support can be quite challenging even in 2022.

Sure, localization support is getting better each decade, but it's far from perfect. And if you've used software since 1986, then you should remember that at that time the concept of localization DIDN'T EVEN EXIST and majority of the software (including DOS!) used US format (there were "localized" versions of DOS, ofc, but those were even branded differently and the best you could get is codepage, keyboard and printer support).

[1] https://www.statmethods.net/input/dates.html

> it's definitely not "insane" and not "bizzare". Why would anyone even claim that?

Pretty sure it's just anti-American signalling/globalist cultural imperialism. Asian/8601 date format makes a lot more "logical" sense, but nothing beats what you grew up using, at least for me, in a non-technical context.

Obviously your software should present dates in a locale-aware manner, just as it should (where possible) present localised text. But if it's anti-American signalling to expect reference documentation and technical features for non-localised systems (such as the Go programming language) to use the internationally accepted standards for the presentation of non-localised data, then… sure, I guess I'm happy to signal anti-American?

But I am one of Those People who goes around $WORK's internal wiki (which has users in the USA and GB) telling people off for failing to use 8601 in their meeting minutes, so perhaps take with a pinch of salt.

To be clear, I meant it as a general phenomenon and wasn't directed at you. I see a lot of the same thing on reddit, etc. Metric vs imperial is an almost-daily comment chain in the Oxygen Not Included subreddit, for example. It just gets a bit tiring to see the same discussion again and again.

(edit) I'm perfectly fine using 8601 in technical contexts, or anywhere really, but describing American cultural practices as "insane" and "bizarre" is exactly what I'm talking about.

Then ditch letter mapping and use named parameters: "{day} {month} {year}" etc. The Golang approach already requires complicated string parsing, so a formatting language isn't going to be more complicated, but then the documentation can just tell you exactly what you need to write to get the desired output, rather then debugging whether you got an invisible implied order wrong (with a failure mode of either giving random output, or just literals).
But why? It's so handy in practice!

I work a lot with times (not just dates) and having strings like "15:04" make it immediately recognizible, consise and just right.

What I actually lack in Go's time is ability to work with time only without timezone (like, event starts at "12:00", regardless of timezone). But here is the thing - Go is a programming language, and not "framework with solutions for every use case" as many others. So I just spent 10 minutes and wrote my own type for that and use it happily along with time.Time.

> But here is the thing - Go is a programming language, and not "framework with solutions for every use case" as many others.

So... why did they provide a terrible time formatting routine in the first place if it's not a solution to use cases?

OP was talking about an issue unrelated to time formatting in the part you quote (the lack of a timezoneless time type).
Because they're not that smart as HN commenters, obviously. Be compassionate.
> slight inconvenience for non-US citizens

So, 96% of the world's population?

And even in the US, science and engineering projects commonly use ISO dates.

Go's date formatting is as if a random American had taken a quick glance at a paper calendar pinned to the side of their fridge, and decided "yeah, let's do it like that".

Most people can remember "January 2nd" and don't think about US-date vs non-US date.
Or "the second of January", as it may map better to your language ("El dos de Enero")
Or “le deuxième de janvier”, or “der zweite Januar”, or…
Well...

January 2nd?

or

January 2?

or

2 January?

Just remember the date and use it in your date formatting string.

If you want today to be printed as "December 11" put "January 2" in your Format() call. If you want "11 December", put "2 January". No need to remember any alphabet mappings anymore, it's way simpler.

With both approaches you have to remember some sort of mapping technique for date formatting.

What I claim that Go's approach is far superior and easier to remember and use.

Leaving aside "oh, but we used to letters" thing, one of the problems with letters is that it's just o much stuff to remember and it only grows as you use more than one languages on a daily basis. I sometimes switch between 4 languages during the day, and this alphabet juggling goes out of hand very quickly. Like it's unusable without constantly opening documentation pages for strfime-like functions.

I mean, I get the attachment part, but I believe that more efficient approaches should get recognition, not punishement.

> What I claim that Go's approach is far superior and easier to remember and use. [...] I mean, I get the attachment part, but I believe that more efficient approaches should get recognition, not punishement.

The Go approach is barely "more efficient" if your only consideration is exclusively US-centric, it's outright hell everywhere else, because US date formats make even less sense than their units. Go's datetime format is gimmicky, gimpy (it's missing a good fifth of the TR35 fields, to say nothing of the field formats), inextensible, and localisation-hostiles.

It's also telling that while Go made up this nonsense with date formats, they retained the significantly worse and more error prone printf formats for, well, printf, in case mixing the two might have been an option (as it is in python for instance).

> is exclusively US-centric

It's literally not a problem at all. It's just one bit of information to remember in your head (I explained in a sibling comment how common US-style date in software, so for anyone who used computer longer than their last iPhone, it's just one bit of information - either date in US style or not). But with Go formatting it's even simpler - you literally never think about it.

You just remember that it's "January 2nd" and that's it. I'm so surprised that people trying to present it as a huge problem, let alone "insane" or "bizzare".

> It's literally not a problem at all. It's just one bit of information to remember in your head

Your assertions do not make that a reality.

> But with Go formatting it's even simpler - you literally never think about it.

True, because you quickly learn to use something else.

> Your assertions do not make that a reality

What reality? I write Go almost daily for 9 years and was running Golang meetups in two countries for years. I think I have pretty good grasp of what actual problem real people have with dates in Go. Order of day-month in mnemonic ("Jan 2nd" vs "Feb 1st") is literally not a problem. This problem exists only in HN comments of people used to other approaches.

That’s cool. I constantly get tripped up with day/month ordering. Maybe I am dumb, but it is a silly design to defend in my opinion.
Can you elaborate in details how exactly you tripped by this?

Like, when you need to present current date so it looks like "11 December, 2022" - where your issue starts with Go's approach? Can you walk me through your thought process here? I'm genuinly curious.

> Order of day-month in mnemonic ("Jan 2nd" vs "Feb 1st") is literally not a problem

I mean, you literally got it wrong in this comment, so maybe it’s worth considering whether such a simple mistake might be hard to catch and thus cause real production issues?

(The GP was using the US convention to refer to the two dates 01/02 and 02/01, not trying to use two conventions to refer to a different date each.)
Ah, I see. Apologies to GP, but I believe the point is still reinforced - the biggest enemy of an engineer when it comes to dates is ambiguity.
(comment deleted)
I mean, how often do you have to parse that part of the code? If it is everywhere, you should start writing helper functions or some other abstraction.

In my opinion this is typically a very static part of code bases, it is written correctly once (where you can look at the documentation all day long) and possibly never ever looked back on.

Well, the proof is in the pudding, and the pudding has spoken and found Go wanting, compared to its most direct competitor, Rust.

Even though Go appeared earlier than Rust, and Go's creators are much more famous than Rust's, and the corporation behind Go is much more influential than that behind Rust, adoption of Rust is skyrocketing for mission critical systems while (apart from the container ecosystem) Go is much less popular.

Isn't Go more positioned as a language for web services, rather than systems programming?
It's true... Go was supposed to be great for mission-critical stuff but the issues pop up quickly. I've seen several cases where someone migrates a Go service to Rust and like a bad dream the major problems disappear.
And yet Go is much more popular than Rust.
And PHP is more popular than both of them, so it's not tomorrow that gauging the qualities of a language on the use the vulgum pecus makes of it will be a good idea.
… is it? For example, the Stack Overflow survey says Rust was loved by 86% of its users, while Go by 64%. Rust also beat Go on the percentage of respondents who wanted to use it. (But Golang was used by 2% more respondents than Rust, at 11.15% vs 9.32%.)

https://survey.stackoverflow.co/2022/#section-most-loved-dre...

Describing that as "2% more" instead of "20% more" is a bit misleading when comparing just two items.
Sorry, I intended to say "2% more of the respondents". That was what I had in my mind when I wrote it. (In general, my opinion is that Relative Statistics Considered Harmful, they're like absolute statistics but with all the context removed, and I never intend to give them.)
> compared to its most direct competitor, Rust.

No, no, no and no. How on Earth people come to this conclusion? Go is closer to node.js than to Rust. Like, it is not even a competition they are so far apart. One is a “zero-cost abstraction, low-level language”, the other is a “managed language with a GC like million others, which is as badly expressive as C”. To mention some positive as well, virtual threads are cool.

But the two is nothing alike and this sentiment is just utterly stupid and I see it everywhere for God knows why.

> How on Earth people come to this conclusion?

1. Because they were first unveiled around the same time, as AOT natively-compiled languages with drastically different takes and directions.

2. Because when the authors of Go first released it, they called it a systems language. By which they meant, unlike anyone else, a language to write systems with. Like… programs.

3. Because when first unveiled, Rust was a very different language, much more similar to Go. It always had a much more functional and type-heavy bent, but it had a runtime and scheduler, userland threads, kinda-sorta a GC in potential (“GC’d pointers” though no GC was ever implemented for real). As noted in (1) it looked a bit on a completely different take on a similar niche. It was brought downstack over time as the community saw its potential as an “innovative” breath of fresh air for low-level uses: higher levels of the stack already had memory-safe and type-heavy programming languages if they wanted those.

> Of course, mnemonics 1-2-3-4-5-6-7 (1st month, 2nd day, 3rd hour, 4th minute, 5th second, 6-th year, 7th timezone) is harder to remember than mmHHMMssSSYYyy crap that is different in different languages.

Very much so. The latter is at least portable between cultures, because a month is a month, unlike "1".

And then of course you've got all the collisions like weekday (which for some reason does not follow the US standard either) or yearday, the ambiguities (like sub-seconds which can be either 0 or 9), and the outright missing fields (week numbers? week-years? No comprendo, señor..)

Very much so?

Let's put it to the pragmatic challenge. What will be the output of Go's "Jan 2006" and of R's "%B %h %M" format strings? Don't google.

(comment deleted)
“Jan 2006” and short month, short month, minutes?
To be clear "Jan 2006" - is a format string. Current date would yield "Dec 2022", obviously.

R example - it's "Long month, hour, decimal minute", so something like "December 15 35"

So you got Go format style right, and alphabet-style wrong. But Go's format is worse, right?

> which start make sense as long as you use them and put some thought behind the reasons of the design

Hard disagree. I have been using Go at my company for years, and these features still make no sense to me.

> lack of enums

Not even talking about the eyesore that is the magical const/iota combination (oh yes, your constant definition are scope-dependent, remember to never screw up a copy/paste), go enums are basically C #define. And I think that the consensus has been, for decades, that having badly typed magical constants was not a good thing.

> implicit interfaces

Implicit interfaces are OK when you want to cow-boy your way through through prototyping. When I go back to your code 3 years later, it's a huge obstacle. OK, so you have this function that takes a FooBar. Oh, FooBar is an interface. Great, now I'll have to pull a rickety 3rd-package or ‶just grep bro″ through the whole code base to find what types implement this interface. And God forbid I needed to extend this interface and missed to add the new method to one of the 32 types implementing it, so that it blows on my face in prod at 3AM.

> by popular urban myth

Tell me exactly who finds that nil pointers are a great thing in a high-level language?

> And God forbid I needed to extend this interface and missed to add the new method to one of the 32 types implementing it, so that it blows on my face in prod at 3AM.

The language has static type-checking. If to you it's a regular thing that something like this blows up in your face, maybe it's the code base doing too many things dynamically, and not the language?

> Great, now I'll have to pull a rickety 3rd-package or ‶just grep bro″ through the whole code base to find what types implement this interface.

Or just use any of the popular Go code editors/plugins which give you a "show me all implementations".

In general, implicit interfaces let structures and functions require much less by defining their needed functionality subset, instead of requiring the whole "domain interface" from somewhere else.

If you have a struct that only needs to list an S3 bucket, it's nice to accept an interface that only requires listing, and not the other 30 methods the AWS library "full" interface has. This makes understanding what a piece of code is doing much easier, while simplifying mocking as well.

I do agree that enums, or even better, sum types, would be desirable. It's not a big issue in my day to day, though.

> Or just use any of the popular Go code editors/plugins which give you a "show me all implementations".

And me thinking the Go culture is against languages that tend to come with IDEs...

You don't need an IDE to do that. More or les anything that can hook up with the Go language server will do the job.
So somehow needing a language server to work around language design flaws is alright for Go then.
Sarcasm aside, I do think it’s fair to assume that someone writing code in a modern programming language in 2022 has access to basic language-aware editor tooling. That’s the beauty of the language server. You can look up the implementations of an interface from vim, or some other minimalist development environment, if that’s your bag.
"Go is so simple you don't need an IDE to write it, unlike java"

"Well obviously you need a proper IDE to protect you from Go's fuckups"

Cool.

You don’t need an IDE, as I explained in my two preceding posts in this thread. Please at least try to respond to someone who is actually making the arguments that you take yourself to be refuting.
Go culture, like any other, is diverse.

I prefer to use an editor/IDE and don't think it's pleasant to work with any language (including Go) without them.

Not to mention how slow it becomes in large projects
> The language has static type-checking.

No it does not, at least not completely. When you carry around pointers to struct implementing interfaces, it does not check for anything at compile time.

> Or just use any of the popular Go code editors/plugins which give you a "show me all implementations".

Does not work all the time, hence my use of the ‶rickety″ adjective.

> This makes understanding what a piece of code is doing much easier, while simplifying mocking as well.

That's a design problem, not an interface problem. Split you giant 30-methods interface into smaller ones if need be, that the whole point of interfaces: not be a 1-1 mapping to methods, but a semantic (sub)group of them.

>When you carry around pointers to struct implementing interfaces, it does not check for anything at compile time.

What exactly do you mean here? You can't assign a value that doesn't implement an interface Foo to a variable of type Foo. That will cause a compile time error.

> That's a design problem, not an interface problem. Split you giant 30-methods interface into smaller ones if need be, that the whole point of interfaces: not be a 1-1 mapping to methods, but a semantic (sub)group of them.

It really isn't. This way it becomes a guessing game "Which methods will be used with which other ones?".

Even if I have a KVStore interface with 4 methods: Get, Set, List and Delete, it's a huge value to future code readers if my new structure only accepts an interface with List and Get, or List and Delete.

The other commenter already responded to your static typing assertion, so leaving that for there (it absolutely does check it).

> it's a huge value to future code readers if my new structure only accepts an interface with List and Get, or List and Delete.

But that's not specific to golang. You can use the same approach in Java, Kotlin, C#, etc

Unless I'm missing something, you then have to write wrappers that translate between the big and the small interfaces, or explicitly mark all interfaces as implemented at the original type definition point.

The first one is a lot of boilerplate, the second one is not always possible.

(comment deleted)
> Implicit interfaces are OK when you want to cow-boy your way through through prototyping. When I go back to your code 3 years later, it's a huge obstacle. OK, so you have this function that takes a FooBar. Oh, FooBar is an interface. Great, now I'll have to pull a rickety 3rd-package or ‶just grep bro″ through the whole code base to find what types implement this interface.

Language servers are a thing nowadays, and the standard go one will find all implementations before you finish blinking. Why is this considered harder than whatever you are using to find implementations in your language of choice?

> And God forbid I needed to extend this interface and missed to add the new method to one of the 32 types implementing it, so that it blows on my face in prod at 3AM.

So like every other language with interfaces then? But why are you even compiling in prod at 3AM in the first place?

I thought it was a joke about mnemonic, until realized you're serious. This is NOT a mnemonic, because it lacks either rule like each next component gives more specific time, or external reference like poem or image. It can be easily substituted with 1st year, 2nd day, 3rd hour, 4th minute, 5th second, 6-th month, 7th timezone.
mnemonic is just a memory aid, it can be anything. if you prefer other term, feel free to use it, but the point still holds - it's a 1234567-based date that you remember once (most people can do it) and it helps remembering what should be in the format string.
What is 1234567 anyway? Why 1 is month? From which clue should I remember that?
From the cultural assimilation of being american for which it makes perfect sense that the month comes first and the year comes last. Apparently. That’s also why the timezone is a weird-ass US timezone (MST, Mountain Standard Time), and why you don’t get tzdata timezone names.

But don’t rely too much on cultural assimilation, because DOW is not the Sunday / 0 you might then expect.

There is an ISO standard for date formatting. Go ignores it, which makes interop problematic. We do get RFC3339 support, as well as a few others, but if ISO dates are being used in data being read by a Go program it’s a huge headache to ensure it will be correct using the standard library. There’s no real debate here, it was a design mistake and admitted as such.
> (1st month, 2nd day, 3rd hour, 4th minute, 5th second, 6-th year, 7th timezone)

Who was the bright spark that decided they the month of all things should go in first? This format makes almost as much sense as Sunday being the first day of the week

Well it’s Americans in both cases so… there you go.

Also funnily enough Go does not use the field 0 for DOW, instead it uses 1 (Monday).

Which makes no sense at any level, because it uses 2 for both month-day and year-day, but apparently week-day is not a day (and you can’t get the numerical week-day anyway, because who needs that).

Implicit interfaces are one of the defining features of the language that make it so great, I love them. Calling them borderline insane without any supporting arguments does make me question whether you ever gave idiomatic Go a chance. What do you dislike about them?

Re error handling, I've been looking at the proposals and haven't yet seen a satisfying one.

Sure, the current error handling is a bit verbose (three lines after each erroring function call), but it's a completely irrelevant problem to me in my day to day, and I much prefer it to exceptions as it makes you think about each error and makes people wrap errors with helpful context everywhere.

In general, I like the approach of the Go team of only accepting proposals that are good and actually widely requested, as opposed to accepting "something" because "our story for this isn't great to some".

> I much prefer it to exceptions as it makes you think about each error and makes people wrap errors with helpful context everywhere…

This would be absolutely fine, for the reasons you state, if Go made you deal with the error. But because it will silently allow you to completely ignore the error, your statement that it "makes you think about each error" is quite optimistic. In an exceptionful language, the runtime will let you know loudly and fatally when you've got garbage; in a language with a type system that admits a Result type, the compiler will forbid compilation unless you note that you're throwing away the error (which might be a single character of syntactic sugar, it need not be verbose!). But Golang and C and friends are the languages where you silently get garbage if you forget to do the due diligence that machines are perfectly capable of doing for you, and it makes me extremely sad every time I see a bug of this nature.

I don’t like Go error handling, but exceptions are terrible and not better.

Rust/Zig/Swift approach is definitely the best.

Exceptions are much better, especially checked ones (which are analogous to Rust-style result types).
Not analogous.

Rust-style result types continue the normal control flow.

Any exception thrown deep into a function can be very hard to debug. The normal line of execution is stopped, even without a return, and if it’s a checked exception it can “bubble up” to higher functions despite nothing signifying a return, just a marker saying somewhere this can throw an exception.

Exceptions are terrible.

This is literally the exact same thing. Exceptions are not gotos. They bubble up one method at a time, and can be handled at any level you want. Matching on a result type is literally the same thing as putting in inside a try-catch block. Putting a ? macro at the end is literally the default behavior. You can handle it on as tight/wide scope as you wish, plus you also get a must-have stacktrace — so your debugging is just looking at where the exception originated from.
> What do you dislike about them [implicit interfaces]?

Implicitly implemented interfaces can lead to problems where you're "implementing" an interface because your methods happen to be named the same, but the functionality doesn't adhere to the contract the interface is supposed to represent. Not every contract can be described by the type system, especially not by a type system as limited as Go's.

Method names like "Write", "Get", or "Execute" are very common, yet in Go you have to be careful because suddenly you're implementing an interface that is intended for things that have nothing to do with what your code actually does.

Simply put, it's an enormous weakening of contract safety, for essentially zero benefit. After all, spelling out that your type implements a certain interface really isn't a lot of work.

You call it an "enormous weakening of contract safety", I call it an issue I've never once had happen in practice.

Sometimes you can't spell out that your type implements a certain interface, because it's not a type controlled by you.

Moreover, this completely kills the use-case of "define your own small interface with the subset that you actually require, instead of accepting the whole thing", which is a big part of the value-add. See my other comment[0].

[0]: https://news.ycombinator.com/item?id=33943352

> Sometimes you can't spell out that your type implements a certain interface, because it's not a type controlled by you.

Dynamically inferred interfaces only help there if function names and arguments happen to exactly match those of the interface. So if, for example, a method is called ‘Run’ rather than ‘Execute’, or you have ‘Run(string, int)’ and ‘Run(int, string)’ methods, that won’t work.

Some languages that require explicit interfaces (and, I think, most modern ones) allow you to specify that mapping at run time, and also allow you to retroactively make all code (including third party code) that implements FatInterface implement your SmallInterface that’s a subset of it.

Golang chooses to not require programmers to do such work and, I think, hopes programmers will keep names and argument type order identical for common methods, so that those problems occur less often. I don’t think that will work in large code bases, but then, I don’t have experience with large golang code bases.

It also leads to easily fucking up interface implementations because that's not checked, some folks actually put "interface assertions" above the interface methods to ensure they're not forgetting anything and did not screw up any signature e.g.

    var _ Iface = (*someStruct)(nil)
will fail to compile if someStruct does not correctly and fully implement Iface.
That's just your opinion. I love the error handling in go and I believe it is a vocal minority that complain about the error handling on hn.
> Go repeats design mistakes that were recognized as mistakes decades ago (nil pointers)

All popular languages have some way to encode the fact that a value is absent.

When languages that don't support nil/null values become popular, you might have a point in claiming that it was a mistake.

Right now it makes no sense to make that claim.

>All popular languages have some way to encode the fact that a value is absent.

The problem is that many languages don't make the distinction in the type system. Why oh why do we not force everyone to null check every time?

The concern around C-style nullable pointers is that you can't opt out of nullability; everything you point to may or may not dereference. As opposed to other designs (e.g. "optional" types leveraging generics) that are opt-in, and thus let you choose which references are always valid and which ones are not.
Rust doesn't have nil/null values. It has an Option type that enforces at compile time that you explicitly handle missing values, through pattern matching, transformations, or unwraps.

This makes 95% of the problems associated with nil/null pointers magically disappear. The difference this makes for the safety of large-scale systems is huge.

> Rust doesn't have nil/null values.

Yeah, and it's also not popular. The reason I said "popular" is because the popular languages are addressing pain-points for devs.

If nil/null was a pain point, none of the popular languages would allow nil/null values.

The simple fact is that, for many developers, there is a pressing need to indicate lack of a value. Languages that don't address this pain-point never really get taken up en masse.

I don't think anyone gets to say that Rust isn't popular anymore. Especially considering it's now in the Linux kernel.
Playing devil's advocate: what's now in the Linux kernel is the infrastructure to allow writing code in Rust, but AFAIK for now nothing uses it (there's Asahi Lina's Rust driver for the Apple M1/M2 GPU, but it's still out-of-tree).

I find it likely that Rust in the Linux kernel will not be used for anything important (other than architecture-specific code) until either it can be compiled using GCC (either the GCC backend for the Rust compiler, or the Rust frontend for the GCC compiler), or the LLVM backend supports every single architecture supported by the Linux kernel (or the Linux kernel loses support for the architectures the LLVM backend doesn't support).

Android fork of Linux kernel uses it. Bluetooth stack was rewritten in Rust for Android 12, and the new virtualization stack in Android 13 is based in crosvm, also a Rust.
> I don't think anyone gets to say that Rust isn't popular anymore. Especially considering it's now in the Linux kernel.

Popular amongst Rust fans, certainly. Popular amongst employers, no. It's still barely a rounding error for employers.

"Old language have it, so it must not be a problem" is not a very convincing argument.

Almost every new language that has come out in the last ~10 years or so tries hard to avoid null pointers, and old languages try very hard to mitigate the problem in various ways.

Dart went through a tedious process of removing nullability.

Kotlin doesn't have nullability (well, sort of, using Java bindings can still get you null pointer exceptions).

There are lot's of attempts for Java, from static analysis tools (eg recently from Meta , but there are older solutions), Springs new @NonNull, etc.

C# switched to making non-nullable the default a few years ago.

Rust only has null pointers in the unsafe part of the language, and only for low level raw pointers, not for traits.

Go is the odd one out.

> "Old language have it, so it must not be a problem" is not a very convincing argument.

That strawman is not the argument. The argument is that "popular languages have it, so it must be solving some problem".

You can say what you like about popularity, but it still remains a good indicator of what works in practice and what doesn't, especially when the alternatives are all well-known and have been around for decades.

You are playing with words, Rust / OCaml Option types are effectively the same as nulls in null-safe language, like `string | null` in TypeScript or `string?` in Dart, although I agree it has become necessary for such things to be checked at compile-time
The difference is crucial. With these Sum types a variable of type Option<Dog> / Dog | null / Dog? might be a dog, but a variable of type Dog is definitely a dog and the language has both of those types.

Languages which just have one nullable type here like Go must use their "might be a dog" type to represent things which definitely are a dog, meaning either you write paranoid checks everywhere or you might screw up - and it's no wonder there are more bugs as a result.

> Rust doesn't have nil/null values.

I have to nitpick here: Rust raw pointers (https://doc.rust-lang.org/std/primitive.pointer.html which does note "Working with raw pointers in Rust is uncommon") do have null values. It's not usually a problem since raw pointers can only be dereferenced in unsafe code; most of the time, you will be using a reference wrapped with an Option, which has the same size and same possible values (the compiler knows a reference is never null, so it uses the "missing" null value to represent the None variant of the Option enum).

> since raw pointers can only be dereferenced in unsafe code

Sure, and "unsafe" is not only a huge red flag but can and should be tightly scoped and can then be audited much easier than chasing around NPE through an entire Class or even codebase.

Secondly, I've used Rust for a few years now and have NEVER needed to or thought of using raw pointers once. It's unnecessary for almost every single usecase I can think of except maybe OS or driver development but even then you can tightly control where "unsafe" uses pop up.

Sorry, but what are you talking about? Poorly designed? A lot of incredibly good software is written in Golang and while that might not mean it's the best language ever designed, it would be hard to say it's "poorly designed" based on that fact alone?
Incredibly good software has been written in JS, that does not make a good language out of it.

To a competent team, a bad language is an hindrance, not a deadly obstacle.

So then it's just a subjective statement because whether or not you think the "design" is good, it's design is obviously practical, well liked and in heavy use?
The difference is options. Developers can use anything on the back. You are stuck with JS on the front. That devs _choose_ Go means something.
You'd be surprised that the answer in a lot of times cooties just be hype or inertia. I've seen it first hand at an employer that supported both Java and golang. A new project was started in golang just because of inertia, whereas Java would have been a strictly superior choice on basically every front. The resulting code base was a huge mess, even as per the admission of the "lead" who made the original decision, who clearly had no business making that decision in the first place. Literally over a million dollars spent on a mediocre, unmaintainable result
It only worked out because language authors are on Google's paychecks.

Their former language designs (see Limbo and Alef) weren't as successful.

Go is basically Limbo with a bit of Oberon-2 facelift.

You took the words out of my mouth
Go explicitly prioritizes productivity in large Eng orgs with a range of programmer experience. This is often at the expense of what language enthusiasts this is important/new/cool

You may not agree, but they are doing what they said they would from the start, and it’s been fairly effective.

Java has been doing that (“blue collar language”) since its inception, and is arguably doing a much better job at it.
Eschewing industry standard ergonomics that have been taught to every developer for the past 3 decades seems like the total opposite of "prioritizing productivity" to me.

Besides, in large engineering organisations you're always going to find multiple languages. Even at Google, which keeps a very short list of blessed languages compared to most companies, the Googlers I know who typically work on "proto to proto" middleware in Go are often jumping into codebases in Java, Kotlin, Dart or TypeScript. So clearly, they can deal with modern language features too.

At google it mostly displaced uses of C++ and python
I'll disagree that the compiler is great. Is it fast? Sure, but it produces suboptimal code.

I personally believe that the biggest factor that it became popular was marketing, having well known names behind it both as a corporation, and individuals who had some contributions in Unix land

I still don't see a benefit with this over custom error types. Having a huge switch statement to determine the HTTP response code seems really inefficient vs just doing errors.As and getting the response code directly.
It is not efficient, except at that one point in time you write the code.

This is `errors.As`: https://cs.opensource.google/go/go/+/refs/tags/go1.19.4:src/.... It uses reflection and type assertions and itself contains error checking (e.g. to validate that the arguments are actually error interfaces).

It looks efficient in your code because the call is compact, but this is not even close to efficient, and especially not as efficient as a switch statement.

It’s very useful when multiple errors happen concurrently, like collecting errors from separate goroutines after they all finish.
Great example, should have known this one.
As I said in the article, the example is simplified. As you point out, incorporating the HTTP code into the error and using errors.As is much better way of doing that:

"Obviously, the common HTTP status codes could easily be a new error type (based on int type) so the actual code could be easily extracted via errors.As, but I want to keep the example simple."

When comparing how Go changes with how C++ changes, one gets renewed appreciation for how good of a job the C++ designers are doing.

The more Go evolves, the more clear it gets that it has a very poorly designed foundation.

I say that as someone who's coded and reviewed a lot of Go code.

Maybe they should not have skipped 40 years of language theory when first creating it. It wasn't obvious in the beginning, but now...

> It wasn't obvious in the beginning, but now...

Was it not obvious at the beginning that this was a bad idea? What was the state of programming languages where consciously tossing out progress was considered not on, its face, insane?

In a sense this is a tradition that the Go authors already followed once when designing C.

"Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own. "

-- https://www.bell-labs.com/usr/dmr/www/chist.html

Depends where you were going. What I mean is that you can start a small language with shaky fundamentals, but when you try to grow it to take over it becomes a problem.

But yes, if the goal was to grow then it's a bad place to start from.

> When comparing how Go changes with how C++ changes, one gets renewed appreciation for how good of a job the C++ designers are doing.

We must live in a different world...

I get where you're coming from but consider that C++ has added an obscene number of features and it somehow all works. You can say "that's bad that they added tons of features" and I'll agree with you, but the fact that they've been able to do so is impressive and speaks to a powerful design process.
You may not like the language, but it's managed to grow with a consistent story. Yes, there have been some missteps, like auto_ptr and vector<bool>, but it does work together.

At worst you have to write small adapters because there's no equivalent of smatch for string_view, etc...

But compare this to Go, where contexts, error wrapping, generics, uh... error wrapping again (this multierror thing), horrible conditional build hacks, etc...

C++ is a big and complex language. But it's consistent. Go is a small simple language with lots of ill-fitting extensions added as core language features and standard library bolted all around it.

In other words: any language can be critiqued, and it often helps by showing that a deficiency is not unavoidable, by showing how another language has avoided it.

I'm saying this after 20 years using C++, from AT&T CFront onwards, all the GotW, years of Boost posts, every Modern, Effective and Exceptional book read. I've loved C++, but now it is complex even with my experience.
Again, I'm not saying C++ is not complex. I'm saying the complex pieces fit. And that Go has the problem that the pieces don't fit together. (and that this is because it has a bad foundation)

I have more than 20 years of C++ experience. Maybe more than 25. I'm surprised you used CFront 20 years ago. Is this like how in my head the beginning of the Unix epoch was "about 30 years ago" when actually it's almost 53?

Just not counting the last ten years not using directly C++, only following its development for affection ;)
I guess it's become trendy to hate on Go in this forum, so I'll offer a counterpoint.

Being a relative newcomer to the language, transitioning from Python circa 2016, I've found it a joy to use. The simplicity of the spec, the true "one way to do it" philosophy, the way it forces you to simplify and avoid cleverness (yes, sometimes at the expense of verbosity, which only makes things clearer, and rarely tedious), the great crossplatform support, stdlib, and tooling around it, and many more features, have made it my favorite language.

Yes, there are still some warts and odd design decisions, but those are far outnumbered by the good things about it. Most of the issues are related to Go's team unwavering dedication to backwards compatibility, which is respectable. The fact we got generics in v1 is incredible. I can't wait for what v2 will bring, without the shackles of backwards compatibility.

This errors feature in 1.20 is neat, but I'm much more excited for context cancellation reasons[1], which will hopefully be released soon as well. That should greatly simplify debugging, and avoid the common generic "context canceled" errors.

[1]: https://github.com/golang/go/issues/46273

The conversation around Go on HN is as broken as a political discussion on Facebook. It’s pathetic.
I think “go haters” bring up plenty of valid points, unlike.. unfortunately any discussion on facebook.
The post is about a new feature related to errors, the top comment is the same old complaint about verbose error handling, which is only tangentially related. I wouldn’t consider that quality discussion.
> transitioning from Python ... the true "one way to do it" philosophy

I love this :)

> I can't wait for what v2 will bring

There is no such thing as V2

I find it ironic that golang refuses to introduce exceptions, but instead is slowly turning errors into a crappy substitute.

I feel like a modern language should do either two things with errors: exceptions, or a result monad. Since the language is GC'd and doesn't have language support to make a result type practical, it seems like exceptions are a no brainer.

>Since the language is GC'd and doesn't have language support to make a result type practical, it seems like exceptions are a no brainer.

Elaborate? What prevents Go from introducing result monad aside from the syntax?

I'm sure it could introduce something _like_ a result monad, but with the associated very clunky/verbose syntax in golang which reduces to conditionals and lots of type casts. I guess this is sort of where error is going.

Something like Rust's '?' operator comes to mind. Also, pattern matching with parameter extraction.

(comment deleted)
This functionality is really meant for situations where you do stuff like validate a struct for completeness (part of checking user input, often) and return a list of errors instead of failing at the first error. It's kinda a godsend for the kind of application I write.
"What only matters is the unwrapping procedure and conversion to string because that is what is needed."

This, in a nutshell, encapsulates what Go gets wrong about error handling. The simplicity is great; the extreme simplicity at the expense of runtime efficiency and clarity is not. Errors are not just strings. They can (and should) encode information, and the language should support that efficiently. And the mechanisms Go includes to make it more ergonomic impose small penalties in performance that add up at scale. This "solution" doesn't help that (it makes it worse).

This blog post is inaccurate. The only information an error is required to implement is their string representation. You can add whatever you want on top, there are no further restrictions.
It's true that you can add additional information via structs or even just `fmt.Errorf`, but I disagree that the blog post is inaccurate. Fundamentally Go's error handling is very much string-centered. All the extra encapsulation you add via structs implementing the `error` interface just make things worse, because of the poor design to check those types (`errors.As` is a monstrosity).
I was trying to say that eventually, errors in Go distill to strings. Of course the type which represents errors can be anything, I thought it is obvious and it is also mentioned in the introduction section which you may have skipped.
Errors are undeniably verbose but they incite the developer to consider them, and add context or information to them. I wouldn't say they're a massive issue.

Now, enums I find myself missing, and the same goes for parsing dates from strings. Holy moly.

Otherwise, it's a straight to the point language and I get stuff done with it. Less tinkering, less overthinking, it just gets stuff done.

I still feel there's space for a middle ground language. One that:

* compiles (ie no interpreter/VM required)

* is statically typed and has generics

* has exceptions or option types

* doesn't have a borrow checker

* isn't purely functional but has map, filter, pattern matching, etc

* has reasonable traction

Is there anything like this?

… F#? As of .NET 6, ahead-of-time compilation is a thing, and I am led to believe it's got substantially better in .NET 7.
Some correction though.

AOT is already a thing since .NET 1.0, but NGEN has always had the inconvenience of AOT on install with dynamic linking.

WinRT brougth MDIL on Windows 8.x, replaced by .NET Native on Windows 10.

Mono has always had AOT, specially due to Xamarin for Android.

Unity uses AOT for C# code, via translation of IL into C++, IL2CPP.

Currently Native AOT only supports CLI and libraries, .NET 8 hopefully will support GUI applications as well.

Zig? I think it ticks all the boxes.
That's exactly the language I want as well. A mashup of Go and Rust that keeps the best of both. Also, don't forget "compiles fast"! And the garbage collector, although I'd love a language that allowed me to handle memory as well.
Here is one, https://dlang.org/
Thanks for posting this. I've never actually looked at D before but it does seem to tick all the boxes.
Dlang is a statically typed language that is more productive than most dynamic languages
Classes and Templates, ugh :(

Beyond that, it actually looks really cool!

> a borrow checker

Why do you see that a negative though?

Too complicated :(

Overall it provides guarantees I don't need for what I want to write so doesn't justify the additional cognitive overhead.

I think something like V is what you're looking for:

https://vlang.io/

Perhaps V will be suitable for writing production systems one day. Until then, it doesn't seem suitable for anything outside hobby development.
To be clear, I wasn't telling GP to go and write production systems in V. Even though V has stabilized a lot by now, it's still a pre-v1.0 language and with that it carries potential risks.

I suggested to GP to take a look at V, though, because V has a Go-like syntax + it has everything GP is looking for.

Agree with that assessment, particularly relative to what the GP seems to be looking for. Vlang is a great alternative to check out.
Java and C# do compile (GraalVM, OpenJ9 do AOT), .NET has NativeAOT, .NET Native, Mono AOT, IL2CPP.

Otherwise, D, Nim, Delphi, FreePascal.

Nim [0] feels like it ticks a lot of those boxes. Although it has Options I do not think the machinery is as robust as Rust where that feels like a foundational core of the language.

[0] https://nim-lang.org/

Java with Graal.
I once liked Go a lot, then over time got frustrated by few things and moved on. Now I'm all about "Rust is the best thing since sliced bread!"

I find it a bit hard to understand why people are critizing Go "yaddayadda oh how wrong this decision is yaddayadda". Like, who cares. Let them do how they like it. Nobody is forcing anyone to use the language.

Go's design decisions are not a universally applicable law or anything that you must follow even if you don't like them. I mean, if you're using the language and dislike this decision, then it's a bit different story and I think you should influence the change through the official channels. Probably you've done it already.

Fact of the matter is that there are TONS of people out there (and here too) who agree with those decisions, keep using the language and enjoy it. And for that I'm happy for Go!

> I find it a bit hard to understand why people are critizing Go "yaddayadda oh how wrong this decision is yaddayadda". Like, who cares. Let them do how they like it. Nobody is forcing anyone to use the language.

A choice of programming language affects people other than the ones who decided on it. An obvious example is bug fixing and other maintenance; if for instance I find a bug in syncthing and want to fix it, I have to learn Go, because that's the programming language they chose. Another example is finding work; if most employers have systems written in Java, and you don't want to be picky about it, you have to learn and use Java. And finally, the popularity of a language directly affects its ecosystem's health, and since there's a limited amount of programmer time in the world, one language becoming more popular implies other languages become less popular (and after some point, their ecosystems begin to suffer).

Given all that, it makes sense that even people who do not like Go want its designers to make good choices (and get annoyed when they make bad choices), since its popularity means that there's a good chance that they will have someday to use it.

> Nobody is forcing anyone to use the language.

Except when your employer does

Nobody is forcing you to choose that employer
The secret to wrapping errors is the erros.As and errors.Is special interfaces called out only in the documentation: https://pkg.go.dev/errors#Is

You must build a Directed Acyclic Graph (DAG) to ensure these special interfaces don't hit an infinite loop.

To stop the infinite loop cycles, and actually implement a DAG, I marked visited nodes and returned `false` if the nodes were already marked.

If you’re needing to implement this I’d highly suggest your code could be restructured to eliminate the issues. This is a code smell.

I’ve been using errors.Is since day one and have never once needed to implement anything like you described. I define sentinel errors in my root “business logic and types” package, and my child packages use those special errors in their own error Is() methods.

I like the simplicity of Go but it is unfortunate that Rob and Ken were spooked by sum types and pattern matching.