gotypeserror-handlingreturn-valuemultiple-return-values

How does Go determine the context in which multiple values can be used?


package main

import "fmt"

func multipleRets() (int, int, int, int) {
    return 11, 22, 33, 44
}

func main() {
    // Q1
    fmt.Println(multipleRets())    // This is fine.
    fmt.Println(1, multipleRets()) // But this one errors.

    // Q2
    s1 := append([]int{}, []int{11, 22, 33, 44}...) // This is fine.
    s2 := append([]int{}, multipleRets())           // But this one errors.

    // Q3
    lit1 := []int{11, 22, 33, 44} // This is fine.
    lit2 := []int{multipleRets()} // But this one errors.

    // Q4
    fmt.Println(1, []int{11, 22, 33, 44})    // This is fine.
    fmt.Println(1, []int{11, 22, 33, 44}...) // But this one errors.
}

There are 4 errors in a source code above, all of which complaining that multiple elements cannot be put in a function/literal.

But having the other examples in my mind, I can't really find a reason for those errors to be considered error.

Shouldn't they be fine? What does it mean when Go gives an error like that saying "multiple-value in single-value context"?

And the way those three pernicious dots work, how does that make sense? What exactly does ... do?


Solution

  • This is why syntactic sugar is avoided by the Go designers; it leads to confusion when you expect more sugar than there is. You're seeing two types of syntactic sugar in Go here; exploding a slice to pass to a variadic function, and passing multiple return values to a function.

    Exploding a slice to a variadic function is documented here: https://golang.org/ref/spec#Passing_arguments_to_..._parameters and the key detail is:

    If the final argument is assignable to a slice type []T, it may be passed unchanged as the value for a ...T parameter if the argument is followed by .... In this case no new slice is created.

    fmt.Println is a function with a single, variadic parameter. That means you can either pass it individual items, or a single item which is a declared slice with the ... explosion operator appended, in which case that slice will be passed unchanged. It can't be a slice literal, and it can't follow other parameters, in order to utilize this helper.

    The other is documented here: https://golang.org/ref/spec#Calls specifically:

    As a special case, if the return values of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order. The call of f must contain no parameters other than the call of g, and g must have at least one return value. If f has a final ... parameter, it is assigned the return values of g that remain after assignment of regular parameters.

    Again, because fmt.Println has a single, variadic parameter, you can't mix and match passing specific values with using the above syntactic sugar for passing a multi-value return straight through to the parameters of another function.

    As Peter notes, "And Q3 is a slice literal, which doesn't support variadic "parameters" at all (because the values are not parameters)." The syntactic sugar above for passing multiple returns to a function doesn't apply to a slice literal simply because it is not a function at all.