Live data from Hacker News

Go subtleties

harrisoncramer.me

41–50 of 190 posts

Re: Go subtleties

#41
post #19

Earlier quoted context omitted.

I'm amused by posts like this because it shows that Go is finally slowly moving away from being an unergonomically simplistic language (its original USP?) to adopt features a modern language should have had all along. My experience developing in it always gave me the impression that the designers of the language looked at C and thought "all this is missing is garbage collection and then we'll have the perfect languag…

If you think Go and C are that similar then you don't know either.

They are similar in the sense that there are very few abstractions, relying on the programmer to reimplement common patterns and avoid logical mistakes.

You have to put thought into such things as:

- Did I add explicit checks for all the errors my function calls might return?

- Are all of my resources (e.g. file handles) cleaned up properly in all scenarios? Or did I forget a "defer file.Close()"? (A language like C++ solved this problem with RAII in the 1980s)

- Does my Go channel spaghetti properly implement a worker pool system with the right semaphores and error handling?

Re: Go subtleties

#42

Go has certainly come a long ways from its initial mission to be a simple language for Rob Pike's simple coworkers. type User struct { Name string `json:"name"` Password string `json:"-"` Email string `json:"email"` } So you can specify how to serialize a struct in json using raw string literals containing arbitrary metadata. And json:"X" means to serialize it to X, except the special value "-" means "omit this one,"…

Of all the things one might critique Go for as not being simple, I'm not sure this is it. I've never needed to serialize "-" as a key in JSON but `-,` makes some sense given the general pattern of field tags, e.g. `json:"name,omitempty"`

Re: Go subtleties

#43
post #9

Earlier quoted context omitted.

Yes, that'a bit too late after ten+ years perhaps but I wished we had a nil type and checking whether the interface is empty was a type assertion. In all other cases, like any(2) == 2, we compare the values. Then again that would mean that the nil identifier would be coerced into a typed nil and we would check for the nilness of what is inside an interface in any(somepointer) == nil. wrt the current behavior, it also…

Agree the ship has likely sailed, but if it could be addressed wouldn't it be nice to remove nil value interfaces altogether? Maybe start by letting new interface types declare/annotate that they don't box nil values? Then one day that becomes the default. Oh well.

Oh that's probably doable. Introducing something like this is a bit orthogonal to the point above, but yes.

It's not straightforward but probably something that will be considered at some point I reckon when thinking about making union interfaces first class. That will require to track a not nil typestate/predicate in the backend, something like that I guess.

Re: Go subtleties

#44
post #10

Earlier quoted context omitted.

The advice I've read (and follow) is always to return values, not interfaces, from functions and test for nil against them. That IME tends to nip the majority of nil interface problems in the bud.

That works until 1) you don't want to export the value types 2) the return values aren't simple structs but slices or maps because []x is not a []X even if x implements X.

For 1/, you can return a struct value type without exporting it. If it satisfies the receiving interface they won’t have a problem.

That’s exactly the pattern I use for most Go development

Re: Go subtleties

#45

Go has certainly come a long ways from its initial mission to be a simple language for Rob Pike's simple coworkers. type User struct { Name string `json:"name"` Password string `json:"-"` Email string `json:"email"` } So you can specify how to serialize a struct in json using raw string literals containing arbitrary metadata. And json:"X" means to serialize it to X, except the special value "-" means "omit this one,"…

I never liked the concept of struct tags, it's a kind of stringly typed programming where the meaning of X or - depends entirely on what the json package says it means. An alternative is to introduce something like annotations, but I'm sure there will be resistance as it makes the language lean closer to e.g. Java. But my take on that is that if you want stricter typing like that, you should actually go to Java or C#…

Java resisted first party support of annotations. It was a very controversial addition in the early 2000s

Support for the types of metaprogramming/metadata that annotations are used for is a useful attribute of languages in general

Re: Go subtleties

#46
post #43

Earlier quoted context omitted.

Agree the ship has likely sailed, but if it could be addressed wouldn't it be nice to remove nil value interfaces altogether? Maybe start by letting new interface types declare/annotate that they don't box nil values? Then one day that becomes the default. Oh well.

Oh that's probably doable. Introducing something like this is a bit orthogonal to the point above, but yes. It's not straightforward but probably something that will be considered at some point I reckon when thinking about making union interfaces first class. That will require to track a not nil typestate/predicate in the backend, something like that I guess.

Having pondered on a bit more.. I think it's the struct that would declare that it's not usable as nil, and that in turn would tell the runtime not to box it if it's nil. That would also help the compiler (and copilot etc) spot calls on nil pointers which will panic.

Re: Go subtleties

#47
post #10

Ah the old nil values boxed into non-nil interfaces. Even after 8 years writing go code almost every day this still bites me occasionally. I've never seen code that actually uses this. I understand why it is the way it is but I hate it.

The advice I've read (and follow) is always to return values, not interfaces, from functions and test for nil against them. That IME tends to nip the majority of nil interface problems in the bud.

On the happy path downstream, yes, and it does work really well. But the error flow back upstream flips that, as errors are returned as, often nested, interfaces.

This is fine for a lot of general purpose code that exits when running into problems. But when errors are an expected part of a long lived process, like an API, it’s painful to build logic around and conditionally handle them.

The ergonomics of errors.Is and As are pretty bad and there doesn’t seem to be a clear indication as when to expect a sentinel, concrete, or pointer to a concrete error.

All that to say, I think Go’s errors really illustrate the benefit of “return values, not interfaces”. Though for errors specifically, I’m not sure you could improve them without some pretty bad tradeoffs around flexibility.

Re: Go subtleties

#48
post #24

Earlier quoted context omitted.

And code with zero ability to do fancy trickery ("expressive" as some people like to say) is easy to read even if the codebase - or even the language - is unfamiliar. Which is really handy when shit's on fire and you need to find the error yesterday. You can just follow what happens instead of trying to figure out the cool tricks the original programmer put in with their super-expressive language. Yes, the bug is on…

I know it's not exclusive to Go or any language, but you can most certainly write incomprehensible code in it. If anything, expressiveness and proper abstractions can save you from this. I think people often get burnt by bad abstractions in expressive languages, but it's not a problem of the language, but the author's unfamiliarity with the tools at their disposal. If someone starts being clever with abstractions bef…

I like to say this: "Only my code is allowed to be clever"

But, on a serious note, I agree with you. Go lacks a lot of power, especially in its type system, that causes a ton of problems (and downtime) that in other languages is trivial to prevent statically.

Re: Go subtleties

#49
post #32
post #20

Earlier quoted context omitted.

This advice of "returning concrete types" is in most cases a horrible anti-pattern that prevents evolution due to lack of information hiding. It has been also deliberately broken in the standard library in several places. This "advice" cannot be generically applied. Places where it is has been deliberately broken: net.Dial (Conn, error) image.Decode(r io.Reader) (Image, string, error) sha256.NewXXX() hash.Hash flate.…

On the contrary, in recent proposal reviews, returning interfaces has been discouraged unless you're trying to make a generic interface like fs.FS, or dispatch functions like net.Dial / image.Decoder. The advice of returning concrete types is paired with defining interfaces when you need them on the consumer side. It's returning interfaces that prevents good evolution, since the standard library will not add methods…

It seems there is a dichotomy in the real implemented world and your hypothetical advice world.

Due to lack of native support of defaults for optional methods , many interfaces in Go are using hacks for optional methods added by evolution.

The Value interface has a `IsBoolFlag()` optional method not part of the interface signature

The other way for evolution is just add sub-interfaces. Like `io.WriterTo` and `io.ReaderFrom` which are effectively just extensions of `io.Writer` and `io.Reader` with `WriteTo` and `ReadFrom` methods - which are checked for in consumers like `io.Copy`.

Anyways, my point was specifically about generic interfaces and alternative implementations, so it appears you agree.

Re: Go subtleties

#50
post #20

Earlier quoted context omitted.

Return concrete types, accept interfaces. Returning interfaces hides behavior and hampers evolution; accept interfaces so callers can swap implementations. For testing, mock the dependency, not the return value. Loudest arguments against returning concrete types were on the terraform core team and the excuse was it makes testing easier. I disagree.

This advice of "returning concrete types" is in most cases a horrible anti-pattern that prevents evolution due to lack of information hiding. It has been also deliberately broken in the standard library in several places. This "advice" cannot be generically applied. Places where it is has been deliberately broken: net.Dial (Conn, error) image.Decode(r io.Reader) (Image, string, error) sha256.NewXXX() hash.Hash flate.…

Like anything else there are exceptions to the rule. Pointing to the standard library is a weak position because it is consistently inconsistent.

Go's standard library interfaces (like net.Conn) earned their place.

Premature interfaces calcify mistakes and that's what the guideline pushes back on.

Post reply on HN