Gophers, have you ever benefited from defer's scoping behaviour?

agolangf · · 340 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;m talking about the fact that deferred functions execute at the end of the enclosing function, not scope. My question is: have you ever directly benefited from this? Has this ever made your code simpler? (Compared to the behaviour when defer would be executed at the end of the scope.)</p> <p>No is a valid answer too ;)</p> <p>To clarify, this is how it currently works (have you benefited from it?):</p> <pre><code>func openManyFiles(filenames ...string) { for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() // this executes at the end of openManyFiles doSomething(f) } } </code></pre> <p>And an alternative semantics would be:</p> <pre><code>func openManyFiles(filenames ...string) { for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() // this executes at the end this scope = before the next iteration of the for loop doSomething(f) } } </code></pre> <hr/>**评论:**<br/><br/>nosmileface: <pre><p>Yes, but only in one case. Sometimes it&#39;s nice to do conditional termination. For example if your program writes to stdout or to a file. You can make a function with an if statement where in one case you open a file and defer its closing, in another case you don&#39;t open anything, but set your io.Writer to os.Stdout. </p> <p><a href="https://github.com/nsf/bin2go/commit/b4d735d0bf7e15d74f00bce527a4add710b59f56#diff-633e151079a12d77214c0c387e0c0717R100">https://github.com/nsf/bin2go/commit/b4d735d0bf7e15d74f00bce527a4add710b59f56#diff-633e151079a12d77214c0c387e0c0717R100</a></p> <p>While I don&#39;t see uses like that often, it is what it is.</p></pre>nilium: <pre><p>I do this fairly often for CLI tools (with stdin, usually), so it&#39;s been a recurring thing. Scope-based defers would make for a much more annoying flow here of likely needing a nop closer for stdin and deferring scope at the root of the function.</p> <p>Aside from that, there are also cases where I&#39;ll defer cancellation of a context inside of a conditional, but only if there was a timeout or something else configured. That&#39;s another case where I&#39;d need a nop function, since the alternative would be using a conditional and goto to jump over a defer at the root of a function (because the conditional would introduce a new scope for the defer).</p> <p>There&#39;s little to gain, in my opinion, by having scope-based defers. The moment you need one, the correct answer always seems to be to write a new function. (This doesn&#39;t mean there&#39;s nothing to gain – only that I don&#39;t see much from my POV.)</p></pre>cedricnorth: <pre><p>Here is a nice example in influxdb&#39;s optional gzip content-encoding : <a href="https://github.com/influxdata/influxdb/blob/8bb80321d9bfe9f46a07ee43131ad96e355ae336/services/httpd/handler.go#L627" rel="nofollow">https://github.com/influxdata/influxdb/blob/8bb80321d9bfe9f46a07ee43131ad96e355ae336/services/httpd/handler.go#L627</a></p> <p>Rather than scopes in the if it defers closing the compressor at the end of the func </p></pre>faiface: <pre><p>Alright, that&#39;s a nice example. A lonely one, but nice.</p></pre>fishywang: <pre><p>Lonely? I don&#39;t think have <code>defer</code> inside an <code>if</code> or <code>else</code> is that uncommon.</p> <p>Remember that <code>defer</code>is not only for closing resources. You can also use it for logging and other stuff.</p></pre>SteveMcQwark: <pre><p>An example is this code used to enable profiling from the <a href="https://blog.golang.org/profiling-go-programs">Go Blog</a>:</p> <pre><code>var cpuprofile = flag.String(&#34;cpuprofile&#34;, &#34;&#34;, &#34;write cpu profile to file&#34;) func main() { flag.Parse() if *cpuprofile != &#34;&#34; { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } ... </code></pre> <p>This relies on being able to conditionally defer code to function exit, which you couldn&#39;t do if <code>defer</code> were block scoped rather than function scoped.</p></pre>xiegeo: <pre><p>I do see this pattern a lot in my own code. What I often do end up looking like is this.</p> <pre><code>func openManyFiles(filenames ...string) error { for _, filename := range filenames { err := func() error { f, err := os.Open(filename) if err != nil { return err } defer f.Close() return doSomething(f) }() if err != nil { return err } } return nil } </code></pre></pre>ruertar: <pre><p>i do the same thing -- i use an anonymous function to limit a defer&#39;s scope.</p></pre>Hualet: <pre><p>Good iead :)</p></pre>sseth: <pre><p>I do this as well. Seems like a candidate for an experience report for go2.0. Although I don&#39;t have a solution in mind.</p> <p>I don&#39;t understand why defer is tried to a function rather than a scope. For example, in C++, RAII is scope based.</p> <p>I have experience needing to put functions inside loops, but I don&#39;t have experience needing to queue up a bunch of defers in a loop.</p></pre>faiface: <pre><p>A solution could be to just make defer scope based? Being scope-based, it would also be possible to implement defer with no performance penalty at all.</p></pre>ruertar: <pre><p>or add another keyword like &#39;delay&#39;.</p></pre>faiface: <pre><p>So it looks like you would benefit from the alternative semantics, right?</p></pre>xiegeo: <pre><p>I&#39;m not sure, the inner function makes the defer behavior explicit following simple rules. The code is also telling me to refactor it into two functions. The code is verbose, but I don&#39;t see any wast in semantics.</p> <p>What would an alternative look like?</p></pre>faiface: <pre><p>The alternative would be that a deferred function is executed at the end of the scope / curly-braces block. So you could write your code like this:</p> <pre><code>func openManyFiles(filenames ...string) error { for _, filename := range filenames { f, err := os.Open(filename) if err != nil { return err } defer f.Close() doSomething(f) } return nil } </code></pre></pre>titpetric: <pre><p>It&#39;s not an equal example, as you don&#39;t handle doSomething() error return. Is rewriting as:</p> <pre><code>err = doSomething(f) if err != nil { return err } </code></pre> <p>somehow worth the rewrite? I don&#39;t think so, at least not in this particular case, xiegos example is clearer. And since it&#39;s a closure, the go compiler will do it&#39;s best to inline that code, so it&#39;s really just the difference of what you deduce from reading those 10 lines, the actual code output should be very much equal between the two.</p></pre>nevyn: <pre><p>Yes, sometimes there is no failure so the success lives inside the if. Eg.</p> <pre><code>f, err := os.Open(filename) if err == nil { defer f.Close() /// Do something with f but it needs to stay live until we hit a return } </code></pre> <p>Also cases of logging in main like:</p> <pre><code>logf := os.Stderr if loggingFilename != &#34;&#34; { logf, err := os.Create(loggingFilename) if err { /* blah */ } defer logf.Close() } </code></pre> <p>...although in theory leaking a file from main() is &#34;ok&#34;, if you just removed it (dito. defer closing os.Stderr if you moved it out of the block as a workaround).</p> <p>My assumption is that this was done so the compiler didn&#39;t have to optimize out all of the block scope defer checks, and even if it might be better I don&#39;t want it to change.</p></pre>sh41: <pre><p>I haven&#39;t thought about it too much. I&#39;ve read the spec, understood the behavior, and used it as it&#39;s documented. It works well and I&#39;m happy to focus my attention on my task at hand.</p></pre>ohaiyou_meepo_desu: <pre><p>?? That&#39;s like the only way to use them, what do you mean ?</p></pre>faiface: <pre><p>I updated the post with a clarification.</p></pre>piratelax40: <pre><p>your alternative semantics is literally the exact same code with a different comment??</p></pre>faiface: <pre><p>Yes. Semantics = how the code is executed, the meaning of the code. I show two ways of interpreting the same code.</p></pre>piratelax40: <pre><p>hmm, in the context of talking only about the semantics of defer (by adding in the additional code block you make it seem like you&#39;re also trying to demonstrate different syntax), deferring just in the scoped context seems unnecessarily. Generally, scoped areas should be reasonably concise (or often ripe for refactor). </p> <p>If you need the &#39;scoped&#39; symantics wrapped in an iife, as the top poster commented, or just don&#39;t use defer</p> <pre><code> f, _ := os.Open(filename) err := doSomething(f) if err != nil { f.Close() return.... } </code></pre> <p>Granted it could protect a little more against lazy error handling within a given scope.</p> <p>If anything, given people really wanted scoped defers, I&#39;d rather have a second keyword, like <code>scdefer</code> </p></pre>faiface: <pre><p>Just to be sure, the whole function is also a scope. And thanks for an opinion.</p></pre>epiris: <pre><p>For mutexs I think it&#39;s useful more often to not have block scoping as on occasion you may want to wrap acquiring a Mutex in a if statement. But for files it can be a easy trap to fall into I suppose, but I almost always find myself just having openDescriptionOfFiles call a function under it of openDescriptionOfFile. Mostly because I don&#39;t want a bunch of file processing nested in a for loops block because it makes unit testing more annoying as well.</p> <p>If I&#39;m doing something quick / temporarily, it&#39;s never an error to call file.Close multiple times. Go will ensure the underlying file handle is never double freed. So you can run the defer and just call f.Close after processing to prevent lingering file handles.</p> <p>Edit: for your example I would pass filename to dosomething and call it a day.</p></pre>Deltigre: <pre><p>Pretty sure for-loops use a single scope until they complete execution, FYI.</p> <p>Similar behavior caught me off-guard in JavaScript with asynchronous callbacks before I learned about closures.</p></pre>cedricnorth: <pre><p>Here is a nice example in influxdb&#39;s optional gzip content-encoding : <a href="https://github.com/influxdata/influxdb/blob/8bb80321d9bfe9f46a07ee43131ad96e355ae336/services/httpd/handler.go#L627" rel="nofollow">https://github.com/influxdata/influxdb/blob/8bb80321d9bfe9f46a07ee43131ad96e355ae336/services/httpd/handler.go#L627</a></p> <p>Rather than scopes in the if it defers closing the compressor at the end of the func </p></pre>kardianos: <pre><p>defer is function scoped because that allows the implementation to cost significantly less. You don&#39;t put try-catches in tight loops for the same reason you don&#39;t put a <code>func(){defer()}()</code> in a tight loop loop. You get extra frames and calls and checks. If defer was lexical scoped additional frames would need to be created.</p></pre>faiface: <pre><p>That&#39;s not true. Scoped-based semantics allows for much more efficient implementation. You can just statically put the deferred function call to all places where the scope ends, such as break, return, end of scope, ...</p></pre>kardianos: <pre><p>defer must execute even during a panic. So you can&#39;t do that in such a simple way do to how stack unwinding works.</p></pre>tv64738: <pre><p>I benefit greatly from it being well optimized. <a href="https://research.swtch.com/goabstract" rel="nofollow">https://research.swtch.com/goabstract</a></p></pre>ChristophBerger: <pre><p>Defer at function level is useful because a function can contain many return statements, and <code>defer</code> ensures that resources are cleaned up no matter where and when the function exits.</p> <p>A simple block (e.g. a loop as in your example) usually has exactly one exit point (unless using one or more break statements). So you can easily put any cleanup code at the end of the loop body without risking a resource leak.</p></pre>cdoxsey: <pre><p>Just thought I&#39;d mention that most <code>Close</code> implementations are idempotent, or at least tolerant of being called again. You can usually just do this:</p> <pre><code>func openManyFiles(filenames ...string) { for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() // guarantee you don&#39;t forget doSomething(f) f.Close() } } </code></pre> <p>Also FWIW files are closed automatically anyway:</p> <pre><code>// NewFile returns a new File with the given file descriptor and name. func NewFile(fd uintptr, name string) *File { fdi := int(fd) if fdi &lt; 0 { return nil } f := &amp;File{&amp;file{fd: fdi, name: name}} runtime.SetFinalizer(f.file, (*file).close) return f } </code></pre></pre>chmikes: <pre><p>But you don&#39;t know when the finalizer is run. So there is a risk for many open files as garbage. However it&#39;s good to know. </p></pre>0xjnml: <pre><p>Instead of</p> <pre><code>func openManyFiles(filenames ...string) { for _, filename := range filenames { f, _ := os.Open(filename) defer f.Close() // this executes at the end of openManyFiles doSomething(f) } } </code></pre> <p>it should be something like</p> <pre><code>func openManyFiles(filenames ...string) error { for _, filename := range filenames { if err := doSomething(filename); err != nil { return err } } } func doSomething(fn string) (err error) { if f, err := os.Open(fn); err != nil { return err } defer func() { if e := f.Close(); e != nil &amp;&amp; err == nil { err = e } }() // ... do something with f } </code></pre></pre>faiface: <pre><p>That&#39;s not the point.</p></pre>0xjnml: <pre><p>Then what <em>is</em> the point of wrongly using <code>defer</code> in the wrong place?</p></pre>faiface: <pre><p>I&#39;m simply asking whether the current defer scoping is useful and I&#39;m giving two toy code examples to demonstrate what I mean.</p></pre>0xjnml: <pre><p>And the obvious answer is that the current defer scoping is not useful if used incorrectly. But the problem is in using it wrongly, not in how it works. It&#39;s like asking if the integer division is useful and providing an example where division by zero occurs.</p></pre>faiface: <pre><p>The answer is not obvious. However, I don&#39;t see how my post implies that it is useful. I think it&#39;s not useful and my post should imply the same.</p></pre>0xjnml: <pre><p>Asking if wrong code is useful is a question that cannot provide any useful answer beyond the obvious &#34;the code is wrong&#34;.</p></pre>faiface: <pre><p>Alright, just for you: simply read the first paragraph of my post and imagine there&#39;s nothing more.</p></pre>0xjnml: <pre><p>Just for you: It&#39;s okay to propose a different semantics. Making your point by using wrong code as an example to show the current behavior is &#34;bad&#34; is wrong.</p></pre>: <pre><p>[deleted]</p></pre>faiface: <pre><p>Yes. The comment just describes the difference between the semantics of defer.</p></pre>: <pre><p>[deleted]</p></pre>faiface: <pre><p>No, why? It&#39;s just demonstrating how defer works.</p></pre>: <pre><p>[deleted]</p></pre>faiface: <pre><p>You have it quite confused. f is a local variable inside the loop and defer evaluates function arguments immediately, that&#39;s why it closes all files at the end of the function.</p> <p>And well, with the alternative semantics, defering on condition wouldn&#39;t be possible. The question I&#39;m asking is: is it useful?</p> <p>EDIT: I meant exactly what I wrote, just assume I have it correct. I&#39;m comparing two semantics, the actual one, and a virtual alternative one.</p></pre>lukechampine: <pre><p>I&#39;ve considered adding something like this to <a href="https://github.com/lukechampine/ply" rel="nofollow">Ply</a>. Other languages have constructs like <code>with</code> that automatically call a destructor when the scope ends. It would be nice to have something like that for Go. (It would also be nice if <code>defer</code> had no overhead...)</p></pre>Rudd-X: <pre><p>Wat. I didn&#39;t know that&#39;s how defer worked.</p></pre>Land_Apple: <pre><p>defer is slow and bad, don&#39;t use defer</p></pre>megromby: <pre><pre><code>- defer is slow and bad, don&#39;t use defer + Land_Apple is slow and bad, don&#39;t listen to Land_Apple </code></pre></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

340 次点击  
加入收藏 微博
0 回复
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传