"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Utiliser Maps en toute sécurité dans Golang : différences de déclaration et d'initialisation

Utiliser Maps en toute sécurité dans Golang : différences de déclaration et d'initialisation

Publié le 2024-11-08
Parcourir:147

Safely using Maps in Golang: Differences in declaration and initialization

Introduction

Cette semaine, je travaillais sur l'un des packages de wrapper d'API pour Golang, qui traitait de l'envoi de demandes de publication avec des valeurs codées en URL, de la configuration des cookies et de toutes les choses amusantes. Cependant, pendant que je construisais le corps, j'utilisais le type url.Value pour construire le corps et je l'utilisais pour ajouter et définir des paires clé-valeur. Cependant, j'obtenais une erreur de référence de pointeur nul câblé dans certaines parties, je pensais que c'était à cause de certaines des variables que j'avais définies manuellement. Cependant, en déboguant de plus près, j'ai découvert un piège courant ou une mauvaise pratique consistant simplement à déclarer un type mais à l'initialiser, ce qui provoque des erreurs de référence nulles.

Dans cet article, je vais expliquer ce que sont les cartes, comment créer des cartes, et surtout comment les déclarer et les initialiser correctement. Créez une distinction appropriée entre la déclaration et l'initialisation des cartes ou de tout type de données similaire dans Golang.

Qu'est-ce qu'une carte à Golang ?

Une carte ou un hashmap en golang est un type de données de base qui nous permet de stocker des paires clé-valeur. Sous le capot, il s'agit d'une structure de données de type carte d'en-tête qui contient des compartiments, qui sont essentiellement des pointeurs vers des tableaux de compartiments (mémoire contiguë). Il contient des codes de hachage qui stockent les paires clé-valeur réelles et des pointeurs vers de nouveaux compartiments si le courant déborde du nombre de clés. Il s'agit d'une structure de données vraiment intelligente qui fournit un accès temporel presque constant.

Comment créer des cartes dans Golang

Pour créer une carte simple en golang, vous pouvez prendre un exemple de compteur de fréquence de lettres utilisant une carte de chaîne et d'entier. La carte stockera les lettres sous forme de clés et leur fréquence sous forme de valeurs.

package main

import "fmt"

func main() {
    words := "hello how are you"
    letters := map[string]int{}

    for _, word := range words {
        wordCount[word]  
    }

    fmt.Println("Word counts:")
    for word, count := range wordCount {
        fmt.Printf("%s: %d\n", word, count)
    }
}
$ go run main.go

Word counts:
e: 2
 : 3
w: 1
r: 1
y: 1
u: 1
h: 2
l: 2
o: 3
a: 1

Ainsi, en initialisant la carte en tant que map[string]int{}, vous obtiendrez une carte vide. Cela peut ensuite être utilisé pour remplir les clés et les valeurs, nous parcourons la chaîne et pour chaque caractère (rune), nous convertissons cet octet de caractère dans la chaîne et incrémentons la valeur, la valeur zéro pour int est 0, donc par défaut si la clé n'est pas présente, elle sera nulle, c'est un peu à double tranchant cependant, on ne sait jamais que la clé est présente dans la map avec la valeur 0 ou que la clé n'est pas présente mais la valeur par défaut est 0. Pour cela, vous devez vérifier si la clé existe ou non dans la carte.

Pour plus de détails, vous pouvez consulter mon article Golang Maps en détail.

Différence entre déclaration et initialisation

Il y a une différence dans la déclaration et l'initialisation de n'importe quelle variable dans un langage de programmation et cela doit faire beaucoup plus avec l'implémentation du type sous-jacent. Dans le cas de types de données primaires comme int, string, float, etc., il existe des valeurs par défaut/zéro, ce qui revient donc à la déclaration et à l'initialisation des variables. Cependant, dans le cas des cartes et des tranches, la déclaration garantit simplement que la variable est disponible dans la portée du programme, mais pour l'initialisation, elle la définit sur sa valeur par défaut/zéro ou sur la valeur réelle qui doit être attribuée.

Ainsi, la déclaration rend simplement la variable disponible dans le cadre du programme. Pour les cartes et les tranches, déclarer une variable sans initialisation la définit sur zéro, ce qui signifie qu'elle ne pointe vers aucune mémoire allouée et ne peut pas être utilisée directement.

Alors que l'initialisation alloue de la mémoire et définit la variable dans un état utilisable. Pour les cartes et les tranches, vous devez les initialiser explicitement en utilisant une syntaxe telle que myMap = make(map[keyType]valueType) ou slice = []type{}. Sans cette initialisation, tenter d'utiliser la carte ou la tranche entraînera des erreurs d'exécution, telles que des paniques pour accéder ou modifier une carte ou une tranche nulle.

Regardons les valeurs d'une carte lorsqu'elle est déclarée/initialisée/non initialisée.

Imaginez que vous créez un gestionnaire de configuration qui lit les paramètres d'une carte. La carte sera déclarée globalement mais initialisée uniquement au chargement de la configuration.

  1. Déclaré mais non initialisé

Le code ci-dessous illustre un accès à la carte qui n'est pas initialisé.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings map[string]string // Declared but not initialized

func main() {
    // Attempt to get a configuration setting before initializing the map
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func getConfigSetting(key string) string {
    if configSettings == nil {
        log.Fatal("Configuration settings map is not initialized")
    }
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Server port: Setting not found
  1. Déclaré et initialisé en même temps

Le code ci-dessous montre un accès à la carte qui est initialisé en même temps.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings = map[string]string{
    "server_port":  "8080",
    "database_url": "localhost:5432",
}

func main() {
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func getConfigSetting(key string) string {
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Server port: 8080
  1. Déclaré puis initialisé

Le code ci-dessous montre un accès à la carte qui est initialisé ultérieurement.

package main

import (
    "fmt"
    "log"
)

// Global map to store configuration settings
var configSettings map[string]string // declared but not initialized

func main() {
    // Initialize configuration settings
    initializeConfigSettings()
    // if the function is not called, the map will be nil

    // Get a configuration setting safely
    serverPort := getConfigSetting("server_port")
    fmt.Printf("Server port: %s\n", serverPort)
}

func initializeConfigSettings() {
    if configSettings == nil {
        configSettings = make(map[string]string) // Properly initialize the map
        configSettings["server_port"] = "8080"
        configSettings["database_url"] = "localhost:5432"
        fmt.Println("Configuration settings initialized")
    }
}

func getConfigSetting(key string) string {
    if configSettings == nil {
        log.Fatal("Configuration settings map is not initialized")
    }
    value, exists := configSettings[key]
    if !exists {
        return "Setting not found"
    }
    return value
}
$ go run main.go
Configuration settings initialized
Server port: 8080

Dans le code ci-dessus, nous avons déclaré les paramètres de configuration de la carte globale mais ne l'avons pas initialisé à ce stade, jusqu'à ce que nous voulions accéder à la carte. Nous initialisons la carte dans la fonction principale, cette fonction principale pourrait être d'autres parties spécifiques du code, et la variable globale configSettings une carte d'une autre partie du code, en l'initialisant dans la portée requise, nous l'empêchons de provoquer un pointeur nul erreurs d'accès. Nous n'initialisons la carte que si elle est nulle, c'est-à-dire qu'elle n'a pas été initialisée ailleurs dans le code. Cela évite de remplacer la carte/d'éliminer l'ensemble de configuration d'autres parties de la portée.

Pièges liés à l'accès aux cartes non initialisées

Mais comme il s'agit de pointeurs, il comporte ses propres pièges, comme l'accès nul aux pointeurs lorsque la carte n'est pas initialisée.

Prenons un exemple, un cas réel où cela pourrait se produire.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        var vals url.Values
        vals.Add("foo", "bar")
        fmt.Println(vals)
}

Cela entraînera une panique à l'exécution.

$ go run main.go
panic: assignment to entry in nil map

goroutine 1 [running]:
net/url.Values.Add(...)
        /usr/local/go/src/net/url/url.go:902
main.main()
        /home/meet/code/playground/go/main.go:10  0x2d
exit status 2

C'est parce que l'url.Values ​​est une carte de chaîne et une liste de valeurs de chaîne. Puisque le type sous-jacent est une carte pour les valeurs, et dans l'exemple, nous avons uniquement déclaré la variable vals avec le type url.Values, elle pointera vers une référence nulle, d'où le message sur l'ajout de la valeur au type. C'est donc une bonne pratique d'utiliser make lors de la déclaration ou de l'initialisation d'un type de données cartographiques. Si vous n'êtes pas sûr que le type sous-jacent est map, vous pouvez utiliser Type{} pour initialiser une valeur vide de ce type.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        vals := make(url.Values)
        // OR
        // vals := url.Values{}
        vals.Add("foo", "bar")
        fmt.Println(vals)
}
$ go run urlvals.go
map[foo:[bar]]
foo=bar

Il est également recommandé par l'équipe golang d'utiliser la fonction make lors de l'initialisation d'une carte. Donc, utilisez make pour les cartes, les tranches et les canaux, ou initialisez la variable de valeur vide avec Type{}. Les deux fonctionnent de la même manière, mais ce dernier s’applique également de manière plus générale aux structures.

Conclusion

Comprendre la différence entre déclarer et initialiser des cartes dans Golang est essentiel pour tout développeur, pas seulement dans Golang, mais en général. Comme nous l'avons vu, le simple fait de déclarer une variable de carte sans l'initialiser peut entraîner des erreurs d'exécution, telles que des paniques lors de la tentative d'accès ou de modification d'une carte nulle. L'initialisation d'une carte garantit qu'elle est correctement allouée en mémoire et prête à l'emploi, évitant ainsi ces pièges.

En suivant les bonnes pratiques, telles que l'utilisation de la fonction make ou l'initialisation avec Type{}, vous pouvez éviter les problèmes courants liés aux cartes non initialisées. Assurez-vous toujours que les cartes et les tranches sont explicitement initialisées avant utilisation pour vous protéger contre les déréférencements inattendus de pointeurs nuls

Merci d'avoir lu cet article. Si vous avez des questions, des commentaires et des suggestions, n'hésitez pas à les déposer dans les commentaires.

Joyeux codage :)

Déclaration de sortie Cet article est reproduit sur : https://dev.to/mr_destructive/safely-using-maps-in-golang-differences-in-declaration-and-initialization-2jfi?1 En cas de violation, veuillez contacter study_golang@163 .com pour le supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3