Não é possível converter [] string em [] interface {}

97

Estou escrevendo um código e preciso capturar os argumentos e transmiti-los fmt.Println
(quero o comportamento padrão, escrever argumentos separados por espaços e seguidos por uma nova linha). No entanto, leva, []interface {}mas flag.Args()retorna a []string.
Aqui está o exemplo de código:

package main

import (
    "fmt"
    "flag"
)

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

Isso retorna o seguinte erro:

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

Isso é um inseto? Não deve fmt.Printlnter nenhuma matriz? A propósito, também tentei fazer isso:

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

mas recebo o seguinte erro:

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

Existe uma maneira "Vá" de contornar isso?

Cruizh
fonte
1
Eu estava mexendo com um exemplo simples ( go run test.go some test flags), e parecia funcionar ao mudar flags.Args()...para apenas flag.Args()(a saída é [some test flags], seguida pela nova linha; também parecia funcionar com o registro de sinalizadores reais). Não vou fingir que entendi o porquê, e a resposta de Stephen é muito mais informativa de qualquer maneira :)
RocketDonkey

Respostas:

117

Este não é um bug. fmt.Println()requer um []interface{}tipo. Isso significa que deve ser uma fatia de interface{}valores e não "qualquer fatia". Para converter a fatia, você precisará fazer um loop e copiar cada elemento.

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

O motivo pelo qual você não pode usar nenhuma fatia é que a conversão entre a []stringe a []interface{}requer que o layout da memória seja alterado e acontece no tempo O (n). Converter um tipo em interface{}requer tempo O (1). Se eles tornassem esse loop for desnecessário, o compilador ainda precisaria inseri-lo.

Stephen Weinberg
fonte
2
A propósito, encontrei este link em golang-nuts: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh
2
Sim, cada iteração requer tempo O (1) e o loop requer tempo O (n). Foi isso que eu disse. Quanto à função que recebe a []string, ela espera a interface{}. Um interface{}tem um layout de memória diferente de um, stringportanto, o problema é o fato de que cada elemento precisa ser convertido.
Stephen Weinberg de
1
@karlrh: Não, suponha que a Printlnfunção modifique a fatia e defina alguns elementos (não altera, mas suponha que sim). Em seguida, ele pode colocar qualquer interface{}na fatia, que deve ter apenas strings. O que você realmente quer é algo como o curinga Java Generics Slice<? extends []interface{}>, mas isso não existe no Go.
newacct
4
Append é mágico. É embutido e tratado de forma especial pela linguagem. Outros exemplos incluem new(), len()e copy(). golang.org/ref/spec#Appending_and_copying_slices
Stephen Weinberg
1
Hoje eu aprendi que 'novo' não é uma palavra-chave de reserva! Nem é fazer!
Sridhar
11

Se for apenas uma parte das strings que deseja imprimir, você pode evitar a conversão e obter exatamente a mesma saída juntando:

package main

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

func main() {
    flag.Parse()
    s := strings.Join(flag.Args(), " ")
    fmt.Println(s)
}
Moshe Revah
fonte
11

Nesse caso, uma conversão de tipo é desnecessária. Basta passar o flag.Args()valor para fmt.Println.


Questão:

Não é possível converter [] string em [] interface {}

Estou escrevendo um código e preciso capturá-los e passá-los por meio de fmt.Println (quero que seu comportamento padrão seja escrever argumentos separados por espaços e seguidos por uma nova linha).

Aqui está o exemplo de código:

package main

import (
    "fmt"
    "flag"
)

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

Bandeira de pacote

import "flag"

func Args

func Args() []string

Args retorna os argumentos de linha de comando não sinalizadores.


Pacote fmt

import "fmt"

função Println

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

Printlnformatos usando os formatos padrão para seus operandos e grava na saída padrão. Espaços são sempre adicionados entre operandos e uma nova linha é acrescentada. Ele retorna o número de bytes gravados e qualquer erro de gravação encontrado.


Nesse caso, uma conversão de tipo é desnecessária. Basta passar o flag.Args()valor para fmt.Println, que usa reflexão para interpretar o valor como tipo []string. Package reflectimplementa reflexão em tempo de execução, permitindo que um programa manipule objetos com tipos arbitrários. Por exemplo,

args.go:

package main

import (
    "flag"
    "fmt"
)

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

Resultado:

$ go build args.go
$ ./args arg0 arg1
[arg0 arg1]
$ 
PeterSO
fonte
Se eu quisesse omitir os colchetes, a conversão ainda seria desnecessária?
cruizh
1

Em Go, uma função só pode aceitar argumentos dos tipos especificados na lista de parâmetros na definição da função. O recurso de linguagem de parâmetro variável complica um pouco isso, mas segue regras bem definidas.

A assinatura da função para fmt.Printlné:

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

De acordo com a especificação do idioma ,

O parâmetro de entrada final em uma assinatura de função pode ter um tipo prefixado com .... Uma função com tal parâmetro é chamada de variadic e pode ser chamada com zero ou mais argumentos para aquele parâmetro.

Isso significa que você pode passar Printlnuma lista de argumentos do interface{}tipo. Já que todos os tipos implementam a interface vazia, você pode passar uma lista de argumentos de qualquer tipo, que é como você pode chamar Println(1, "one", true), por exemplo, sem erros. Consulte a seção "Passando argumentos para ... parâmetros" da especificação do idioma:

o valor passado é uma nova fatia do tipo [] T com uma nova matriz subjacente cujos elementos sucessivos são os argumentos reais, que devem ser atribuídos a T.

A parte que está causando problemas está logo depois na especificação:

Se o argumento final puder ser atribuído a uma fatia do tipo [] T, ele pode ser passado inalterado como o valor para um parâmetro ... T se o argumento for seguido por .... Neste caso, nenhuma nova fatia é criada.

flag.Args()é tipo []string. Já que Tem Printlné interface{}, []Té []interface{}. Portanto, a questão se resume a se um valor de fatia de string pode ser atribuído a uma variável do tipo de fatia de interface. Você pode testar isso facilmente em seu código go tentando uma atribuição, por exemplo:

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

Se você tentar essa atribuição, o compilador produzirá esta mensagem de erro:

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

E é por isso que você não pode usar as reticências após uma fatia de string como um argumento para fmt.Println. Não é um bug, está funcionando conforme o esperado.

Há muitas ainda de maneiras que você pode imprimir flag.Args()com Println, tais como

fmt.Println(flag.Args())

(que sairá como [elem0 elem1 ...], por documentação do pacote fmt )

ou

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

(que produzirá os elementos de fatia de string, cada um separado por um único espaço) usando a função Join no pacote de strings com um separador de string, por exemplo.

Jrefior
fonte
0

Acho que é possível usar reflexão, mas não sei se é uma boa solução

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)
}

Resultado:

$ go run .\main.go arg1 arg2
arg1,arg2
hello,world
1,2,3,4,5,6
{John 30},{Not John 20}
Juan Torres
fonte
-1

fmt.Printlnleva parâmetro variadic

func Println (a ... interface {}) (n int, erro de erro)

É possível imprimir flag.Args()sem converter em[]interface{}

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}
Shahriar
fonte
2
fmt.Printlna assinatura de não mudou em mais de 6 anos (e isso foi apenas uma mudança de pacote para error). Mesmo se tivesse, a especificação diz claramente `variadic com um parâmetro final p do tipo ... T, então dentro de f o tipo de p é equivalente ao tipo [] T`, então não faria diferença. fmt.Println(flags.Args()...)ainda não funciona (está faltando a expansão do slice), fmt.Println(flags.Args())sempre funcionou.
Marc
Como você conseguiu essa informação de que a assinatura de fmt.Println não mudou em mais de 6 anos? Você verificou o código-fonte?
Shahriar
O comentário sobre a operação sugere que usar flag.Args()apenas como argumento fmt.Printlnfuncionou naquela época. (Seria surpreendente se não acontecesse naquela época)
leaf bebop
Então? Se houver um comentário, isso significa que minha resposta deve ser rejeitada?
Shahriar