"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Uso seguro de mapas en Golang: diferencias en declaración e inicialización

Uso seguro de mapas en Golang: diferencias en declaración e inicialización

Publicado el 2024-11-08
Navegar:506

Safely using Maps in Golang: Differences in declaration and initialization

Introducción

Esta semana, estuve trabajando en uno de los paquetes contenedores de API para golang, y se ocupaba del envío de solicitudes de publicación con valores codificados en URL, la configuración de cookies y todas las cosas divertidas. Sin embargo, mientras construía el cuerpo, estaba usando el tipo url.Value para construir el cuerpo y usarlo para agregar y establecer pares clave-valor. Sin embargo, recibí un error de referencia de puntero nulo cableado en algunas de las partes, pensé que se debía a algunas de las variables que configuré manualmente. Sin embargo, al depurar más de cerca, descubrí un error común o una mala práctica de simplemente declarar un tipo pero inicializarlo y eso causa errores de referencia nulos.

En esta publicación, cubriré qué son mapas, cómo crear mapas y, especialmente, cómo declararlos e inicializarlos correctamente. Cree una distinción adecuada entre la declaración y la inicialización de mapas o cualquier tipo de datos similar en golang.

¿Qué es un mapa en Golang?

Un mapa o un hashmap en golang es un tipo de datos básico que nos permite almacenar pares clave-valor. Debajo del capó, es una estructura de datos similar a un mapa de encabezado que contiene depósitos, que son básicamente punteros a matrices de depósitos (memoria contigua). Tiene códigos hash que almacenan los pares clave-valor reales y punteros a nuevos depósitos si el actual se desborda con la cantidad de claves. Esta es una estructura de datos realmente inteligente que proporciona acceso en tiempo casi constante.

Cómo crear mapas en Golang

Para crear un mapa simple en golang, puede tomar un ejemplo de un contador de frecuencia de letras usando un mapa de cadena y número entero. El mapa almacenará las letras como claves y su frecuencia como valores.

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

Entonces, al inicializar el mapa como map[string]int{} obtendrás un mapa vacío. Esto luego se puede usar para completar las claves y los valores, iteramos sobre la cadena y para cada carácter (runa) convertimos ese byte de carácter en la cadena e incrementamos el valor, el valor cero para int es 0, por lo que de forma predeterminada si la clave no está presente, será cero, aunque son un poco espadas de doble filo, nunca sabemos que la clave está presente en el mapa con el valor 0 o que la clave no está presente pero el valor predeterminado es 0. Para eso, debes verificar si la clave existe en el mapa o no.

Para obtener más detalles, puedes consultar mi publicación de Golang Maps en detalle.

Diferencia entre declaración e inicialización

Existe una diferencia al declarar e inicializar cualquier variable en un lenguaje de programación y tiene que ver mucho más con la implementación del tipo subyacente. En el caso de tipos de datos primarios como int, string, float, etc., hay valores predeterminados/cero, por lo que es lo mismo que la declaración e inicialización de las variables. Sin embargo, en el caso de mapas y sectores, la declaración solo garantiza que la variable esté disponible para el alcance del programa; sin embargo, para la inicialización, se establece en su valor predeterminado/cero o en el valor real que se debe asignar.

Entonces, la declaración simplemente hace que la variable esté disponible dentro del alcance del programa. Para mapas y sectores, declarar una variable sin inicialización la establece en nulo, lo que significa que no apunta a memoria asignada y no se puede usar directamente.

Mientras que la inicialización asigna memoria y establece la variable en un estado utilizable. Para mapas y sectores, debe inicializarlos explícitamente usando una sintaxis como myMap = make(map[keyType]valueType) o slice = []type{}. Sin esta inicialización, intentar utilizar el mapa o el segmento generará errores de tiempo de ejecución, como pánico al acceder o modificar un mapa o segmento nulo.

Veamos los valores de un mapa cuando se declara/inicializa/no inicializa.

Imagina que estás creando un administrador de configuración que lee la configuración de un mapa. El mapa se declarará globalmente pero se inicializará solo cuando se cargue la configuración.

  1. Declarado pero no inicializado

El siguiente código muestra un acceso al mapa que no está inicializado.

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. Declarado e inicializado al mismo tiempo

El siguiente código muestra un acceso al mapa que se inicializa al mismo tiempo.

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. Declarado y posteriormente inicializado

El siguiente código muestra un acceso al mapa que se inicializa más tarde.

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

En el código anterior, declaramos la configuración del mapa global pero no la inicializamos en ese punto, hasta que quisimos acceder al mapa. Inicializamos el mapa en la función principal, esta función principal podría ser otras partes específicas del código, y la variable global configSettings un mapa de otra parte del código, al inicializarlo en el alcance requerido, evitamos que cause un puntero nulo. errores de acceso. Solo inicializamos el mapa si es nulo, es decir, no se ha inicializado en ninguna otra parte del código. Esto evita anular el mapa/eliminar el conjunto de configuración de otras partes del alcance.

Errores en el acceso a mapas no inicializados

Pero dado que se trata de punteros, tiene sus propios inconvenientes, como el acceso nulo a los punteros cuando el mapa no está inicializado.

Veamos un ejemplo, un caso real donde esto podría suceder.

package main

import (
    "fmt"
    "net/url"
)

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

Esto provocará un pánico en tiempo de ejecución.

$ 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

Esto se debe a que url.Values ​​es un mapa de cadenas y una lista de valores de cadenas. Dado que el tipo subyacente es un mapa para Valores, y en el ejemplo, solo hemos declarado la variable vals con el tipo url.Values, apuntará a una referencia nula, de ahí el mensaje sobre agregar el valor al tipo. Por lo tanto, es una buena práctica utilizar make al declarar o inicializar un tipo de datos de mapa. Si no está seguro de que el tipo subyacente sea mapa, puede usar Type{} para inicializar un valor vacío de ese tipo.

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

El equipo de golang también recomienda utilizar la función make al inicializar un mapa. Entonces, use make para mapas, sectores y canales, o inicialice la variable de valor vacía con Type{}. Ambos funcionan de manera similar, pero el último también se aplica de manera más general a las estructuras.

Conclusión

Comprender la diferencia entre declarar e inicializar mapas en Golang es esencial para cualquier desarrollador, no solo en golang, sino en general. Como hemos explorado, simplemente declarar una variable de mapa sin inicializarla puede provocar errores de tiempo de ejecución, como pánico al intentar acceder o modificar un mapa nulo. La inicialización de un mapa garantiza que esté correctamente asignado en la memoria y listo para su uso, evitando así estos errores.

Al seguir las mejores prácticas, como usar la función make o inicializar con Type{}, puedes evitar problemas comunes relacionados con mapas no inicializados. Asegúrese siempre de que los mapas y sectores se inicialicen explícitamente antes de su uso para protegerlos contra desreferencias inesperadas de puntero nulo

Gracias por leer esta publicación. Si tienes alguna pregunta, comentario o sugerencia, no dudes en dejarla en los comentarios.

Codificación feliz :)

Declaración de liberación Este artículo se reproduce en: https://dev.to/mr_destructivo/safely-using-maps-in-golang-differences-in-declaration-and-initialization-2jfi?1 Si hay alguna infracción, comuníquese con Study_golang@163 .com para eliminarlo
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3