Griping about Go
https.www.google.com.tedunangst.com
https.www.google.com.tedunangst.com
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.However, by having them be function scoped, this isn't so anymore; e.g, a defer occurring in an if() statement needs to happen at the end of the function, but only if the if occurs. If you loop over a defer, we need to accumulate those. (And the golang tour even explicitly calls the behavior out.[1]) So, instead of just running the defer at the end of the if / inside the if statically, we need to push the defer onto a runtime stack of yet-to-be-run defers that we'll evaluate at the end of the function. This now has to happen at runtime, not compile time, and makes the function compilation more complex, and requires a stack somewhere to push this stuff onto.
From a compiler writer's perspective, I would agree w/ the parent: this seems much more complex, and runs counter to golang's otherwise simple design philosophy.
†Though the linked list that the compiler has to generate could hit malloc, which costs more than a few nanoseconds.
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/However, I think this change was mostly just an optimization for common cases with few arguments or no closures (e.g. "defer f.Close()").
Putting the defers on the stack is also part of how the runtime unwinds the stack during a panic.
All exception ABIs on all major platforms can do this without any overhead in the no-exception case. The compiler embeds static metadata (in a subset of DWARF, on Linux) alongside the function, and the unwinder parses that metadata in order to determine which destructors to invoke.
If you're using recover, it's probably time to rewrite the func.
I say this because Go forces you (minus just ignoring with _) to error check; so panic's shouldn't happen to start with.
Errors are for when the input (specifically the I/O) is wrong, a precondition isn't met, or some other error that doesn't mean there is something fundamentally wrong with your code.
If to make the problem go away you need to fix the code, panic is OK.
fmt.Println("foo")
Where did golang force you to error check?How about (taken from here: (https://www.reddit.com/r/programming/comments/ak305l/goodbye...):
r1, err := fn1()
r2, err = fn2()
if err != nil {
return err
} 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?Looking back for a few pages before that, I don't see any moderators warning him. He does seem to post an awful lot of single-sentence replies, so maybe it was automated?
I have my own concerns about the lack of transparency around moderation & banning.
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.
What do you think is missing? My only ask would be syntax to capture an exception Future/Expression style for smaller use cases.
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!
I like Go's use of a single error type in most public API's. Combine with checked exceptions and you would have two kinds of functions: those that can fail and those that can't. It keeps the "what color is your function" problem to a minimum.
Naturally one just blames the one that actually got famous using them.
IDE support makes this nicer, and Java's tooling is certainly first-rate in my experience.
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...
The value ends up being a non-nil interface value that holds nil.
To avoid encountering this issue, return `error`, not something else.
Shadowing in a different scope allows it to work properly as well.
I admit that I've never actually run in to this issue in practice as I generally return custom errors as `error`, but I can see why it would confuse someone.
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 -- Looks impossible to crash. :P
Spoiler: it's a boxed null, not a null.
You can always unwrap it via type assertion, however ugly that may be.
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".
Except for errors. Use the `error` interface.
I've never really cared about Generics, but having nullable types is a real mistake to me.
Nullable types by themselves are fine, the language and the compiler just needs to make handling “is X null?” work the same way as option types do - or better yet: languages were a variable is only in scope if it is not null.
https://play.golang.org/p/N_3BiUOkRJo
It's probably the first and last to have this quirk.
https://github.com/improbable-eng/grpc-web/tree/master/go/gr...
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.
Archive of archive of page: https://app.pagedash.com/p/d5c8c4bf-d88a-470b-a7f3-adb986ccb...
loop i over whatever {
defer foo(i)
}
I.e. a loop's iterations can put things into the defer list, which is then executed when the function exits. Whereas if the defer list were block scoped, then each defer would execute immediately on the termination of its enclosing iteration.Also, there is problem with conditional defers like:
if (condition)
defer whatever
Okay, so that is in the surrounding scope. Now I need to add some piece of logic: if (condition) {
defer whatever
piece of logic
}
Now it's scoped to the curly braces and executes immediately after "piece of logic" is done?In terms of performance, the defer list has to be an actual run-time object; the compiler can't always optimize away the existence of the defer list. If defer is block-scoped and used in numerous nested scopes of the same function, where the compiler isn't able to remove it, then you get multiple defer list instantiations in the function's stack frame, which is a kind of bloat. These have to be initialized to the empty state on each entry into a block. At most defer list to initialize on entry into a function is less time and space overhead.
A more flexible design would be defer to work with named blocks:
foo: {
bar: {
defer h() foo;
defer g() bar;
defer f();
}
}
h() is deferred to the termination of foo (thus using foo's defer list); likewise g() in relation to bar, and the f() defer is function scoped.Why would passing a "large" slice be inefficient?? Maybe on x86 due to a lack of registers, but on 64-bit?
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.
for _, f := range fs {
func() {
defer f.Close()
}()
}What is this supposed to parse to?
Looking up jib on a dictionary, none of the definitions sound like they make sense in this context at all.
Oh well.
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
}https://https.www.google.com.tedunangst.com/flak/post/gripin...
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.
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.