Can anyone explain why this is a runtime error and not found at compile time?

agolangf · · 455 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;d expect the compiler to generate an error saying that <em>type C</em> doesn&#39;t implement <em>text() string</em> from the <em>type Instance</em>. However it compiles just fine and only generates an error at runtime:</p> <p><a href="https://play.golang.org/p/TBl7L9f2JT">https://play.golang.org/p/TBl7L9f2JT</a></p> <p>There&#39;s probably a good reason for it. Would the great if someone can share the missing insight with me. =)</p> <hr/>**评论:**<br/><br/>shovelpost: <pre><p>In Go, interfaces <a href="https://tour.golang.org/methods/10" rel="nofollow">are satisfied implicitly</a>.</p> <p>Here you have a concrete type that embeds an interface:</p> <pre><code>type base struct { Instance } </code></pre> <p>Then by doing <code>c.Instance = c</code> you have created an infinite chain that never stops because C does not have a <code>text()</code> method. So when you call <code>base.text()</code> it attempts to &#34;call&#34; <code>c.Instance.text()</code> so to speak, but you have set that to be <code>c</code> so the loop starts all over again.</p></pre>mosaic_school: <pre><p>What I don&#39;t understand is why the assignment <code>c.Instance = c</code> is even valid wit the implementation if a <code>text() string</code> method ?</p> <p>I mean in the example you liked ( <a href="https://tour.golang.org/methods/10" rel="nofollow">https://tour.golang.org/methods/10</a> ) you cannot remove the these lines without a compile time error:</p> <p><code>func (t T) M() { fmt.Println(t.S) }</code></p></pre>weberc2: <pre><p>Removing that line means that <code>T</code> no longer implements the <code>I</code> interface (because <code>T</code> no longer has an <code>M</code> method); however, in your example, <code>C</code> <em>does</em> implement <code>Instance</code> because <code>C</code> has a <code>text() string</code> method that it inherits (via anonymous struct embedding) from <code>C.Instance</code>. So <code>c.text()</code> evaluates to <code>c.Instance.text()</code>; however, <code>c.Instance</code> points to <code>c</code> (interfaces are pointers under the covers), so it tries to run <code>c.text()</code> and the cycle repeats indefinitely.</p></pre>shovelpost: <pre><p>What you usually want to do is this:</p> <pre><code>type Instance interface { text() string } type base struct { } func (b base) text() string { return fmt.Sprintf(&#34;text&#34;) } </code></pre> <p>If you remove the method <code>text</code> from type <code>base</code> then it no longer satisfies the interface and you will get a compile time error.</p> <p>Please read <a href="https://research.swtch.com/interfaces" rel="nofollow">Go Data Structures: Interfaces</a>.</p></pre>comrade_donkey: <pre><p>It is valid because methods can be inpure. The first call to <code>text()</code> might make the second recursion terminate (e.g. by reassigning <code>c.Instance</code> or setting some global flag, etc). In your case it doesn&#39;t.</p></pre>pierrrre: <pre><p>C does implement text()</p> <p><a href="https://play.golang.org/p/GTUe8TTMXh" rel="nofollow">https://play.golang.org/p/GTUe8TTMXh</a></p></pre>mosaic_school: <pre><p>yeah that&#39;s the point. should that be a compile time error, rather than runtime?</p></pre>pierrrre: <pre><p>There is no compile time error, because C DOES implement text().</p> <p>The runtime error is caused by a stack overflow, text() calls itself recursively.</p></pre>mosaic_school: <pre><p>Can you please go into into detail a bit. I mean it makes sense from the stack overflow kinda but how exactly does <code>type C</code> implement <code>text() string</code>?</p> <p>I mean I don&#39;t see a single concrete function for text() in your or my examples. Is this some how auto generated. If yes, what are the exact rules for that?</p></pre>pierrrre: <pre><p><a href="https://talks.golang.org/2012/10things.slide#3" rel="nofollow">https://talks.golang.org/2012/10things.slide#3</a></p> <p>or search &#34;golang anonymous struct field&#34;</p></pre>mosaic_school: <pre><blockquote> <p>golang anonymous struct field&#34;</p> </blockquote> <p>Well, I know that what I&#39;m using are <em>anonymous struct fields</em>. Unfortunately, neither the slide nor the first results of the google search yield a detail explanation on the code generated to produce the stack overflow.</p> <p>Sorry, I keep asking but I&#39;d really like to understand the issue here completely. Unfortunately none of the answers helped me to grasp every aspect.</p></pre>weberc2: <pre><p>Structs &#34;inherit&#34; methods defined on their anonymous struct fields. In this case, your struct embeds an anonymous interface (Instance) which defines <code>text()</code>, so thus the struct &#34;inherits&#34; <code>text()</code> as well, making it satisfy the <code>Instance</code> interface. The runtime error is a stack overflow because c.Greet() invokes c.text() which is actually c.base.Instance.text() which is implemented by c.text() (because you&#39;ve set <code>c.Instance</code> equal to <code>c</code>in <code>NewC()</code>). The compiler isn&#39;t catching <em>this</em> error because it doesn&#39;t know that you don&#39;t want to infinitely recurse into c.text() (it could catch this sort of error at compile time in egregious cases like this, but the cases that are trivial to catch are also very rare in practice).</p></pre>mosaic_school: <pre><p>Thanks, your answer covered all missing points I think. I still think <code>c.Instance = c</code> should give a compile time error since it <em>could</em> be caught at compile time. However as your answer says, the compiler simply assumes it to be implemented by the forwarding call which then yields to the stack overflow.</p></pre>weberc2: <pre><p>Maybe, but Go&#39;s compiler never attempts to prevent infinite recursion (or infinite loops, for that matter). If you want to recurse indefinitely, that&#39;s your business, I suppose. Probably better off leaving it to a linter. Besides, this error is easy to catch because there are no branches in the call path; however, such errors certainly are nearly non-existent in practice, so why slow the compiler for artificial problems? The common stack-overflow/call-depth errors that occur in practice contain branches, and most of these are impossible to statically preclude (for example, a function that checks for user input and recurses if none is found).</p> <p>In practice, the compiler rarely (never?) looks through an interface; it just compiles in the dynamic dispatch code and carries on. I think this is too bad, because it seems like this could unlock a lot of useful optimizations (statically-dispatching [and thus possibly inlining] interface methods when the compiler can prove the interface type doesn&#39;t change, for example).</p></pre>marcelmolina: <pre><p>But the infinite recursion is a red herring. If C wasn’t being used recursively but instead something like C with no concrete implementation of the embedded interface you’d still compile and fail at runtime with a nil pointer.</p></pre>1lann: <pre><p>Your example isn&#39;t that clear for the question you&#39;re asking. Here&#39;s a better example:</p> <p><a href="https://play.golang.org/p/0KOTwz19RM" rel="nofollow">https://play.golang.org/p/0KOTwz19RM</a></p> <p>This reason for this occurring, is because Go doesn&#39;t have true inheritance, instead there&#39;s a bit of syntactic sugar going on. When I call <code>a.Greet()</code>, it first checks to see if the <code>Person</code> struct implements the <code>Greet</code> method, which it doesn&#39;t. So then it falls back to the embedded value, which you have provided to be <code>Person</code>. So it attempts to do <code>a.Person.Greet()</code>, because that is theoretically valid, because <code>Person</code> should implement the <code>Greet</code> method.</p> <p>However, <code>Person</code> has not been initialized in <code>a</code>. Therefore it results in a SIGSEGV as you attempt to call a method on a nil value. This is why it&#39;s a runtime error, and not a compile time error.</p> <p>Here is an example of a valid way of setting <code>Person</code> dynamically, satisfying the use of <code>Greet</code> without error: <a href="https://play.golang.org/p/_xRC0LQLgD" rel="nofollow">https://play.golang.org/p/_xRC0LQLgD</a></p></pre>mosaic_school: <pre><p>Thanks for the detailed answer. You simple example makes total sense with your explanation. However, what I don&#39;t understand is why in my example I don&#39;t get a SIGSEGV but a stack overflow instead. Can you please elaborate on that?</p></pre>marcelmolina: <pre><p>C implements text() by way of the embedded field of type Interface but the zero value of the field is nil so assigning &amp;C as Interface means you have satisfied the interface but you provided a nil value at runtime</p></pre>mosaic_school: <pre><p>Hm, that partly makes sense to me. However, wouldn&#39;t then a nil pointer error / segmentation fault be the result and not a stackoverflow?</p> <p>Also I added a print statement and none of the values is nil:</p> <p><a href="https://play.golang.org/p/IqgRW9NvOa" rel="nofollow">https://play.golang.org/p/IqgRW9NvOa</a></p> <p>Can you please add print statement to show me which value is nil exactly?</p></pre>weberc2: <pre><p>You&#39;re correct, <a href="/u/marcelmolina" rel="nofollow">/u/marcelmolina</a> is wrong. <code>nil</code> isn&#39;t the issue here; you&#39;re setting <code>c.Instance</code> equal to <code>c</code> in <code>NewC()</code>, which means <code>c.text()</code> calls <code>c.Instance.text()</code> which calls <code>c.text()</code> which calls <code>c.Instance.text()</code> ad infinitum. Each of these calls allocates some stack, so you very quickly consume all of your stack limit.</p></pre>jeremiahs_bullfrog: <pre><p>I don&#39;t think so. You&#39;re never severally dereferencing a nil pointer, but continually doing lookups for <code>text()</code>. That function is never actually called (it&#39;s nil), so it doesn&#39;t generate a nil pointer exception.</p> <p>I think Go&#39;s compiler should be a bit more intelligent for things like this, but it&#39;s not a violation of anything in the Kenya&#39;s the spec AFAIK, so it won&#39;t generate a compile error.</p></pre>

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

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