Maison > développement back-end > Golang > 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

王林
Libérer: 2024-08-31 22:31:08
original
1267 Les gens l'ont consulté

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 ne provoquait aucune erreur de référence.

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 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 l'exemple d'un 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)
    }
}
Copier après la connexion
$ 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
Copier après la connexion

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 montre 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
}
Copier après la connexion
$ go run main.go
Server port: Setting not found
Copier après la connexion
  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
}
Copier après la connexion
$ go run main.go
Server port: 8080
Copier après la connexion
  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
}
Copier après la connexion
$ go run main.go
Configuration settings initialized
Server port: 8080
Copier après la connexion

In the above code, we declared the global map configSettings but didn't initialize it at that point, until we wanted to access the map. We initialize the map in the main function, this main function could be other specific parts of the code, and the global variable configSettings a map from another part of the code, by initializing it in the required scope, we prevent it from causing nil pointer access errors. We only initialize the map if it is nil i.e. it has not been initialized elsewhere in the code. This prevents overriding the map/flushing out the config set from other parts of the scope.

Pitfalls in access of un-initialized maps

But since it deals with pointers, it comes with its own pitfalls like nil pointers access when the map is not initialized.

Let's take a look at an example, a real case where this might happen.

package main

import (
    "fmt"
    "net/url"
)

func main() {
        var vals url.Values
        vals.Add("foo", "bar")
        fmt.Println(vals)
}
Copier après la connexion

This will result in a runtime panic.

$ 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
Copier après la connexion

This is because the url.Values is a map of string and a list of string values. Since the underlying type is a map for Values, and in the example, we only have declared the variable vals with the type url.Values, it will point to a nil reference, hence the message on adding the value to the type. So, it is a good practice to use make while declaring or initializing a map data type. If you are not sure the underlying type is map then you could use Type{} to initialize an empty value of that type.

package main

import (
    "fmt"
    "net/url"
)

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

It is also recommended by the golang team to use the make function while initializing a map. So, either use make for maps, slices, and channels, or initialize the empty value variable with Type{}. Both of them work similarly, but the latter is more generally applicable to structs as well.

Conclusion

Understanding the difference between declaring and initializing maps in Golang is essential for any developer, not just in golang, but in general. As we've explored, simply declaring a map variable without initializing it can lead to runtime errors, such as panics when attempting to access or modify a nil map. Initializing a map ensures that it is properly allocated in memory and ready for use, thereby avoiding these pitfalls.

By following best practices—such as using the make function or initializing with Type{}—you can prevent common issues related to uninitialized maps. Always ensure that maps and slices are explicitly initialized before use to safeguard against unexpected nil pointer dereferences

Thank you for reading this post, If you have any questions, feedback, and suggestions, feel free to drop them in the comments.

Happy Coding :)

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal