Hacker News new | past | comments | ask | show | jobs | submit login

> This is something I had not discovered yet

No doubt because the Go team disagrees. They have been abundantly clear that, from their point of view, goroutines should be used and even used as part of the public API when it makes sense.

That said, it is still probably really good advice for newcomers who won't have a good understanding for the cases where it does make sense, and especially because goroutines are the shiny thing every newcomer wants to play with and will try to find uses that don't make sense just to use it. As a rule, you don't want to force the caller into things they might not want. In the vast majority of cases, a synchronous API is what you will want to give them as it is the most versatile.

And, really, that's something that applies generally. For example, in the case of the common (T, error) pattern, you will want to ensure T is always useful even when there is an error. The caller may not care about the error condition. That's not you for, the library author, to decide. The fewer assumptions you can make about the caller the better.




> For example, in the case of the common (T, error) pattern, you will want to ensure T is always useful even when there is an error. The caller may not care about the error condition.

This applies to maybe 0.1% of functions. The overwhelming majority of functions, in the Go stdlib as well as real projects, that return (T, error) return an empty meaningless value for the error cases.


Not my experience. Some very early code did not recognize this, but since then pretty much everyone has come to agree that values should always be useful. If you are writing a function today, there is no reason to not observe this.

In practice, that typically means returning the zero value. To which idioms suggest that zero values should be useful. Rob Pike's Go Proverb[1] even states: "Make the zero value useful." Most commonly when returning (T, error) that zero value is nil. In Go, nil is a useful value!

If the caller wants to observe the error state, great. But it is needlessly limiting if you force it upon them. That is not for the library author to decide.

[1] https://go-proverbs.github.io


One problem I've found as a newcomer to Go (and I'm perfectly willing to accept that I just haven't developed the right "language mindset" yet) is that the zero value can be problematic—particularly for scalar types—because it's often a perfectly valid value in a model where you need a way to indicate an invalid value.

Obviously if there is a possibility of invalidity, you would expect the caller to check the error, but the fact that I always have to return something as the callee, and always have to make sure I'm not accidentally using the value in error conditions as the caller, is just asking for mistakes to me.

I appreciate that it's not the path Go has chosen to tread, but I find Result<T, Error> to be so much more of a foolproof pattern than (T, error), especially considering prevention-of-foot-shooting is an established Go design goal.

(Equally obviously you could use a pointer and return nil, but I find that muddles the semantics, because there are multiple reasons you might opt to use pointers besides the ability to express "no value".)


Given (T, error), what do you return for error when no error occurred? When error is "invalid"? The caller is, no doubt, expecting you to be consistent, so the answer for T no doubt lies therein.

There is nothing special about errors.


If the zero value is valid, I usually just use a pointer to the scalar type in question


Zero and nil values are almost always bogus. The Go language itself doesn't even respect that proverb: the zero value of a map is not a useful map.

There are some rare cases where a 0 value is actually meaningful in some way. But even for types where it is fully functional like integers, it's often not meaningful in the specific context it is used.


I've seen just two APIs that returned non-nil/non-default T (representing the partially completed work) with a non-nil error, and those were a constant source of bugs and errors. I've changed those to always return dummy empty T, and even though the retries now hurt performance more (they could not re-use partial completed result), it was a much more straight-forward code.


Practically speaking, the (T, error) pattern is pervasive because there isn't any other alternative. Go simply lacks sum types.

> Not my experience.

To what experience do you speak of? My 5,000+ hours in kubernetes and terraform space tells me Rob Pike's views are fan fiction at best.


Let's be real, Kubernetes is a Java project with code that just happens to share some resemblance to Go syntax. It's also one of the oldest projects using Go, long predating the "make zero values useful" proverb, so it is not surprising that it doesn't follow the idioms recognized today. Idioms cannot be conceived in advance. They emerge from actual use after finding out what works and what doesn't.

What new code being written today is violating that pattern?


I usually return zero values just because its easy, not because its useful. I don't expect the caller to use the return value if err !=nil and haven't heard anything to the contrary on my team. If Go were a more powerful language, we would be returning Either[A,B] not multiple return values, which would guarantee that you rely on one or the other, not some weird in-between case.


> I don't expect the caller to use the return value if err !=nil and haven't heard anything to the contrary on my team.

Yet you admit to following the advice for error, returning the zero value for err and making it useful when you do. If you don't have a meaningful error state, why not just return junk? Clearly you recognize the value of making the return values useful, always. Why make exceptions?


No. And GP explictly said they don't tend to make it useful, but only do it when it's easy.

Making the return value of e.g. a database handle always "useful" is a ridiculously dangerous idea that can lead to application bugs further down the route becaause some list/get returned an empty value to continue the pattern of "useful" empty values.

The main reason there is ever a useful error next to a non-nill err is because go doesn't have a useful way to not do it.


if I need to return some person,error how do I return junk for the person? I just return person{}, error. I guess I could fill person out with a bunch of silly values but why would I do that work? If there was some easier way to make a person and it was filled with junk, I wouldn't hesitate to use it because the caller would never use the value.


Logically, in that case you would return nil, just like you do for error. There is no person to return. nil is how Go signifies the absence of something. nil is useful, as proven by error. Why make exceptions?

It’s funny how people forget how to write software as soon as the word error shows up. I don’t get it.


On top of the issue with nil not being a useful value for most types

Nil requires pointer values. I.e. it's impossible to know whether something is a pointer to allow for nil, or because a copy would be prohibitively expensive and therefore references are used, or even because it's into a mutable structure.

Go's overlapping of implicit nullability and by value/by reference marker make it entirely useless to build information into APIs / necessarily promotes the value into a different type to use.


Because nil panics on member accessors... It's the opposite of what you claim to be the standard in go.

Thanks for demonstrating that you forget how to write software around erros.


What are you returning for error in its “junk” state, then? Clearly not nil, else by your assertion your code will panic. error has member accessors you will call - Error() if nothing else.

Methinks you’ve not thought this through. What’s it about the word error that trips up programmers like this?


That's not what my team of gofers decided. Apparently zero is better than nil. I know how to write code but Go is its own thing. I mean the idea of returning multiple values is totally goofy in itself.


> Apparently zero is better than nil.

Zero often is better than nil. Consider something like atoi. If it fails, 0 is often exactly what you want. No need to care about any error state. Although the error state is there if your situation is different. The caller gets to choose.

But for something like a person that doesn't exist, nil is almost assuredly the appropriate representation. You didn't end up with an empty person, or a made up person, you ended up with no person. nil is how the absence of something is represented. Same reason you return nil when there is no error.

There seems to be no disagreement that nil is the proper return value for cases where there is no error. Why would no person be different?

> I mean the idea of returning multiple values is totally goofy in itself.

It is, but then again so is accepting multiple inputs. Neither is mathematically sound, but they have proven useful in practice.


> Consider something like atoi. If it fails, 0 is often exactly what you want. No need to care about any error state.

No, 0 is a bogus value if atoi() failed. 5 would be exactly as appropriate. If I'm parsing a form to find a user's age and they entered "old", their age is definitely not 0. I can't even imagine a scenario where I'd care what value atoi() returned if it returned an error.


> No, 0 is a bogus value

No. 0 is the integer's (what i in atoi identifies) zero value, which carries the expectation of being useful.

The problem here is that your example is using the wrong type. Age is not an integer, it is an age. Use an age type that defines the proper semantics for age when you what you have is an age.

Not even the best type system can stop a bad programmer choosing to use the wrong types. Stop being a bad programmer, I guess. No amount of tooling can fix that problem, I'm afraid.


0 is a perfectly good age, plenty of humans have been 0 years old. The point is simply that the first return value of atoi() if the second is non-nil is meaningless. Atoi would have been exactly as useful if it had been defined that atoi() returns 167 and an error if it can't interpret the string as an integer. Code which proceeds to use the first return value of atoi() if the second one is non-nil is wrong code, even if it happens to work for some convoluted scenarios.

Edit to note: atoi() actually doesn't always return 0 if it fails: if the second return is err.Err=ErrRange, then the first is the max value that can fit on 32 bits.


another example is when reading from a file using io.Reader. EOF is returned as an error, but you still need to check the slice for any new bytes that we read before the EOF. A lot of errors are actually good to have, and still return critical data despite being an "error".


yeah I get it. you're talking common sense but I'm coding in Go.


Yes, the biggest mistake Go made was introducing the error keyword.

It should have used banana. If it were (T, banana), nobody would have trouble with these concepts. There's just something about the word error that causes programmers to lose their mind for some reason.


> What new code being written today is violating that pattern?

You are putting the burden of proof on me now? How unfair, You didn't bring any. Go to CNCF and pick anything written in Go.

> Let's be real, Kubernetes is a Java project

Let's be real, Rob Pike is the flat earther of PLT. Sum types are Rob Pike's Foucault pendulum.


> You are putting the burden of proof on me now?

No. I don't give a shit about what you do. Where did you dream up this idea?

> Let's be real, Rob Pike is the flat earther of PLT.

No doubt, but when using the programming language of flat earthers, one has to accept that the particular world is, indeed, flat.

But the advice is undeniably sound. There is no programming language where you should leave someone hanging with junk values. You might avoid junk in other languages using some other means (e.g. sum types), but it is to be avoided all the same.




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: