
Griping about Go - dmit
https://https.www.google.com.tedunangst.com/flak/post/griping-about-go
======
cdoxsey
The whimsical nature of interfaces is definitely different coming from other
languages. An `io.Flusher` might be nice, but it's not really necessary. You
can just write:

    
    
        type Interface interface {
            io.Writer
            Flush() error
        }
    
        func someFunction(w Interface) error {
            w.Write(nil)
            w.Flush()
            panic("etc...")
        }
    
    

Or even drop the type name:

    
    
        func someFunction(w interface {
            io.Writer
            Flush() error
        }) error {
            w.Write(nil)
            w.Flush()
            panic("etc...")
        }
    
    

But maybe that looks too weird.

~~~
rbrtl
This is something I'm still struggling to grok. The interface idioms feel so
different to my Java instincts, like loose fitting clothes after years of
skinny jeans.

~~~
skj
Basically, the interfaces get defined where they're _used_ , not where they're
implemented.

------
pcwalton
What's worse, defer's function-scoped nature means that the only way to
compile it is to dynamically push closures onto a stack _at runtime_ and then
pop them off one-by-one before returning. The compiler may be able to optimize
this in specific cases, but in the general case the semantics are extremely
dynamic for no real benefit. Designing defer in this way is an especially
strange decision for a language that in many ways is architected to make life
easy for the compiler writer.

~~~
Thaxll
I don't understand what you're saying but defer cost a few ns so it's very
fast and usually not a performance problem. ( ie: don't use it in hot path )

~~~
cyphar
I haven't tested this since then, but this wasn't the case in 2014[1]. I
haven't noticed these issues since then, but back then it cost hundreds of
microseconds -- 8 orders of magnitude more than "a few ns".

EDIT: I just re-did the relevant tests on Go 1.11.5. It's significantly better
than in 2014 but it still costs between ~20us and ~50us ("only" 4 orders of
magnitude more than "a few ns").

    
    
       goos: linux
       goarch: amd64
       BenchmarkPut-8             50000             27502 ns/op
       BenchmarkPutDefer-8        30000             46774 ns/op
       BenchmarkGet-8             50000             29812 ns/op
       BenchmarkGetDefer-8        20000             89701 ns/op
       PASS
    

[1]:
[https://lk4d4.darth.io/posts/defer/](https://lk4d4.darth.io/posts/defer/)

~~~
yalue
It actually did get significantly better in Go 1.8, in early 2017. See
[https://golang.org/doc/go1.8](https://golang.org/doc/go1.8) ("The overhead of
deferred function calls has been reduced by about half.").

However, I think this change was mostly just an optimization for common cases
with few arguments or no closures (e.g. "defer f.Close()").

~~~
cyphar
I just re-did the benchmarks with Go 1.11.5, it is much better but it's still
4 orders of magnitude more than "a few nanoseconds".

------
barrystaes
The article is on a suspect domain:

    
    
      https://https.www.google.com.tedunangst.com/flak/post/griping-about-go
    

Is there any legit reason to have https.www.google.com in there?

~~~
draw_down
I think that’s what we call “a joke”.

~~~
comex
FYI, you appear to have been shadowbanned for a while.

~~~
nicoburns
How come we can see his posts if he's been shadow banned?

~~~
comex
For the comment in this thread, I used the "vouch" link to reinstate it
(requires a certain amount of karma AFAIK), so it's no longer "dead", i.e.
hidden. But most of the rest of their comments in the last two weeks are still
dead. Anyone can opt in to seeing such posts by turning on "showdead" in their
profile options.

------
Animats
Automatic closeout remains a hard problem. "Defer" has the same problem as
RAII - what if the closeout fails? Python's "with" clause, and how it
interacts with exceptions, is one of the few constructs that can handle
multiple closeout faults correctly.

Trying to get rid of exceptions seems to force workarounds that are worse than
exceptions. C++ and Java exceptions were botched and gave the concept a bad
name. Go's "panic" and "recover" are an exception system, but not a good one.
Python comes closer to getting it right.

Key concepts for successful exceptions:

\- A predefined exception hierarchy. Catch something in the tree, and you get
everything below it. Python added this in the 2.x era, and it made exceptions
usable. (Then they messed it up in the 3.x era, putting too much stuff under
"OSerror".) This solves the problem of "Have I caught everything"?

\- The case where a closeout event raises an exception has to work. This is
hard. Attempts to get it right resulted in such horrors as "re-animation" in
Microsoft Managed C++. It needs something like the Rust borrow checker model
to make sure that object lifetimes are properly enforced on all error paths.

~~~
kjeetgill
Hm. I actually love how Java does exceptions. I think a little better
conventions around when to use checked vs unchecked might help but generally
I'd rather checked exceptions be the norm than wholely absent.

What do you think is missing? My only ask would be syntax to capture an
exception Future/Expression style for smaller use cases.

~~~
cytzol
I think this is what you’re asking for with your last sentence, but I’m not
sure so I’ll say it anyway:

My main gripe with Java’s checked exceptions is that there’s no way to bubble
up a checked exception from inside a functional interface, because the
function signatures differ. For example, if you want to close a bunch of
files, you can’t do `files.forEach(file -> file.close())` if the call to close
throws IOException — you need to use an iterator (which is longer) or catch
and re-throw it as a RuntimeException (which is _much_ longer). Libraries like
JOOL can help here, but not completely.

It also doesn’t help that certain methods are declared as throwing exceptions
that aren’t actually thrown, such as `ByteArrayOutputStream#close()`.

I used to argue in favour of checked exceptions, but once I switched to
Kotlin, I realised that I didn’t actually miss them at all. However, my other
favourite language (Rust) uses something a lot closer to checked than
unchecked, so maybe I’m just writing different kinds of code on the JVM to let
me get away with having this opinion!

------
LordHeini
I think go has some serious other problems.

Like the billion dollar mistake. Why is this repeated in any new language? It
is just plain awful and stupid. This is easily my biggest gripe with the
language (apart from missing generics).

Go combines that greatly with the bonkers error handling:

    
    
      if err != nil {...}
    

Half of all go code ever written consists of the line above.

Now combine that with defer (or go routines) returning errors...

------
hombre_fatal
Here's one of my favorite gotchas in Go: why does this error?

[https://play.golang.org/p/6LTbtuocu5-](https://play.golang.org/p/6LTbtuocu5-)

~~~
echlebek
It errors because the type of `err` is `error`, not *formatError. That's why
you are supposed to return the error interface, instead of a concrete type.

The value ends up being a non-nil interface value that holds nil.

To avoid encountering this issue, return `error`, not something else.

[https://play.golang.org/p/jZ5Fa24bbUz](https://play.golang.org/p/jZ5Fa24bbUz)

~~~
hombre_fatal
> To avoid encountering this issue, return `error`, not something else.

But best practice is to return concrete types.

Also, `err` is just an example in this minimal snippet. It can happen with
_any_ variable.

Here's a similar snippet:
[https://play.golang.org/p/WKPey_PL_ht](https://play.golang.org/p/WKPey_PL_ht)
\-- Looks impossible to crash. :P

Spoiler: it's a boxed null, not a null.

~~~
thegeekpirate
> But best practice is to return concrete types.

You're taking this as an absolute when you shouldn't be. There's plenty of
places in the standard library that don't follow this "rule".

------
rhacker
I heard it's better now but when I was trying to do grpc I couldn't get this
to build:

[https://github.com/improbable-eng/grpc-
web/tree/master/go/gr...](https://github.com/improbable-eng/grpc-
web/tree/master/go/grpcwebproxy)

Basically there was dependency that changed, and it caused it to not build.
The maintainer was just pointing fingers at google. I had no idea what to do,
but it just scared the crap out of me.

------
ernsheong
Blog seems to have collapsed under HN weight (link is bad too).

Archive of archive of page:
[https://app.pagedash.com/p/d5c8c4bf-d88a-470b-a7f3-adb986ccb...](https://app.pagedash.com/p/d5c8c4bf-d88a-470b-a7f3-adb986ccb1fe/4tbHK69h6Z0EaM51gvUm)

------
favorited
Is there a reason that go's defer is only function-scoped?

~~~
hcnews
All variables in Go are function-scoped. They are just being consistent with
the overall design in this case.

~~~
cdoxsey
Variables are lexically scoped by blocks:
[https://golang.org/ref/spec#Blocks](https://golang.org/ref/spec#Blocks)

------
atilaneves
> as opposed to passing large byte slices or strings around. In theory, this
> should be more efficient

Why would passing a "large" slice be inefficient?? Maybe on x86 due to a lack
of registers, but on 64-bit?

------
vldo
> Usually this can be resolved by creating a new function and calling that
> from the loop. But frequently not. [etc.]

For me this is the appeal of the language; I really prefer things confined and
manually scoped rather than having things globally scoped which would cause a
lot of debate just around that. You often have to think about what you want to
expose and where, but that's a good thing in my opinion.

~~~
cpuguy83

        for _, f := range fs {
          func() {
            defer f.Close()
          }()
        }

------
LandR
> I mostly like go, but after working with it a bit more I realize there are a
> few jibs of which the cut I do not like.

What is this supposed to parse to?

~~~
logicchains
I believe the author meant "a few jibs the cut of which I do not like".

~~~
LandR
> [https://www.merriam-webster.com/dictionary/jib](https://www.merriam-
> webster.com/dictionary/jib)

Looking up jib on a dictionary, none of the definitions sound like they make
sense in this context at all.

Oh well.

~~~
gmac
[https://www.urbandictionary.com/define.php?term=I%20like%20t...](https://www.urbandictionary.com/define.php?term=I%20like%20the%20cut%20of%20your%20Jib)

------
nvarsj
Defer also encourages the unsafe behavior of not checking error results. You
can't propagate errors from it. About the best you can do is wrap whatever
function you were deferring in another function, and then panic if there is an
error.

~~~
shabbyrobe
You can actually propagate errors from it. It's not pretty but it works:

    
    
        func Pants() (rerr error) {
            defer func() {
                if err := doStuff(); err != nil && rerr == nil {
                    rerr = err
                }
            }()
            // ...
            return nil
        }
    

I use this function all the time with `io.Closer` implementations:

    
    
        func DeferClose(err *error, closer io.Closer) {
            cerr := closer.Close()
            if *err == nil && cerr != nil {
                *err = cerr
            }
        }
    
        func Pants() (rerr error) {
            f, _ := os.Open(...)
            defer errtools.DeferClose(&rerr, f)
            // ...
            return nil
        }

------
abbiya
what kind of domain is this ?

[https://https.www.google.com.tedunangst.com/flak/post/gripin...](https://https.www.google.com.tedunangst.com/flak/post/griping-
about-go)

------
dekken_
#notasuspectURL

~~~
dmit
What do you mean? I'm a tech-savvy person, so I checked that the lock was
green before I submitted my financial credentials.

------
dana321
I avoid using defer

~~~
echlebek
The other commenter wasn't very kind towards you, but I'd definitely encourage
you to use defer to get correct semantics around releasing resources. It's the
best tool that the language gives you for that.

~~~
dana321
Sorry, but for me its not a great method of releasing resources.

I tend to (for example) open a connection in one function, do something with
it in another function, close it in another. I'm not writing traditional go
programs, i wrap things in an api.

~~~
kazinator
The higher level procedure which uses these three functions could use _defer_
to ensure that the closing function is called.

~~~
dana321
I would have to re-arrange my call stack so that the read or write functions
would be inside the open statement, then it would close it at the end anyway
so its kind of pointless for my uses.

I try and totally avoid panics and never throw them unless its at some initial
parser stage. But this is made me rethink the structure of how some of my tags
operate in my interpreter / transpiler.

