<p><a href="https://github.com/ascheglov/go/pull/1/files">Here's a patch which adds a "Rust-style" <code>__try</code> keyword</a>.</p>
<p>It transforms <code>x, y := __try foo()</code> into </p>
<pre><code>x, y, __err := foo()
if __err != nil { return }
</code></pre>
<p>This patch changes compiler's parser, so that it adds the <code>IfStmt</code> node when a block-level assign statement or a call-expression statement has the <code>__try</code> keyword.<br/>
The <code>__err</code> is a reserved name, function with the <code>__try</code> keyword should look like this (also see example/try.go):</p>
<pre><code>func f() (__err error) {
__try g()
// not reached if g() returns an error
return
}
</code></pre>
<p>This patch is not intended for production,<br/>
it's a mere demonstration that the language could be improved (yeah) by adding some few hundreds of lines.</p>
<hr/>**评论:**<br/><br/>TheMerovius: <pre><p>First, I think it's great that you wrote up a prototype. That is really valuable and much more effort than most people (me included) are willing to do. Thank you. :)</p>
<p>I disagree, though, that this is in any way an improvement of Go. If all you do is pass on an error, you are very likely doing it wrong (there are a few exceptions, but they are rare). At the very least, you are going to want to annotate that error with useful information. Far more likely, though, you should replace it with your own, better error message.</p>
<p>For example, I literally do not care which syscall failed when I run <code>go get example.com/foo/bar</code>. I neither want "connection refused", nor do I want <code>getting "example.com/foo/bar": fetching meta-tag: parsing "https://example.com/foo/bar": extract vcs-info: import prefix "github.com/foo/bar" is not a prefix of "example.com/foo/bar"</code>, nor do I want a stacktrace.</p>
<p>I want an error message to the effect of <code>domain example.com does not exist</code>, or <code>broken go-imports tag on "https://example.com/foo/bar": import prefix "github.com/foo/bar" is not a prefix of example.com/foo/bar"</code>. (The <em>developers</em> of the go tool might care about a stack trace or more detailed debug information when a bug happened, but that's what a "-debug" or "-v" flag is for)</p>
<p><em>Yes</em> writing the code to produce readable, helpful error messages is hard and tedious. But that's because <em>it's code</em>.</p>
<p>Constructs like these would have exactly one effect: People would think, that not handling your errors is blessed by the language, so they wouldn't handle their errors.</p></pre>Abyxus: <pre><p>In my experience, and judging from results of<br/>
<code>go/src $ git grep -A 1 "if err != nil {" *.go</code>,<br/>
most of the error handling is in fact an error passing: <code>{ return nil, err }</code>.</p>
<p>Also we use stacktraces in our production code (server back-end) and it shows us exactly what we (its developers) want to see from an error message.</p></pre>TheMerovius: <pre><blockquote>
<p>most of the error handling is in fact an error passing</p>
</blockquote>
<p>"Being common" isn't the same as "being good". And yeah, that explains why so many people believe that this broken idiom (I still consider "error handling" a categorically wrong term for it) should be improved.</p>
<blockquote>
<p>Also we use stacktraces in our production code (server back-end) and it shows us exactly what we (its developers) want to see from an error message.</p>
</blockquote>
<p>Do you also send stack traces to people sending you a request? Because that's what an error message is for. There is nothing wrong with logging stack-traces - as long as they go to the debug-logs, where they belong.</p>
<p>The developer of a package, the developer of an <em>importer</em> of that package and the user of a binary are all different audiences. Use debug-logs for the first, error values for the second and error <em>messages</em> for the last. Either way, don't just blindly pass on your error.</p>
<p>(this set of audiences is of course only an example. Depending on your structure and use case you might have different sets of audiences. But that doesn't change that a) they in general need different ways to communicate failure to, b) those should use different mechanism and c) in general, error-messages should target humans with minimal knowledge of the intrinsics of the software and tell them specifically what <em>they</em> did wrong)</p></pre>bbatha: <pre><blockquote>
<p>At the very least, you are going to want to annotate that error with useful information.</p>
</blockquote>
<p>The <a href="https://github.com/rust-lang/rfcs/blob/master/text/1859-try-trait.md" rel="nofollow">rust version of this</a> the <code>?</code> operator implicitly calls <code>Try::from_error(From::from))</code> before returning which allows you to build up conversions of error types to your local error type by implementing the <code>From</code> trait. Of you course you can expliticly handle errors at the call site or do one off conversions using convenience functions like <code>map_err(|| /* conversion */)</code> before using the <code>?</code> operator. To me this strikes a nice balance. All erroring calls are explicitly marked with the <code>?</code> operator, conversions only exist for what I explicitly supply so that lower level error types don't leak up the stack like exceptions.</p>
<p>I think you can implement a more simple, open ended version of this using something like <a href="https://godoc.org/github.com/pkg/errors#Wrap" rel="nofollow"><code>errors.Wrap</code> from from <code>pkg/errors</code></a>. Though without generics I'm not quite sure how to thread in those conversion helpers easily. Maybe something like this:</p>
<pre><code>// every module that wishes to supply converters provides this func (same type)
// if they don't sub it some default one
// maybe some way to make this an iterface?
// what if instead of a hardcoded function name you had some kind of transform comment?
// eg, go:generate Try.wrapError
func wrapError(err error) error {
// convert
}
// original
func DoIt() error {
foo := try pkg.ErroringThing()
// ...
}
// desugared
func DoIt() error {
foo, err := pkg.ErroringThing()
if err != nil {
return wrapError(err)
}
}
</code></pre>
<p>Integrating having a magic function in a package as part of the language is bizarre, generics could get around this their might be some interface tricks that get you there.</p></pre>parenthephobia: <pre><p>If you do go by variable name, just use <code>err</code>. It's already conventional. No need to make it a keyword: just have <code>try</code> apply to return values called <code>err</code>. It won't break any existing code, because no existing code uses try. </p>
<p>I would actually have it apply to any return variable of <code>error</code> type, but I understand that's much more complicated than just hooking into the parser.</p>
<p>Some other ideas:</p>
<p><em>Allow/require specifying annotations/wrappers.</em></p>
<pre><code>// e.g. Transform into fmt.Errorf
try "Couldn't get widget: %s" http.Get("http://yo.yo")
</code></pre>
<p>It's generally considered bad form to just blindly return errors. Everyone does it though, because it's the easiest option, and it achieves the <em>fundamental</em> goal of stopping the code when there's an error. So, why not make it so that something not as bad as blindly returning errors is easier? Then, people would do that instead. :)</p>
<p><em>Something with switch?</em></p>
<pre><code>switch widgets := try getWidgets() {
case sql.ErrNoRows:
try errors.New("Not as many widgets as expected")
case sql.ErrTxDone:
try errors.New("Database failure while extracting widgets")
case nil:
return widgets, nil
}
</code></pre></pre>GentooMonk: <pre><p>I have to say, I love Go, but the go subreddit have got some of the most toxic people on reddit.</p>
<p>That being said, that's very interesting, a lot of time I really wanted to do something like, even have the always fun <code>ret := func(err error) (xx, xx,xx error) { return nil, nil, nil, err }</code> at the top of the function to do that.</p>
<p>I know this will never get merged, at least in Go1, but you've done an excellent PoC.</p></pre>hipone: <pre><blockquote>
<p>I have to say, I love Go, but the go subreddit have got some of the most toxic people on reddit</p>
</blockquote>
<p>You haven't seen <a href="/r/programming" rel="nofollow">r/programming</a> then.</p></pre>GentooMonk: <pre><p>oh yeah, i forgot about it, been years since i went there.</p></pre>drvd: <pre><blockquote>
<p>it's a mere demonstration that the language could be improved (yeah) by adding some few hundreds of lines.</p>
</blockquote>
<p>Under the assumption that addition == improvement.</p>
<p>(I can prove a lot of things if I may choose the assumptions freely.)</p></pre>Sythe2o0: <pre><p>This wouldn't work in functions without named variables or zero return arguments, based on what you say it replaces. </p></pre>Abyxus: <pre><p>So what? It's a PoC.<br/>
It also doesn't track positions of injected nodes, doesn't modify the "go/parser" package and so on.</p></pre>Sythe2o0: <pre><p>I mean, this exact same idea has been suggested at least a dozen times before, so defending your implementation by saying it's just a proof of concept doesn't inspire confidence.</p></pre>Abyxus: <pre><p>You're free to contribute, as the patch shows - it's fairly easy.</p></pre>Sythe2o0: <pre><p>I'd rather work on any of the things that I'm already working on, than on a misguided and incomplete solution to error propagation.</p></pre>8lall0: <pre><p>I don't like that very much.</p>
<ol>
<li><p>Too much underscores (very python, much unreadable, wow)</p></li>
<li><p>It hides to me the explicit handling of errors (that is one of the reasons why i hate try/catch constructs, even if this isn't one)</p></li>
</ol>
<p>My 2 cents :)</p></pre>Abyxus: <pre><p><code>__try</code> is an example. It cannot be <code>try</code> because it would break existing code. It could be <code>try!</code>, <code>try?</code> or even <code>?!</code>. However <code>__try</code> is good enough for PoC.</p></pre>titpetric: <pre><p>The premise is flawed and the premise doesn't rely on poor syntax, Go doesn't need try in such form. And even more awkwardly, if it did have try, a catch should to go with it. And you'd still have to throw errors which means you'll have the same amount of code, all the <code>if err != nil { throw err }</code> don't really improve anything. </p>
<p>The issue with the above:</p>
<ol>
<li>chooses to return which means only named returns are possible,</li>
<li>doesn't somehow ensure that __err is passed forward (it doesn't just hide the handling of errors, it prevents actual handling of errors)</li>
</ol>
<p>The only nice thing about this example is just to see where you'd add a language based feature, preferably one that's more thought out. But then again, seeing how exactly none of them have a chance to be accepted upstream without an accepted proposal before any development, it's just some sort of self-satisfaction, to know that you <em>can</em> (but to lack the foresight to know that <em>you shouldn't</em>).</p></pre>Abyxus: <pre><p>It's a code-rewriting "try", as in Rust. Something close to the Maybe monad.<br/>
It has nothing to do with exceptions, catch and throw.</p></pre>titpetric: <pre><p>It's clear enough that it's quite useless if you want to handle <code>__err</code> and you should. I mean if you're throwing it away, which is exactly what's happening with the return, you can pretty much just write <code>res, _ = ...</code> and bleh.</p></pre>winger_sendon: <pre><p>If ur gonna handle it yes you should. But how many Go libraries do that for every error ? (Poor man's stack trace). </p></pre>tmornini: <pre><blockquote>
<p>how many Go libraries do that for every error</p>
</blockquote>
<p>That's something we should aspire to improve upon, not exacerbate.</p>
<p>That said, this is a cool PoC, thanks for doing the work!</p></pre>winger_sendon: <pre><p>PoC? Whats that?</p>
<p>Stack traces are one of those things you should let the runtime do for you. You only wrap errors providing more information when you need. It's a pity Go does not support stack traces natively for errors although its runtime can do that. </p>
<p>Now, you will probably point me to panic but most gophers use errors and panic only when it's a truly exceptional situation like programmer atupidity, out of memory and so on.</p></pre>vgbm: <pre><p>PoC being proof-of-concept</p></pre>nevyn: <pre><p>The main point of this patch is improving how code looks, so choosing a really ugly PoC is a bad idea. Also you want a lot more examples, Eg. how do I pass the err up the stack and what about any other return values?</p>
<p>Maybe show a before/after of a couple of real Go code examples (anything big on github would be fine, IMO).</p>
<p>Edit: Also a comparison vs. errWriter: <a href="https://blog.golang.org/errors-are-values" rel="nofollow">https://blog.golang.org/errors-are-values</a></p></pre>Abyxus: <pre><p><code>errWriter</code> is a joke. This idiom doesn't work in real code when you'd have to write <code>errFoo</code>, <code>errBar</code>, and <code>errBaz</code> wrappers in every function, <code>errLog</code> included.</p></pre>shark1337: <pre><p>There shoudl be also an else statemenr in case it returns an error doe..</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传