4 ms·
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
by pcwalton 8y ago
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.
- cremp 8y agoTo add on; recover itself is a nice code-smell. 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.
- echlebek 8y agoWell, panic should mostly be happening as a result of nil pointer deref, slice out of bounds, etc. You wouldn't want those types of operations to return error values.
- d0100 8y agoBut for those you can do a manual bounds & pointer check.
- skj 8y agoYour code is allowed to panic if there is a bug. For instance, the caller passed in a nil pointer where there needed to be actual data. 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.
- cpuguy83 8y agoThe problem is that it doesn't really force you to do error checks... and really most code I've seen doesn't really handle the error either so much as just bubble it up like a mini-exception.
- apta 8y ago> I say this because Go forces you (minus just ignoring with _) to error check; 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... https://www.reddit.com/r/programming/comments/ak305l/goodbye...): r1, err := fn1() r2, err = fn2() if err != nil { return err }
- agumonkey 8y agoAre they even aware of it ? I mean like ML discussions about it.
- Thaxll 8y agoI 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 )
- deathanatos 8y agoIf I am understanding him correctly: If defer were statically scoped, that is, if it were scoped to curly braces, you would "know" at compile time when and if a defer is going to run (relative to that scope). That is, a defer boils down to just "at the end of this scope, run this code" and nothing more, and all the compiler has to do is move that code/function call to the closing curly brace. (Similar to how a C++ compiler knows when to run a destructor for an object.) 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. [1]: https://tour.golang.org/flowcontrol/13 https://tour.golang.org/flowcontrol/13
- pcwalton 8y agoIt's not that defer is slow in absolute terms†; it's that it's slower than it needs to be for no good reason. The corresponding C++ feature (RAII) is "free" (as in, it adds no overhead over manually writing the cleanup code), while defer is not. Defer would be easier to use for the programmer, easier for the compiler writer, and faster at runtime if it were block-scoped. †Though the linked list that the compiler has to generate could hit malloc, which costs more than a few nanoseconds.
- cyphar 8y agoI 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/
- rwj 8y agoA common idiom is the defer, for example, file.Close() only if there wasn't an error when the file was opened. You can also put them in an if statement. This means that the deferred function aren't strictly tied to their scope. Putting the defers on the stack is also part of how the runtime unwinds the stack during a panic.
- pcwalton 8y ago> 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.