Using maps, pointers and arrays

xuanbao · · 532 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Hi guys. I&#39;ve hit bit of a &#34;how should this be done in golang&#34; wall. </p> <p>What I have is a <code>items := map[string][]*someType</code> </p> <p>and a little later a check </p> <pre><code>Add item i if key exists i := new(someType) if cache, ok := items[&#34;key&#34;]; ok { cache = append(cache, i) } </code></pre> <p>Now. What I&#39;ve noticed is that this doesn&#39;t reflect in <code>items[&#34;key&#34;]</code>. Which makes sense, because <code>items[&#34;key&#34;]</code> resolves to a value and not a pointer.</p> <p>What&#39;s the correct approach here?</p> <p>Do I:</p> <p>a) <code>items[&#34;key&#34;] = cache</code>(or inline it with <code>item[&#34;key&#34;] = append(cache, i)</code>)</p> <p>or </p> <p>b) Change the definition of <code>items</code> to <code>map[string]*[]*someType</code> (which requires using the memory reference of the returned append function)</p> <p>Can&#39;t really figure out which one is the cleaner go solution.</p> <p>Thanks in advance</p> <hr/>**评论:**<br/><br/>JackOhBlades: <pre><p>You could create a new container type that manages the slice internally and then hold pointer to that type in the map.</p> <pre><code>type Conatiner struct { list []*sometype } func (c *Container) Append(s *sometype) { if c.list == nil { c.list = []*sometype{} } c.list = append(c.list, s) } </code></pre> <p>And then:</p> <pre><code>i := new(some type) If cache, ok := items[&#34;key&#34;]; ok { cache.Append(I) } </code></pre></pre>Smokey_Circles: <pre><p>So simple, just skipped right passed me. Too much time solving a simple problem lol.</p> <p>Thanks</p></pre>JackOhBlades: <pre><p>Go is all about types and composition :) </p></pre>danredux: <pre><pre><code>if c.list == nil { c.list = []*sometype{} } c.list = append(c.list, s) </code></pre> <p>Could just be..</p> <pre><code>c.list = append(c.list, s) </code></pre></pre>JackOhBlades: <pre><p>Indeed. I like to be explicit. Having nil check is saying &#34;this type is designed to be used without any special setup.&#34; But you are correct :) </p></pre>konart: <pre><blockquote> <p>What I&#39;ve noticed is that this doesn&#39;t reflect in items[&#34;key&#34;]</p> </blockquote> <p>I think you might have provided wrong code here. In your example you are tring to append i to items, which is not a destination slice at all. To begin with.</p> <p>It this what you meant?</p> <p><a href="https://play.golang.org/p/wHaDBtQ4hd" rel="nofollow">https://play.golang.org/p/wHaDBtQ4hd</a></p></pre>Smokey_Circles: <pre><p>Ah, you are correct. Sorry, will redo that.</p> <p>But yes, that is what I meant</p></pre>DenzelM: <pre><p>Great question <a href="/u/Smokey_Circles" rel="nofollow">/u/Smokey_Circles</a>, this trips up a lot of early Go developers. To answer your question, (a) <code>items[&#34;key&#34;] = append(cache, i)</code> is the idiomatic way to update a map value of slice. Let&#39;s look at why that&#39;s the case, because knowledge is useless without understanding.</p> <p>Read over <a href="https://blog.golang.org/go-slices-usage-and-internals" rel="nofollow">Go Slices: usage and internals</a> by the core developers. Take a few minutes. They have some great diagrams on that page. The most important part to understand is that a <em>slice</em> is a descriptor of an array segment made up of three things:</p> <ul> <li>Pointer (*Elem): a pointer to (anywhere in) the underlying array</li> <li>Length (int): the length of this slice segment</li> <li>Capacity (int): the maximum possible length of this slice segment, i.e., constrained by the length of the underlying array and where our pointer is pointing to</li> </ul> <p>What does this mean? Well, a slice is a structure underneath the hood. Depending on your CPU architecture (32- vs. 64-bit) it can either be 12 or 24 bytes worth of data just for the descriptor, not including the underlying array. The awesome thing, as you probably know, is that we can create a bunch of slice descriptors that share the same underlying array. So while you may have 1,000,000 values in a slice, you&#39;re only copying 12 or 24 bytes when you assign that slice to another variable.</p> <p>Now, back to your problem. Why isn&#39;t it updating the slice? You&#39;re appending to it and assigning it back, so what gives? Funnily enough, it is updating the slice (just not the one you think), and if it can, if there&#39;s enough space, it&#39;s updating the underlying array too! Let&#39;s peek underneath the hood for a second with some code:</p> <pre><code>package main import &#34;fmt&#34; func main() { items := map[string][]int{} items[&#34;key&#34;] = make([]int, 1, 10) // initialize with length = 1, capacity = 10 items[&#34;key&#34;][0] = 1 if cache, ok := items[&#34;key&#34;]; ok { fmt.Printf(&#34;cache.Pointer = %p\n&#34;, cache) fmt.Printf(&#34;items[&#39;key&#39;].Pointer = %p\n&#34;, items[&#34;key&#34;]) fmt.Printf(&#34;&amp;cache = %p\n\n&#34;, &amp;cache) cache = append(cache, 2) fmt.Printf(&#34;updated cache.Pointer = %p\n&#34;, cache) fmt.Printf(&#34;updated &amp;cache = %p\n\n&#34;, &amp;cache) fmt.Println(&#34;cache =&#34;,cache) } fmt.Printf(&#34;items[&#39;key&#39;] = %v\n&#34;, items[&#34;key&#34;]) fmt.Printf(&#34;resliced items[&#39;key&#39;] = %v\n&#34;, items[&#34;key&#34;][0:2]) } </code></pre> <p>And here&#39;s what the output looks like:</p> <pre><code>cache.Pointer = 0xc820010140 items[&#39;key&#39;].Pointer = 0xc820010140 &amp;cache = 0xc82000e260 updated cache.Pointer = 0xc820010140 updated &amp;cache = 0xc82000e260 cache = [1 2] items[&#39;key&#39;] = [1] resliced items[&#39;key&#39;] = [1 2] </code></pre> <p>Ok, now it&#39;s getting interesting! What happened here? Well, I wanted to highlight a few things, so I decided to add some debugging statements that print out the underlying pointers. When you use <code>%p</code> with a slice, it prints out the <em>Pointer</em> field of the slice descriptor, i.e., the pointer to the underlying array. I also decided to print out the address of the actual <code>cache</code> slice descriptor, so that you could see it&#39;s different than the underlying array. Why didn&#39;t we also print out the address of <code>items[&#34;key&#34;]</code>... simple, map values aren&#39;t <a href="https://golang.org/ref/spec#Address_operators" rel="nofollow">addressable</a>.</p> <p>But look, <code>cache</code> and <code>items[&#34;key&#34;]</code> share the same underlying array. We appended to that array; and our value was faithfully appended, just as we asked. And our slice was faithfully updated, just as we asked. Except, it&#39;s the wrong slice... because when you do <code>cache := items[&#34;key&#34;]</code> it copies the slice descriptor. And when you append to that slice descriptor, it appends the value to the underlying array, and then updates the <code>cache</code> slice descriptor, making its <em>length</em> = 2... while <code>items[&#34;key&#34;]</code> slice descriptor still has a <em>length</em> = 1.</p> <p>So, just to prove to you that the value is really there in the underlying array, I resliced <code>items[&#34;key&#34;]</code> with <code>items[&#34;key&#34;][0:2]</code> and voila, your #2 is served.</p> <p>That&#39;s all well and good, but remember the semantics of <code>append</code>. What would happen in our program if there wasn&#39;t enough capacity to append a value to the underlying array? What happens when we replace <code>items[&#34;key&#34;] = make([]int, 1, 10)</code> with <code>items[&#34;key&#34;] = []int{1}</code>? Go ahead and see for yourself. See if you understand why now.</p> <p>Yikes, that was a round-a-bout way of saying use <code>items[&#34;key&#34;] = append(...)</code>. Hopefully it helps. :)</p></pre>Smokey_Circles: <pre><p>My boss asked me why I was posting this question on reddit, this is precisely why.</p> <p>Thanks so much man, really appreciate the in depth answer :) </p> <p>I didn&#39;t want to sit down and just hack a solution through, this needs to be prod-grade so I&#39;m always looking for the way go does things. </p> <p>I knew while I was running my tests that I was losing the value as the block exited, thanks to not using pointers correctly. Pointers are a little of a mind bend for me (Java background), and googling it pretty much just describes the mechanics, but not the idiomatically correct way of doing it. One of the reasons I love this language so much was Rob Pike&#39;s statement around (paraphrased) that &#34;Languages are complicated. We wanted to build a complex and powerful tool that looks identical in most forms&#34;. So just hacking away wasn&#39;t really doing anybody any favors.</p> <p>But your answer makes a lot of sense (thanks again for all the effort!) and it does feel like the more correct approach. </p></pre>DenzelM: <pre><p>Anytime, thank you for the kind words, I really appreciate them. You&#39;re mind is in the right place. I have no doubt you&#39;ll be a rock-solid Go developer in less than a month (especially with previous experience).</p></pre>

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

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