Cannot convert []string to []interface {}
Asked Answered
T

7

118

I'm writing some code, and I need it to catch the arguments and pass them through fmt.Println
(I want its default behaviour, to write arguments separated by spaces and followed by a newline). However it takes []interface {} but flag.Args() returns a []string.
Here's the code example:

package main

import (
    "fmt"
    "flag"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args()...)
}

This returns the following error:

./example.go:10: cannot use args (type []string) as type []interface {} in function argument

Is this a bug? Shouldn't fmt.Println take any array? By the way, I've also tried to do this:

var args = []interface{}(flag.Args())

but I get the following error:

cannot convert flag.Args() (type []string) to type []interface {}

Is there a "Go" way to workaround this?

Teodoro answered 20/10, 2012 at 16:19 Comment(1)
I was messing with a simple example (go run test.go some test flags), and it seemed to work when changing flags.Args()... to just flag.Args() (output is [some test flags], followed by the newline; also seemed to work with registering actual flags). Won't pretend to understand why, and Stephen's answer is way more informative anyway :)Murdocca
B
139

This is not a bug. fmt.Println() requires a []interface{} type. That means, it must be a slice of interface{} values and not "any slice". In order to convert the slice, you will need to loop over and copy each element.

old := flag.Args()
new := make([]interface{}, len(old))
for i, v := range old {
    new[i] = v
}
fmt.Println(new...)

The reason you can't use any slice is that conversion between a []string and a []interface{} requires the memory layout to be changed and happens in O(n) time. Converting a type to an interface{} requires O(1) time. If they made this for loop unnecessary, the compiler would still need to insert it.

Boliviano answered 20/10, 2012 at 16:42 Comment(8)
If each iteration requires O(1) time, wouldn't the whole loop need O(n) time?Teodoro
If every element in the slice satisfies interface{}, then a function receiving []string instead should have no problem, since every element inside satisfies the interface, is it?Teodoro
By the way, I found this link in golang-nuts: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussionTeodoro
Yes, each iteration requires O(1) time and the loop requires O(n) time. That is what I said. As for the function receiving a []string, it expects a interface{}. An interface{} has a different memory layout from a string so the fact that each element needs to be converted is the problem.Boliviano
@karlrh: No, suppose the Println function modifies the slice, and sets some elements (it doesn't, but suppose it does). Then it can put any interface{} into the slice, which should only have strings. What you really want is something like the Java Generics wildcard Slice<? extends []interface{}>, but that doesn't exist in Go.Tanjatanjore
Append is magical. It is built-in and treated specially by the language. Other examples include new(), len(), and copy(). golang.org/ref/spec#Appending_and_copying_slicesBoliviano
Explicit conversion to interface{} is unnecessary, instead of new[i] = interface{}(v) you can simply write new[i] = v.Insistent
Today I learned 'new' is a not a reserve keyword! Nor is make!Mahlstick
P
14

In this case, a type conversion is unnecessary. Simply pass the flag.Args() value to fmt.Println.


Question:

Cannot convert []string to []interface {}

I'm writing some code, and I need it to catch the arguments and pass them through fmt.Println (I want its default behaviour, to write arguments separated by spaces and followed by a newline).

Here's the code example:

package main

import (
    "fmt"
    "flag"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args()...)
}

Package flag

import "flag"

func Args

func Args() []string

Args returns the non-flag command-line arguments.


Package fmt

import "fmt"

func Println

func Println(a ...interface{}) (n int, err error)

Println formats using the default formats for its operands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.


In this case, a type conversion is unnecessary. Simply pass the flag.Args() value to fmt.Println, which uses reflection to interpret the value as type []string. Package reflect implements run-time reflection, allowing a program to manipulate objects with arbitrary types. For example,

args.go:

package main

import (
    "flag"
    "fmt"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}

Output:

$ go build args.go
$ ./args arg0 arg1
[arg0 arg1]
$ 
Panamerican answered 31/1, 2018 at 5:27 Comment(1)
If I wanted to omit the brackets, would the conversion still be unnecessary?Teodoro
M
11

If it's only a slice of strings you want to print, you can avoid conversion and get the exact same output by joining:

package main

import (
    "fmt"
    "flag"
    "strings"
)

func main() {
    flag.Parse()
    s := strings.Join(flag.Args(), " ")
    fmt.Println(s)
}
Merci answered 19/12, 2015 at 16:6 Comment(0)
E
1

In Go, a function can only accept arguments of the types specified in the parameter list in the function definition. The variadic parameter language feature complicates that a bit, but it follows well-defined rules.

The function signature for fmt.Println is:

func Println(a ...interface{}) (n int, err error)

Per the language specifiction,

The final incoming parameter in a function signature may have a type prefixed with .... A function with such a parameter is called variadic and may be invoked with zero or more arguments for that parameter.

This means you can pass Println a list of arguments of interface{} type. Since all types implement the empty interface, you can pass a list of arguments of any type, which is how you're able to call Println(1, "one", true), for example, without error. See the "Passing arguments to ... parameters" section of the language specification:

the value passed is a new slice of type []T with a new underlying array whose successive elements are the actual arguments, which all must be assignable to T.

The part that's giving you trouble is right after that in the specification:

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.

flag.Args() is type []string. Since T in Println is interface{}, []T is []interface{}. So the question comes down to whether a string slice value is assignable to a variable of interface slice type. You can easily test that in your go code by attempting an assignment, for example:

s := []string{}
var i []interface{}
i = s

If you attempt such an assignment, the compiler will output this error message:

cannot use s (type []string) as type []interface {} in assignment

And that's why you can't use the ellipsis after a string slice as an argument to fmt.Println. It's not a bug, it's working as intended.

There are still lots of ways you can print flag.Args() with Println, such as

fmt.Println(flag.Args())

(which will output as [elem0 elem1 ...], per fmt package documentation)

or

fmt.Println(strings.Join(flag.Args(), ` `)

(which will output the string slice elements, each separated by a single space) using the Join function in the strings package with a string separator, for example.

Emphasis answered 4/2, 2018 at 9:26 Comment(0)
V
0

I think it's possible using reflection, but I don't know if it's a good solution

package main

import (
    "fmt"
    "reflect"
    "strings"
)

type User struct {
    Name string
    Age  byte
}

func main() {
    flag.Parse()
    fmt.Println(String(flag.Args()))
    fmt.Println(String([]string{"hello", "world"}))
    fmt.Println(String([]int{1, 2, 3, 4, 5, 6}))
    u1, u2 := User{Name: "John", Age: 30},
        User{Name: "Not John", Age: 20}
    fmt.Println(String([]User{u1, u2}))
}

func String(v interface{}) string {
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Array || val.Kind() == reflect.Slice {
        l := val.Len()
        if l == 0 {
            return ""
        }
        if l == 1 {
            return fmt.Sprint(val.Index(0))
        }
        sb := strings.Builder{}
        sb.Grow(l * 4)
        sb.WriteString(fmt.Sprint(val.Index(0)))
        for i := 1; i < l; i++ {
            sb.WriteString(",")
            sb.WriteString(fmt.Sprint(val.Index(i)))
        }
        return sb.String()
    }

    return fmt.Sprintln(v)
}

Output:

$ go run .\main.go arg1 arg2
arg1,arg2
hello,world
1,2,3,4,5,6
{John 30},{Not John 20}
Vedi answered 5/3, 2019 at 19:6 Comment(0)
R
0

Another option is to just iterate the slice:

package main
import "flag"

func main() {
   flag.Parse()
   for _, each := range flag.Args() {
      println(each)
   }
}
Ruffin answered 7/4, 2021 at 15:27 Comment(0)
L
-2

fmt.Println takes variadic parameter

func Println(a ...interface{}) (n int, err error)

Its possible to print flag.Args() without converting into []interface{}

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}
Laminate answered 30/1, 2018 at 18:2 Comment(8)
fmt.Println's signature hasn't changed in over 6 years (and that was just a package change for error). Even if it had, the spec clearly says ` variadic with a final parameter p of type ...T, then within f the type of p is equivalent to type []T.` so it wouldn't matter. fmt.Println(flags.Args()...) still does not work (you're missing the slice expansion), fmt.Println(flags.Args()) always worked.Remiss
How did you get this info that fmt.Println's signature hasn't changed in over 6 years? Did you checked source code?Laminate
Yup: github.com/golang/go/blame/release-branch.go1.10/src/fmt/… (on the 1.10 branch)Remiss
The comment on the op suggest that using flag.Args() solely as argument to fmt.Println worked back that time. (It would be surprising if it did not at that time)Melicent
So? If there exist a comment, that means my answer should be down voted?Laminate
@leafbebop where did you get this rules?Laminate
I vote the answer down because it is not helpful. It does not answer the question. My comment served to tell you that there is nothing new. And I think to downvote is a very personal right of a user that does not need to be justified.Melicent
And I think your edit to the question makes the question less general and more confusing. Is there really anything that requires revision?Melicent

© 2022 - 2024 — McMap. All rights reserved.