Why []string is incompatible with []interface{} ?

3,721 views
Skip to first unread message

yassen

unread,
Nov 21, 2012, 2:12:31 AM11/21/12
to golan...@googlegroups.com
I just start thinking that I begin understanding go and it quickly bytes me back :(

I have a function with a signature "func AssertEqual(t *testing.T, args... interface{})" that happily works if called with strings for args... However, I have this one: "func Contains(list []interface{}, token interface{}) bool" which doesn't: if I call it with a []string and string, the compiler yells "cannot use list (type []string) as type []interface {} in function argument". I am surely missing something but I cannot grasp what. Can anyone please help sorting this out? Thank you!

DisposaBoy

unread,
Nov 21, 2012, 2:19:35 AM11/21/12
to golan...@googlegroups.com
it's mentioned in the FAQ

Jesse McNelis

unread,
Nov 21, 2012, 2:23:30 AM11/21/12
to yassen, golang-nuts
On Wed, Nov 21, 2012 at 6:12 PM, yassen <yasse...@gmail.com> wrote:
I just start thinking that I begin understanding go and it quickly bytes me back :(

I have a function with a signature "func AssertEqual(t *testing.T, args... interface{})" that happily works if called with strings for args... However, I have this one: "func Contains(list []interface{}, token interface{}) bool" which doesn't: if I call it with a []string and string, the compiler yells "cannot use list (type []string) as type []interface {} in function argument". I am surely missing something but I cannot grasp what. Can anyone please help sorting this out? Thank you!

"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."

When you're passing individual strings the call takes those strings converts them to interface{} values and allocates a new []interface{} to put them in.
When you pass a slice with '...' the call won't allocate and will instead use the slice directly. But since this is a []string and not a []interface{} it can't do that because they are different types (as mentioned in the FAQ), so you get a type error.

You can do the conversion yourself

a:=[]string{"pizza","pie"}
b := make([]interface,0,len(a))
for _,i:= range a{
    b = append(b,i)
}
Assert(t,b...)
or just just pass the strings individually, 
Assert(t, a[0],a[1])




--
=====================
http://jessta.id.au


yassen

unread,
Nov 21, 2012, 4:33:27 AM11/21/12
to golan...@googlegroups.com, yassen, jes...@jessta.id.au
Jesse, DisposaBoy, thank you guys! It is indeed in the FAQ: []string and []intreface{} have different representation in memory so they are not compatible.


On Wednesday, November 21, 2012 9:23:41 AM UTC+2, Jesse McNelis wrote:
"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."

When you're passing individual strings the call takes those strings converts them to interface{} values and allocates a new []interface{} to put them in.
When you pass a slice with '...' the call won't allocate and will instead use the slice directly. But since this is a []string and not a []interface{} it can't do that because they are different types (as mentioned in the FAQ), so you get a type error.

You can do the conversion yourself

a:=[]string{"pizza","pie"}
b := make([]interface,0,len(a))
for _,i:= range a{
    b = append(b,i)
}
Assert(t,b...)
or just just pass the strings individually, 
Assert(t, a[0],a[1])
 
Well, my problem is with Contains(t, token, list...). It is supposed to give an answer to the question if the token is found in the list, where list is supposed to be a slice of elements with the same type as token. If I swap Contains() args to have token first and rewrite the call to Contains() to have the token as first arg and the other elements as scalar args, it works. However, that is terribly inconvenient and thus not usable.

Any way to "unpack" a []string slice into a scalar sequence of arguments? I guess no way, but asking just in case. Thanks!

yassen

unread,
Nov 21, 2012, 4:42:24 AM11/21/12
to golan...@googlegroups.com, yassen, jes...@jessta.id.au


On Wednesday, November 21, 2012 11:33:27 AM UTC+2, yassen wrote:
Any way to "unpack" a []string slice into a scalar sequence of arguments? I guess no way, but asking just in case. Thanks!

I mean a syntax construct which does the opposite of ... and will make a call to a variadic function with a sequence of scalar arguments taken from the given slice. Example (represented by the double asterisk):

func Contains(token interface{}, list... interface{}) bool {
    for _, v := range list {
        if token == v { return true }
    }
    return false
}

args := []string{"one", "2", "three"}
ok  := Contains("2", args**)

(Apologies for my go ignorance)

Volker Dobler

unread,
Nov 21, 2012, 6:07:04 AM11/21/12
to golan...@googlegroups.com, yassen, jes...@jessta.id.au


Am Mittwoch, 21. November 2012 10:42:24 UTC+1 schrieb yassen:
I mean a syntax construct which does the opposite of ... and will make a call to a variadic function with a sequence of scalar arguments taken from the given slice. Example (represented by the double asterisk):

func Contains(token interface{}, list... interface{}) bool {
    for _, v := range list {
        if token == v { return true }
    }
    return false
}

args := []string{"one", "2", "three"}
ok  := Contains("2", args**)

This is more than just some syntax and syntactical sugar. This would
need exactly the described conversion between []interface{}
(which is what list in Contains basically is) and []string (which you
want to pass in). It does not matter that your ** tries to say
"unpack slice": After unpacking, the resulting positional parameters
would have to be repacked to be represented by the list []interface
slice. Nothing won.

(I know it "feels strange" at first to write the same Contains
function twice, e.g. once for string and once for int. If you happen
to implement the function 25 times: Refactor massively, e.g.
introduce appropriate interfaces or use reflection. If you just
need string, int and float64 why not a bit of copy&paste? This
might be the easier/more readable/cleaner to maintain way
of doing it. Have some test to maintain correctness. Lets assume
your Contains needs extra cases to handle NaNs or Infs in the 
float64 case: Much easier with a ContainsFloat64 version than
a generic Contains. DRY is a nice principle, but it doesn't have
the status of Kant's Categorical Imperative.)

Volker

yassen

unread,
Nov 21, 2012, 6:22:39 AM11/21/12
to golan...@googlegroups.com, yassen, jes...@jessta.id.au
Thanks, Volker,

On Wednesday, November 21, 2012 1:07:04 PM UTC+2, Volker Dobler wrote:
This is more than just some syntax and syntactical sugar. This would
need exactly the described conversion between []interface{}
(which is what list in Contains basically is) and []string (which you
want to pass in). It does not matter that your ** tries to say
"unpack slice": After unpacking, the resulting positional parameters
would have to be repacked to be represented by the list []interface
slice. Nothing won.

Well, but syntax sugar helps keep the code less noisy.
 
(I know it "feels strange" at first to write the same Contains
function twice, e.g. once for string and once for int. If you happen
to implement the function 25 times: Refactor massively, e.g.
introduce appropriate interfaces or use reflection. If you just
need string, int and float64 why not a bit of copy&paste? This
might be the easier/more readable/cleaner to maintain way
of doing it. Have some test to maintain correctness. Lets assume
your Contains needs extra cases to handle NaNs or Infs in the 
float64 case: Much easier with a ContainsFloat64 version than
a generic Contains. DRY is a nice principle, but it doesn't have
the status of Kant's Categorical Imperative.)
 
When I had my first AssertEqual(fitst, second interface{})  and it worked, I was impressed by Go flexibility and I was sure I've found the Golden Mine and that  slices (and anything else) were going to work ... Alas!

Struggling with this, I first though of using reflection but finally ended up writing a couple of ContainsZzz() s ... not that bad, after all! (and not that elegant, either ;)

Thanks again,
Yassen

André Moraes

unread,
Nov 21, 2012, 2:04:31 PM11/21/12
to yassen, golan...@googlegroups.com, jes...@jessta.id.au
> Any way to "unpack" a []string slice into a scalar sequence of arguments? I
> guess no way, but asking just in case. Thanks!

If you can change the Contains function to receive a interface{}
instead of ...interface{}

package main

import (
"fmt"
"reflect"
)

func main() {
strv := []string{"a", "b", "c"}
intv := []int{1, 2, 3}
fmt.Printf("Contains (str): %v\n", Contains("a", strv))
fmt.Printf("Contains (int): %v\n", Contains(1, intv))
}

// This is OK
func Contains(v interface{}, vals interface{}) bool {
println("TypeOf vals: ", reflect.TypeOf(vals).String())
switch vals := vals.(type) {
case []string:
for _, item := range vals {
if v.(string) == item { return true }
}
return false
case []int:
for _, item := range vals {
if v.(int) == item { return true }
}
return false
default:
panic("You can't handle the truth")
}
return false
}




--
André Moraes
http://amoraes.info

Greg Ward

unread,
Nov 26, 2012, 11:08:42 AM11/26/12
to yassen, golan...@googlegroups.com, jes...@jessta.id.au
On 21 November 2012, yassen said:
> When I had my first AssertEqual(fitst, second interface{}) and it worked,
> I was impressed by Go flexibility and I was sure I've found the Golden Mine
> and that slices (and anything else) were going to work ... Alas!

Before you go too far down that road, I recommend you take a look at
the Testify packages:

https://github.com/stretchrcom/testify

In particular, you want their assert package, which is full of yummy
goodness that lets you type

assert.Equal(t, a, b)
assert.Nil(t, err)

in your tests. Saves a lot of repetitive typing, and the resulting
test failures are pretty clear.

Greg

Jan Mercl

unread,
Nov 26, 2012, 11:16:22 AM11/26/12
to Greg Ward, yassen, golang-nuts, Jesse McNelis
My personal recommendation is to never use canned asserts.

-j

chris dollin

unread,
Nov 26, 2012, 3:16:07 PM11/26/12
to Jan Mercl, Greg Ward, yassen, golang-nuts, Jesse McNelis
On 26 November 2012 16:16, Jan Mercl <0xj...@gmail.com> wrote:

> My personal recommendation is to never use canned asserts.

Care to expand, or possibly expound?

Chris

--
Chris "undecided" Dollin

Jan Mercl

unread,
Nov 26, 2012, 3:45:38 PM11/26/12
to chris dollin, Greg Ward, yassen, golang-nuts, Jesse McNelis
On Mon, Nov 26, 2012 at 9:16 PM, chris dollin <ehog....@googlemail.com> wrote:
> On 26 November 2012 16:16, Jan Mercl <0xj...@gmail.com> wrote:
>
>> My personal recommendation is to never use canned asserts.
>
> Care to expand, or possibly expound?

For example:
https://groups.google.com/d/topic/golang-nuts/6hmxZY8IQNQ/discussion
https://groups.google.com/d/topic/golang-nuts/JC2Nv7XDGGA/discussion
https://groups.google.com/d/topic/golang-nuts/a2mCOKhodxI/discussion

-j
Reply all
Reply to author
Forward
0 new messages