Golang, résoudre le souci d'indexation de type défini

19/09/2015

Golang permet de créer ses prores types et notamment de faire un alias de “map”, mais un jour, en voulant récupérer une valeur indexée dans le map, vous recevez un fameux “type *Foo does not support indexing” lors de la compilation. Ce souci peut-être rapidement réglé à condition de comprendre pourquoi et comment cette erreur arrive.

TL;DR

Vous vous trompez ! Un map est déjà un pointeur, donc arrêtez d’attendre un pointeur sur ce type. Changez votre fonction pour attendre “Foo” et non “*Foo”.

Si vraiment vous voulez utiliser un pointeur sur pointeur, il faut déréférencer, c’est à dire: utiliser (*var)["index"] au lieu var["index"] dans votre fonction

Explication détaillée

Go serait-il aussi chiant que C/C++ alors ?

En gros, que ce soit en C/C++ ou en Go, les tableaux sont des pointeurs. Le fait de définir un type qui surcharge un tableau (map, slice, etc…) prête à confusion.

Imaginons un type Foo qui surcharge un map[string]interface{}:

type Foo map[string]interface{}

Un fonction qui utiliserai ce type pourrait être:

func Modify(f *Foo) {
    //... fait des choses 
}

Le souci est que “f” est un pointeur sur un map, donc un pointeur sur un pointeur… et la modification (expliquée dans la seconde partie) demande un déréférencement. C’est-à-dire “(*f)[…]“.

En réalité, la fonction devrait être:

func Modify(f Foo) {
    //... fait des choses 
}

Ici, “f” est un pointeur puisque c’est un “map”. Donc:

package main

import "fmt"

type Foo map[string]interface{}

func Modify(f Foo) {
    f["hello"] = "world"
}

func main() {
    a := make(Foo)
    Modify(a)
    fmt.Println(a)
}

On teste:

$ go prog.go
map[hello:world]

Cela fonctionne, car “Foo” est un type “pointeur” de par le fait qu’il est un “map”.

Cas d’un pointeur sur pointeur

Dans certains cas, on se force à uiliser un pointeur sur le type défini, même se celui-ci est un map. Cela va induire que la fonction qui récupère un “*Foo” récupère un pointeur sur pointeur, puisque c’est un pointeur sur un map et qu’un map est un poitneur… Vous suivez ?

Prenons un exemple simple.

package main

import "fmt"

// type alias pour un map[string]interface{}
type Foo map[string]interface{}

// affiche l'index "test"
func Test(f *Foo) {
    // l'erreur arrive ici:
    if v, ok := f["test"]; ok {
        fmt.Println(v)    
    }    
}

func main() {
    // on crée notre variable Foo
    a := make(Foo)
    // on assigne une valeur à l'index "test"
    a["test"] = "hello"
    // on l'affiche
    Test(&a)
}

Et là:

$ go run prog.go
prog.go:11: invalid operation: f["test"] (type *Foo does not support indexing)

Le problème est simple. La variable “f” est ici un pointeur. Comprennez bien que “f” est donc une adresse !

Et comme à cette adresse on trouve… un autre adresse - car la valeur est un map et qu’un map correspond à une adresse… houlalala… ça se complique…

Bon..:

  • f est de type Foo
  • Foo est un map, donc un pointeur
  • f *Foo => f est donc un pointeur sur un pointeur

Donc, la réponse est simple, il faut déréférencer la variable, ou pour être plus près de la vérité, il faut “utiliser la valeur pointée” par l’adresse récupérée dans la fonction.

Ce que je veux dire c’est que “f” étant une adresse, si on récupère la valeur à cette adresse, on tombe sur le “map”.

Donc dans la fonction “Test” on va utiliser “(*f)” pour retrouver le map… Ça va ?

En clair:

package main

import "fmt"

// type alias pour un map[string]interface{}
type Foo map[string]interface{}

// affiche l'index "test"
func Test(f *Foo) {
    // On corrige, on utilise maintenant "la valeur pointée par f"
    if v, ok := (*f)["test"]; ok {
        fmt.Println(v)    
    }    
}

func main() {
    // on crée notre variable Foo
    a := make(Foo)
    // on assigne une valeur à l'index "test"
    a["test"] = "hello"
    // on l'affiche
    Test(&a)
}

Cette foi:

$ go run prog.go
hello

et pourquoi pas “*f["test"]” ?

Simplement parce que *f["test"] veut dire “valeur pointée à l’adresse f["test"]” - et c’est pas ce que nous voulons.

Alors que “(*f)["test"]” veut dire “prend la valeur pointée en f (donc on a le map) et récupère dans ce map l’index ‘test’”.

Sémantiquement, c’est différent.

Ça peut vous intéresser aussi


Un exemple Golang de résolution de tâche parallèle

J’ai participé aux BlendWebMix 2015 en tant que “...


Mise à jour du blog en Go

Si vous connaissiez mon blog, vous avez remarqué qu’à ...


Golang, comment définir un destructeur

Si vous avez un peu bourlingué sur Go, vous savez ...


Technique de masque binaire

Une fois n’est pas coutume, j’ai décidé d’...

Merci de m'aider à financer mes services

Si vous avez apprécié cet article, je vous serai reconnaissant de m'aider à me payer une petite bière :)

Si vous voulez en savoir plus sur l'utilisation de flattr sur mon blog, lisez cette page: Ayez pitié de moi

Commentaires

Ajouter un commentaire

Ajouter un commentaire

(*) Votre e-mail ne sera ni revendu, ni rendu public, ni utilisé pour vous proposer des mails commerciaux. Il n'est utilisé que pour vous contacter en cas de souci avec le contenu du commentaire, ou pour vous prévenir d'un nouveau commentaire si vous avez coché la case prévue à cet effet.