<p>I originally posted this on <a href="http://stackoverflow.com/a/30135334/455678">Stack Overflow</a> but I thought others may find this interesting. I've always wondered myself why the <code>ok</code> part of indexing a map is optional, and I didn't even know that you could use it for channels! For context if you don't feel like going to Stack Overflow, OP was wondering why he could not return multiple values with:</p>
<pre><code>func FindUserInfo(id string) (Info, bool) {
return all[id]
}
</code></pre>
<hr/>
<p>To elaborate on my <a href="http://stackoverflow.com/questions/30129206/golang-return-multiple-values-issue?noredirect=1#comment48368941_30129206">comment</a>, the <a href="https://golang.org/doc/effective_go.html#maps">Effective Go</a> mentions that the multi-value assignment from accessing a map key is called the "comma ok" pattern.</p>
<blockquote>
<p>Sometimes you need to distinguish a missing entry from a zero value. Is there an entry for "UTC" or is that the empty string because it's not in the map at all? You can discriminate with a form of multiple assignment.</p>
</blockquote>
<pre><code>var seconds int
var ok bool
seconds, ok = timeZone[tz]
</code></pre>
<blockquote>
<p>For obvious reasons this is called the “comma ok” idiom. In this example, if tz is present, seconds will be set appropriately and ok will be true; if not, seconds will be set to zero and ok will be false.</p>
</blockquote>
<p><a href="http://play.golang.org/p/cgtysKkLEp">Playground demonstrating this</a></p>
<p>We can see that this differs from calling a regular function where the compiler would tell you that something is wrong:</p>
<pre><code>package main
import "fmt"
func multiValueReturn() (int, int) {
return 0, 0
}
func main() {
fmt.Println(multiValueReturn)
asgn1, _ := multiValueReturn()
asgn2 := multiValueReturn()
}
</code></pre>
<p>On the <a href="http://play.golang.org/p/fe29S1706D">playground</a> this will output</p>
<pre><code># command-line-arguments
/tmp/sandbox592492597/main.go:14: multiple-value multiValueReturn() in single-value context
</code></pre>
<p>This gives us a hint that it may be something the compiler is doing. <a href="https://github.com/golang/go/search?utf8=%E2%9C%93&q=commaOk">Searching the source code</a> for "commaOk" gives us a few places to look, including <a href="https://github.com/golang/go/blob/254964074f34dc1cb39693f3e23a95938092044f/src/go/types/call.go#L118-L134"><code>types.unpack</code></a></p>
<p>At the time of writing this it this the method's godoc reads:</p>
<pre><code>// unpack takes a getter get and a number of operands n. If n == 1, unpack
// calls the incoming getter for the first operand. If that operand is
// invalid, unpack returns (nil, 0, false). Otherwise, if that operand is a
// function call, or a comma-ok expression and allowCommaOk is set, the result
// is a new getter and operand count providing access to the function results,
// or comma-ok values, respectively. The third result value reports if it
// is indeed the comma-ok case. In all other cases, the incoming getter and
// operand count are returned unchanged, and the third result value is false.
//
// In other words, if there's exactly one operand that - after type-checking
// by calling get - stands for multiple operands, the resulting getter provides
// access to those operands instead.
//
// If the returned getter is called at most once for a given operand index i
// (including i == 0), that operand is guaranteed to cause only one call of
// the incoming getter with that i.
//
</code></pre>
<p>The key bits of this being that this method appears to determine whether or not something is <em>actually</em> a "comma ok" case.</p>
<p>Digging into that method tells us that it will check to see if the mode of the operands is <strong>indexing a map</strong> or if the mode is set to <code>commaok</code> (where this <a href="https://github.com/golang/go/blob/677839353e013b13e0fb3d28e01a01a5ccf0c7a1/src/go/types/operand.go#L28">is defined</a> does give us many hints on when it's used, but searching the source for assignments to <code>commaok</code> we can see it's used when <a href="https://github.com/golang/go/blob/677839353e013b13e0fb3d28e01a01a5ccf0c7a1/src/go/types/expr.go#L107">getting a value from a channel</a> and <a href="https://github.com/golang/go/blob/677839353e013b13e0fb3d28e01a01a5ccf0c7a1/src/go/types/expr.go#L1343">type assertions</a>). Remember the bolded bit for later!</p>
<pre><code>if x0.mode == mapindex || x0.mode == commaok {
// comma-ok value
if allowCommaOk {
a := [2]Type{x0.typ, Typ[UntypedBool]}
return func(x *operand, i int) {
x.mode = value
x.expr = x0.expr
x.typ = a[i]
}, 2, true
}
x0.mode = value
}
</code></pre>
<p><code>allowCommaOk</code> is a parameter to the function. Checking out where <code>unpack</code> is called in that file we can see that all callers pass <code>false</code> as an argument. Searching the rest of the repository leads us to <code>assignments.go</code> in the <a href="https://github.com/golang/go/blob/254964074f34dc1cb39693f3e23a95938092044f/src/go/types/assignments.go#L207-L208"><code>Checker.initVars()</code> method</a>.</p>
<pre><code>l := len(lhs)
get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid())
</code></pre>
<p>Since it seems that we can only use the "comma ok" pattern to get two return values when doing a multi-value assignment this seems like the right place to look! In the above code the length of the left hand side is checked, and when <code>unpack</code> is called the <code>allowCommaOk</code> parameter is the result of <code>l == 2 && !returnPos.IsValid()</code>. The <code>!returnPos.IsValid()</code> is somewhat confusing here as that would mean that the position <a href="https://github.com/golang/go/blob/6ffed3020c0fb749868b71d5b13fef65ff248902/src/go/token/position.go#L80-L83">has no file or line information associated</a> with it, but we'll just ignore that.</p>
<p>Further down in that method we've got:</p>
<pre><code>var x operand
if commaOk {
var a [2]Type
for i := range a {
get(&x, i)
a[i] = check.initVar(lhs[i], &x, returnPos.IsValid())
}
check.recordCommaOkTypes(rhs[0], a)
return
}
</code></pre>
<p><strong>So what does all of this tell us?</strong></p>
<ul>
<li>Since the <code>unpack</code> method takes an <code>allowCommaOk</code> parameter that's hardcoded to false everywhere <em>except</em> in <code>assignment.go</code>'s <code>Checker.initVars()</code> method, we can probably assume that you will only ever get two values when doing an assignment and have two variables on the left-hand side.</li>
<li>The <code>unpack</code> method will determine whether or not you actually <em>do</em> get an <code>ok</code> value in return by checking if you are indexing a slice, grabbing a value from a channel, or doing a type assertion</li>
<li>Since you can only get the <code>ok</code> value when doing an assignment it looks like in your specific case you will always need to use variables </li>
</ul>
<hr/>**评论:**<br/><br/>mdempsky: <pre><blockquote>
<p>OP was wondering why he could not return multiple values with:</p>
</blockquote>
<p>Wouldn't it be easier to just reference the <a href="http://golang.org/ref/spec" rel="nofollow">Go Programming Language Specification</a>?</p>
<blockquote>
<p>An index expression on a map <code>a</code> of type <code>map[K]V</code> used in an assignment or initialization of the special form</p>
<pre><code>v, ok = a[x]
v, ok := a[x]
var v, ok = a[x]
</code></pre>
<p>yields an additional untyped boolean value. The value of <code>ok</code> is <code>true</code> if the key <code>x</code> is present in the map, and <code>false</code> otherwise.</p>
</blockquote>
<p>The statement <code>return all[id]</code> is not of those special forms, so it doesn't yield the additional boolean value.</p>
<p>Also, the go/types code that you're referring to isn't (at least currently) part of the gc compiler toolchain.</p></pre>Raiyni: <pre><p>I kind of assumed that is how it worked before reading the explanation. Originally I would have said that the compiler didn't check the all function could return two types (like a couple of the answerers suggest).</p></pre>
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889
- 请尽量让自己的回复能够对别人有帮助
- 支持 Markdown 格式, **粗体**、~~删除线~~、
`单行代码`
- 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
- 图片支持拖拽、截图粘贴等方式上传