1. 9

For example:

func print(a ...interface{}) {
}

This will work:

print("a", "b")

// or this:
print([]interface{}{"a", "b"}...)

But, this won’t, because of the type-identities ([]string vs []interface{}) are different:

s := []string{"a","b"}
print(s...)

I know why it doesn’t work. What I want to discuss is that: By passing a sequence of string arguments work, but passing a string slice with an expansion operator don’t work. Maybe it’s better both to work. Why not? Should the Go compiler support also that?

  1.  

  2. 8

    Go’s philosophy is generally to do only a limited amount of runtime work by magic behind the scenes. Converting values to interfaces is work; Go is willing to do it relatively implicitly for single values, but I expect that philosophically it is opposed to extending that work to converting an array of values into an array of interfaces, since the amount of work (and the amount of memory required) is unpredictable and potentially large.

    1. 8

      The above opens a huge can of worms with the type system in regards to variance (in that case, it would be that the function parameter would need to be covariant).

      https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#C.23_examples

      Can read more there.

      1. 11

        Go has a lot of weird semantics that require you to understand the implementation, from it’s type system to it’s function call behavior.

        See the ugliness here: https://play.golang.org/p/yPxfK5VvLw

        In this case it’s that s… to a function that has …type sends the array directly, Go has no concept of “apply” from lisp. So, if you read the implementation of how they handle their variadic calls, you’ll see they kind of grasp around with some heuristics to make some subset of things make sense.

        Secondly, the types []string and []interface do not exist in a hierarchy, so you cannot make []string => []interface. Single values do exist in a hierarchy with interface, as all are assignable. It’s a bottom type for single values, because Go’s type system doesn’t interact well with their slice type due to all it’s magic they’ve added on function calls.

        Go leaves a lot to be desired.

        1. 2

          Part of the reason for this (and other restrictions like the inability to do print("a", interface{}("b", "c")...) is because use of slice... doesn’t unpack and repack the arguments; it drops the passed slice directly into the function’s varargs slice. And a []string can’t be dropped directly into a hole expecting a []interface{} because the items are the wrong type. It’s not a matter of quibbling semantics, it would totally crash. For that call to work, the compiler would have to make a new []interface{}, put all of the strings into interface boxes, place them in the new slice, do the call, and then deallocate the temporaries, which it could do, except

          1. Go pretty much never does that amount of work, especially involving memory allocation, without the programmer’s explicit say-so.
          2. If they were going to do that, they might as well cover the easier and more useful case of copying []A into []B (where A and B are different, but convertible, types) without varargs first.