Patrice Ferlet
Patrice Ferlet
Créateur de ce blog.
Publié le sept. 19, 2015 Temps de lecture: 4 min

Me suivre sur Mastodon

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

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.

comments powered by Disqus