269 votes

Type de conversion des tranches d'interfaces

Je suis curieux de savoir pourquoi Go ne convertit pas implicitement []T a []interface{} alors qu'il convertira implicitement T a interface{} . Y a-t-il quelque chose de non trivial dans cette conversion qui m'échappe ?

Exemple :

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

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

go build se plaint

ne peut pas utiliser un (type []string) comme type []interface {} dans l'argument d'une fonction

Et si j'essaie de le faire explicitement, c'est la même chose : b := []interface{}(a) se plaint

ne peut pas convertir un (type []string) en type []interface {}

Ainsi, chaque fois que j'ai besoin d'effectuer cette conversion (ce qui semble se produire souvent), j'ai procédé de la manière suivante :

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

Existe-t-il une meilleure façon de procéder, ou des fonctions de la bibliothèque standard pour faciliter ces conversions ? Il me 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.

297voto

Stephen Weinberg Points 12682

En Go, il existe une règle générale selon laquelle la syntaxe ne doit pas cacher des opérations complexes/coûteuses .

Conversion d'un string à un interface{} se fait en O(1) temps. La conversion d'un []string à un interface{} se fait également en O(1) temps puisqu'une tranche est toujours une valeur. Cependant, la conversion d'une []string à un []interface{} est en temps O(n) car chaque élément de la tranche doit être converti en un élément interface{} .

La seule exception à cette règle est la conversion des chaînes de caractères. Lors de la conversion d'une string en provenance et à destination d'un []byte o un []rune Go fait un travail O(n) même si les conversions sont "syntaxiques".

Il n'existe pas de fonction de la bibliothèque standard qui effectue cette conversion à votre place. Votre meilleure option est d'utiliser les lignes de code que vous avez données dans votre question :

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

Sinon, vous pouvez en faire un avec reflect mais elle serait plus lente que l'option des 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")
    }

    // Keep the distinction between nil and empty slice input
    if s.IsNil() {
        return nil
    }

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

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

    return ret
}

73voto

Nick Craig-Wood Points 18742

Ce qui vous échappe, c'est que T y interface{} qui contient une valeur de T ont des représentations différentes en mémoire et ne peuvent donc pas être converties de manière triviale.

Une variable de type T est simplement sa valeur en mémoire. Il n'y a pas d'information de type associée (en Go, chaque variable a un type unique connu au moment de la compilation et non au moment de l'exécution). Elle est représentée en mémoire de la manière suivante :

  • valeur

Un interface{} contenant une variable de type T est représenté en mémoire comme suit

  • pointeur sur le type T
  • valeur

Pour en revenir à votre question initiale : pourquoi le go ne convertit-il pas implicitement ? []T a []interface{} ?

Conversion []T a []interface{} impliquerait la création d'une nouvelle tranche de interface {} ce qui est une opération non triviale puisque la disposition en mémoire est complètement différente.

21voto

Yandry Pozo Points 2210

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
}

7voto

dskinner Points 2692

Pruebe interface{} au lieu de cela. Pour rejeter en tant que tranche, essayez

func foo(bar interface{}) {
    s := bar.([]string)
    // ...
}

7voto

I Love Reflection Points 2124

Dans Go 1.18 ou une version ultérieure, utilisez la fonction suivante pour convertir un type de tranche arbitraire en []interface{} ou son alias any :

func ToSliceOfAny[T any](s []T) []any {
    result := make([]any, len(s))
    for i, v := range s {
        result[i] = v
    }
    return result
}

La fonctionnalité générique de Go 1.18 n'élimine pas la nécessité de convertir une tranche arbitraire en []any . Voici un exemple de cas où la conversion est nécessaire : L'application veut interroger une base de données en utilisant les éléments d'un []string en tant qu'arguments d'interrogation variables déclarés en tant que args ...any . La fonction de cette réponse permet à l'application d'interroger la base de données en une seule ligne :

rows, err := db.Query(qs, ToSliceOfAny(stringArgs)...)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X