Type de conversion de tranches d'interfaces

194

Je suis curieux de savoir pourquoi Go ne se convertit []Tpas implicitement en []interface{}quand il se convertira implicitement Ten interface{}. Y a-t-il quelque chose de non trivial dans cette conversion qui me manque?

Exemple:

func foo([]interface{}) { /* do something */ }

func main() {
    var a []string = []string{"hello", "world"}
    foo(a)
}

go build Se plaint

impossible d'utiliser une chaîne (type []) comme type [] interface {} dans l'argument de la fonction

Et si j'essaye de le faire explicitement, même chose: se b := []interface{}(a)plaindre

impossible de convertir une (type [] string) en type [] interface {}

Donc, chaque fois que j'ai besoin de faire cette conversion (ce qui semble arriver souvent), j'ai fait quelque chose comme ceci:

b = make([]interface{}, len(a), len(a))
for i := range a {
    b[i] = a[i]
}

Existe-t-il une meilleure façon de faire cela, ou des fonctions de bibliothèque standard pour aider à ces conversions? Cela semble un peu idiot d'écrire 4 lignes de code supplémentaires chaque fois que je veux appeler une fonction qui peut prendre une liste d'ints ou de chaînes, par exemple.

Danny
la source
vous définissez donc "convertir implicitement [] T en [] interface {}" pour signifier "allouer une nouvelle tranche et copier tous les éléments dessus". Cela est incompatible avec ce que fait "convertir implicitement T en interface {}", qui est juste une vue de la même valeur qu'un type statique plus générique; rien n'est copié; et si vous saisissez-le de nouveau au type T, vous obtiendrez toujours la même chose.
newacct
1
ne pensait pas trop en détail à la façon dont cela serait implémenté, mais simplement qu'à un niveau supérieur, il serait pratique de pouvoir facilement convertir une liste entière en un autre type comme solution de contournement pour ne pas avoir de types génériques.
danny

Réponses:

215

Dans Go, il existe une règle générale selon laquelle la syntaxe ne doit pas masquer les opérations complexes / coûteuses. La conversion de a stringen an interface{}se fait en temps O (1). La conversion de a []stringen an interface{}se fait également en temps O (1) car une tranche est toujours une valeur. Cependant, la conversion de a []stringen an []interface{}est un temps O (n) car chaque élément de la tranche doit être converti en un interface{}.

La seule exception à cette règle est la conversion de chaînes. Lors de la conversion de a stringvers et depuis a []byteou a []rune, Go fait O (n) fonctionne même si les conversions sont "syntaxiques".

Il n'y a pas de fonction de bibliothèque standard qui fera cette conversion pour vous. Vous pouvez en créer un avec Reflect, mais ce serait plus lent que l'option à trois lignes.

Exemple avec réflexion:

func InterfaceSlice(slice interface{}) []interface{} {
    s := reflect.ValueOf(slice)
    if s.Kind() != reflect.Slice {
        panic("InterfaceSlice() given a non-slice type")
    }

    ret := make([]interface{}, s.Len())

    for i:=0; i<s.Len(); i++ {
        ret[i] = s.Index(i).Interface()
    }

    return ret
}

Votre meilleure option est cependant simplement d'utiliser les lignes de code que vous avez fournies dans votre question:

b := make([]interface{}, len(a))
for i := range a {
    b[i] = a[i]
}
Stephen Weinberg
la source
3
cela pourrait être plus lent, mais cela fonctionnerait de manière générique avec n'importe quel type de tranche
newacct
Toute cette réponse s'applique à maps trop btw.
RickyA
Cela vaut également pour channels.
Justin Ohms
Merci pour une explication claire, si possible, pouvez-vous ajouter la réf. pour Converting a []string to an interface{} is also done in O(1) time?
Mayur
56

La chose qui vous manque est celle-là Tet interface{}qui détient une valeur de Tavoir différentes représentations en mémoire et ne peut donc pas être convertie de manière triviale.

Une variable de type Test juste sa valeur en mémoire. Il n'y a pas d'informations de type associées (dans Go, chaque variable a un seul type connu au moment de la compilation et non au moment de l'exécution). Il est représenté en mémoire comme ceci:

  • valeur

Un interface{}tenant une variable de type Test représenté en mémoire comme ceci

  • pointeur sur le type T
  • valeur

Revenons donc à votre question initiale: pourquoi aller n'est-il pas implicitement converti []Ten []interface{}?

La conversion []Ten []interface{}impliquerait la création d'une nouvelle tranche de interface {}valeurs, ce qui est une opération non triviale car la disposition en mémoire est complètement différente.

Nick Craig-Wood
la source
10
C'est informatif et bien écrit (+1), mais vous ne répondez pas à l'autre partie de sa question: "Y a-t-il une meilleure façon de faire cela ..." (-1).
weberc2
13

Voici l'explication officielle: https://github.com/golang/go/wiki/InterfaceSlice

var dataSlice []int = foo()
var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
for i, d := range dataSlice {
    interfaceSlice[i] = d
}
Yandry Pozo
la source
Bonne info mais ce n'est pas une explication officielle. C'est un wiki où tout le monde peut éditer.
Inanc Gumus
Comment dois-je convertir []interfaceau type auquel je m'attends, par exemple []map[string]interface{}?
Lewis Chan
8

Essayez à la interface{}place. Pour reconstituer une tranche, essayez

func foo(bar interface{}) {
    s := bar.([]string)
    // ...
}
dskinner
la source
3
Cela soulève la question suivante: comment l'OP est-il alors supposé itérer barpour l'interpréter comme "une tranche de n'importe quel type"? Notez que son triple revêtement crée un []interface{}, pas []stringou une tranche d'un autre type de béton.
kostix
14
-1: Cela ne fonctionne que si bar est [] string, auquel cas, vous pouvez aussi écrire:func foo(bar []string) { /* ... */ }
weberc2
2
utilisez un commutateur de type ou utilisez la réflexion comme dans la réponse acceptée.
dskinner le
OP devra de toute façon déterminer son type au moment de l'exécution. L'utilisation de non slice interface{}empêchera le compilateur de se plaindre lors de la transmission d'un argument comme dans foo(a). Il pourrait soit utiliser refléter ou taper la commutation ( golang.org/doc/effective_go.html#type_switch ) à l'intérieur foo.
Cassiohg
2

Convertissez interface{}en n'importe quel type.

Syntaxe:

result := interface.(datatype)

Exemple:

var employee interface{} = []string{"Jhon", "Arya"}
result := employee.([]string)   //result type is []string.
yala ramesh
la source
2
En es-tu sûr? Je ne pense pas que ce soit valable. Vous allez le voir: invalid type assertion: potato.([]string) (non-interface type []interface {} on left)
Adriano Tadao
2

Au cas où vous auriez besoin de raccourcir plus votre code, vous pouvez créer un nouveau type pour helper

type Strings []string

func (ss Strings) ToInterfaceSlice() []interface{} {
    iface := make([]interface{}, len(ss))
    for i := range ss {
        iface[i] = ss[i]
    }
    return iface
}

puis

a := []strings{"a", "b", "c", "d"}
sliceIFace := Strings(a).ToInterfaceSlice()
RaditzLawliet
la source