web-dev-qa-db-fra.com

Impossible de convertir la chaîne [] en interface [] {}

J'écris du code, et j'en ai besoin pour saisir les arguments et les passer à travers fmt.Println
(Je veux son comportement par défaut, écrire des arguments séparés par des espaces et suivis d'une nouvelle ligne). Cependant, cela prend []interface {} mais flag.Args() renvoie un []string.
Voici l'exemple de code:

package main

import (
    "fmt"
    "flag"
)

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

Cela renvoie l'erreur suivante:

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

Est-ce un bug? fmt.Println ne devrait-il pas prendre any array? A propos, j'ai aussi essayé de faire ça:

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

mais j'obtiens l'erreur suivante:

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

Existe-t-il un moyen de contourner ce problème?

73
cruizh

Ce n'est pas un bug. fmt.Println() nécessite un type []interface{}. Cela signifie qu'il doit s'agir d'une tranche de valeurs interface{} et non de "toute tranche". Pour convertir la tranche, vous aurez besoin de faire une boucle et de copier chaque élément.

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

La raison pour laquelle vous ne pouvez utiliser aucune tranche est que la conversion entre un []string et un []interface{} nécessite la modification de la structure de la mémoire et est effectuée dans le temps O(n). La conversion d'un type en interface{} nécessite O(1) temps. S'ils rendaient cette boucle inutile, le compilateur aurait toujours besoin de l'insérer.

94
Stephen Weinberg

Si vous souhaitez imprimer uniquement une chaîne de chaînes, vous pouvez éviter la conversion et obtenir exactement le même résultat en joignant:

package main

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

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

Je pense qu'il est possible d'utiliser la réflexion, mais je ne sais pas si c'est une bonne 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)
}

Sortie:

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

Dans Go, une fonction ne peut accepter que les arguments des types spécifiés dans la liste de paramètres de la définition de la fonction. La fonctionnalité de langage de paramètre variadique complique un peu cela, mais elle suit des règles bien définies.

La signature de fonction pour fmt.Println est:

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

Selon la spécificité linguistique ,

Le dernier paramètre entrant dans une signature de fonction peut avoir un type préfixé avec .... Une fonction avec un tel paramètre est appelée variadique et peut être appelée avec zéro ou plusieurs arguments pour ce paramètre.

Cela signifie que vous pouvez transmettre à Println une liste d'arguments de type interface{}. Etant donné que tous les types implémentent l'interface vide, vous pouvez transmettre une liste d'arguments de n'importe quel type, ce qui vous permet d'appeler Println(1, "one", true), par exemple, sans erreur. Voir la section "Passage d'arguments à ... paramètres" de la spécification de langue:

la valeur transmise est une nouvelle tranche de type [] T avec un nouveau tableau sous-jacent dont les éléments successifs sont les arguments réels, qui doivent tous être assignables à T.

La partie qui vous pose problème se trouve juste après dans la spécification:

Si l'argument final est assignable à un type de tranche [] T, il peut être transmis sans modification en tant que valeur d'un paramètre ... T si l'argument est suivi de .... Dans ce cas, aucune nouvelle tranche n'est créée.

flag.Args() est de type []string. Puisque T dans Println est interface{}, []T est []interface{}. La question est donc de savoir si une valeur de tranche de chaîne peut être assignée à une variable de type de tranche d’interface. Vous pouvez facilement tester cela dans votre code d'identification en tentant une affectation, par exemple:

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

Si vous tentez une telle affectation, le compilateur générera ce message d'erreur:

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

Et c’est pourquoi vous ne pouvez pas utiliser les Ellipsis après une tranche de chaîne comme argument de fmt.Println. Ce n'est pas un bug, il fonctionne comme prévu.

Vous pouvez toujours imprimer flag.Args() avec Println de différentes manières, par exemple

fmt.Println(flag.Args())

(qui sera affiché sous la forme [elem0 elem1 ...], par documentation du paquet fmt )

ou

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

(qui produira les éléments de la tranche de chaîne, chacun étant séparé par un seul espace) en utilisant la fonction Join dans le package de chaînes avec un séparateur de chaîne, par exemple.

0
jrefior