Return Nil or Pointer to Empty Struct on Error?

xuanbao · · 23 次点击    
<p>When writing a go func such as</p> <pre><code>foo() (*myStruct, error) </code></pre> <p>If my function encounters an error and returns the error, what should be the value of the *myStruct?</p> <p>Should I return</p> <pre><code>return new(myStruct), fmt.Errorf(&#34;an error occured&#34;) </code></pre> <p>or is it better to return</p> <pre><code>return nil, fmt.Errorf(&#34;an error occured&#34;) </code></pre> <p>Is one approach better than the other?</p> <p>If returning nil is better, is it OK for me to occasionally return new(myStruct)?</p> <hr/>**评论:**<br/><br/>UniverseCity: <pre><p>Returning nil is always preferred.</p></pre>jerf: <pre><p>If you want a truly empty struct to be a valid value, then you&#39;re probably better off returning a nil but making the methods on that nil be &#34;valid&#34;, for whatever the appropriate definition of &#34;valid&#34; is.</p> <p>Contrary to widely-supposed opinion, <a href="https://play.golang.org/p/pG4y2ijOh4" rel="nofollow">you can call methods on nil perfectly fine</a>. Unlike C or C++ where a NULL pointer is missing its type information, a nil pointer in Go is actually (PointerType, Value), where the Value is the nil. Go can still reach the method even on a nil pointer value just fine.</p></pre>winger_sendon: <pre><p><a href="https://play.golang.org/p/EJDwfm4HQ9" rel="nofollow">Backfires</a> </p> <p>I thinks its a bad idea. No one cares about the receiver type while calling a method. Although it is best practise to implement an interface with consistent receiver type, sometimes you write a method with a value receiver and whole thing blows up.<br/> Whats even the use of being able to call methods in nil? It only makes things complicated. </p> <p>P.S your point about pointer is slightly misleading. A nil pointer is just a number in memory. The type information only exists during compilation. Unless you put that in an interface, of course. </p></pre>lausan: <pre><p>That&#39;s not unexpected at all. And following the <a href="https://golang.org/doc/faq#methods_on_values_or_pointers" rel="nofollow">golang faq</a> on this would prevent a panic there.</p> <blockquote> <p>If some of the methods of the type must have pointer receivers, the rest should too, so the method set is consistent regardless of how the type is used.</p> </blockquote></pre>winger_sendon: <pre><p>This is not checked by the compiler. </p></pre>lausan: <pre><p>Not sure what your point is. Never use pointers? No nil pointer dereferences are caught by the compiler along with any other number of bugs you could create by writing bad code. If you expect the go compiler to keep you from shooting yourself in the foot, you&#39;re gonna have a bad time.</p></pre>jerf: <pre><p>In addition to lausan&#39;s point, you are incorrect about the nil pointer just being a number in memory. That is how C works. In Go, the type is tracked. You can get it out of the reflect package. It is possible for the compiler to optimize it away if it can prove it is possible (I don&#39;t know if it <em>does</em>, but it is <em>possible</em>), but the type is tracked at runtime if it can not be proved possible.</p></pre>winger_sendon: <pre><p>Any sources for that? Reflect package uses interfaces to extract type information (i.e. reflect.TypeOf takes an interface{} so when ur assigning the pointer, the compiler inserts type information to interfaces type pointer)</p></pre>jerf: <pre><p>And the way that works, and the way interfaces work, is by tracking the type. I don&#39;t know exactly the breakdown between what is done at run time and what is done at compile time, but that&#39;s subject to change without notice anyhow because that&#39;s an implementation detail. What matters at the Go layer is that, yes, the types are always tracked and pointers always have their type attached to them in a way that C pointers do not.</p> <p>Or, to put it another way, the source for that is exactly the observation you just made. Go couldn&#39;t do that if it weren&#39;t tracking types.</p></pre>winger_sendon: <pre><p>Unless go gets inheritance(it never will), there is no need to make every pointer type contain additional information. I don&#39;t think its an implementation detail because go &#34;laws of reflection&#34; article clearly states only way to do reflection is through interfaces because only they store type information. I dont have time to look at go spec right now. Im not familiar with C pointer but IIRC you can cast any pointer to any other pointer and break the type system. Go also offers this using unsafe package. But as interfaces store type, trying to break type system will panic. </p></pre>lausan: <pre><p>This is an old but still relevant article by rsc about how interfaces are implemented <a href="https://research.swtch.com/interfaces" rel="nofollow">https://research.swtch.com/interfaces</a>. Might be even easier to read the source now since its in go <a href="https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L148" rel="nofollow">https://github.com/golang/go/blob/master/src/runtime/runtime2.go#L148</a> <a href="https://github.com/golang/go/blob/master/src/runtime/iface.go" rel="nofollow">https://github.com/golang/go/blob/master/src/runtime/iface.go</a>. Type assertions are also done at runtime by tracking the concrete type</p></pre>winger_sendon: <pre><p>I think you are missing the point. Hes saying that every pointer has type information associated with it, not just pointers put in interfaces, if i&#39;m getting him right.<br/> Hes saying that every pointer is a fat pointer like in java (except sealed classes).</p></pre>shovelpost: <pre><pre><code>return _, fmt.Errorf(&#34;an error occurred&#34;) </code></pre> <p>Make it happen!</p></pre>Acidic92: <pre><p>cannot use _ as a value</p></pre>shovelpost: <pre><p>I wasn&#39;t trying to give you a solution since that has already been given. I was just doing wishful thinking.</p></pre>Acidic92: <pre><p>oh I see</p></pre>DeedleFake: <pre><p>Specifically, he was referencing a proposal that had been made to allow <code>_</code> as a value when the type is known as a zero-value equivalent of <code>nil</code>, essentially. </p></pre>cdoxsey: <pre><p>easiest workaround is to name your returns:</p> <pre><code>func foo() (res myStruct, err error) { return res, fmt.Errorf(&#34;an error occurred&#34;) } </code></pre></pre>DeedleFake: <pre><p>That works, although it can get a bit annoying in larger functions, since you have to make sure you never assigned anything to it.</p> <p>Personally, I&#39;m in favor of the proposed expansion of type elision for structs that would allow you to drop the type name in almost every case. In other words, <code>{}</code> would be the zero-value for any struct type. That one has the benefits of not overlapping with existing syntax much, and it allows structs to be used more easily as tuples. For example:</p> <pre><code>c := make(chan struct {id uint; val string}) c &lt;- {3, &#34;This is an example.&#34;} </code></pre></pre>DeedleFake: <pre><blockquote> <p>If returning nil is better, is it OK for me to occasionally return new(myStruct)?</p> </blockquote> <p>If you find yourself wanting to do this, my guess is there&#39;s something odd elsewhere in your code. That being said, it&#39;s probably fine to do so internally, but you should generally avoid doing so in anything that crosses API boundaries, and, even if the use is internal, I highly recommend trying to restructure the code that needs it to do so that it doesn&#39;t need to do so.</p> <p>Also, it <em>is</em> alright to return a valid value alongside an error in some cases. For example, <a href="https://godoc.org/io#Reader" rel="nofollow"><code>io.Reader</code> allows implementations to return an error alongside the number of bytes read</a> if an error is encountered while reading data.</p></pre>Acidic92: <pre><p>Thanks for the detailed explanation. I opted to returning nil, fmt.Errorf() and my code works fine!</p></pre>Acidic92: <pre><p>The only reason I can think that I&#39;d want to return an empty pointer (new(myStruct)) is if I knew it was being encoded to JSON to avoid null as the value.</p> <p>If this helps me, is it reasonable for me to switch to new(myStruct) to avoid my json from returning null, or should I still return nil and find another way around?</p></pre>DeedleFake: <pre><p>If you have a function that&#39;s generating a value to be encoded to JSON, and that function can return an error, why are you using the value even if there&#39;s been an error? If you encode a zero-value of a struct, you&#39;ll get an object with zero-values of all of that struct&#39;s fields. Why do you need it to do that?</p> <p>Alternatively, if you decide you need to do that still, could you make the function return the struct type itself, rather than a pointer to it? Then the zero-value would be the expected return if there&#39;s an error.</p></pre>epiris: <pre><p>Always check errors and return nil imo, deviating away from this sets you up for traps down the road and it doesn&#39;t really save you anything not to.</p></pre>nsd433: <pre><p>I&#39;d return nil.</p> <p>It&#39;s good to panic the calling code as early as possible if it fails to check for error or otherwise tries to use the nonexistent return value. Hiding bugs with band-aides like empty structs just leads to harder-to-find bugs later on.</p></pre>goboy123: <pre><p>This helped me a lot.</p> <p><a href="https://dave.cheney.net/paste/gocon-spring-2016.pdf" rel="nofollow">https://dave.cheney.net/paste/gocon-spring-2016.pdf</a></p></pre>
23 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet