"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Usando mapas com segurança em Golang: diferenças na declaração e inicialização

Usando mapas com segurança em Golang: diferenças na declaração e inicialização

Publicado em 2024-11-08
Navegar:640

Safely using Maps in Golang: Differences in declaration and initialization

Introdução

Esta semana, eu estava trabalhando em um dos pacotes wrapper da API para golang, que tratava do envio de solicitações de postagem com valores codificados em URL, configuração de cookies e todas as coisas divertidas. No entanto, enquanto construía o corpo, usei o tipo url.Value para construir o corpo e usei-o para adicionar e definir pares de valores-chave. No entanto, eu estava recebendo um erro de referência de ponteiro nulo com fio em algumas partes, pensei que fosse por causa de algumas das variáveis ​​​​que defini manualmente. No entanto, ao depurar mais de perto, descobri uma armadilha comum ou má prática de apenas declarar um tipo, mas inicializá-lo e causar erros de referência nulos.

Neste post, irei abordar o que são mapas, como criá-los e, principalmente, como declará-los e inicializá-los adequadamente. Crie uma distinção adequada entre a declaração e inicialização de mapas ou qualquer tipo de dados semelhante em golang.

O que é um mapa em Golang?

Um mapa ou hashmap em golang é um tipo de dados básico que nos permite armazenar pares de valores-chave. Nos bastidores, é uma estrutura de dados semelhante a um mapa de cabeçalho que contém buckets, que são basicamente ponteiros para matrizes de buckets (memória contígua). Ele possui códigos hash que armazenam os pares de valores-chave reais e ponteiros para novos buckets se o atual estourar com o número de chaves. Esta é uma estrutura de dados realmente inteligente que fornece acesso em tempo quase constante.

Como criar mapas em Golang

Para criar um mapa simples em golang, você pode pegar um exemplo de contador de frequência de letras usando um mapa de string e número inteiro. O mapa armazenará as letras como chaves e sua frequência como valores.


pacote principal importar "fmt" função principal() { palavras: = "olá, como vai você" letras := mapa[string]int{} para _, palavra := intervalo de palavras { contagem de palavras[palavra] } fmt.Println("Contagem de palavras:") para palavra, conte := range wordCount { fmt.Printf("%s: %d\n", palavra, contagem) } }
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)
    }
}
$ vá executar main.go Contagem de palavras: e: 2 : 3 w: 1 R: 1 você: 1 você: 1 h: 2 eu: 2 ó: 3 uma: 1
$ 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
Portanto, ao inicializar o mapa como map[string]int{} você obterá um mapa vazio. Isso pode ser usado para preencher as chaves e valores, iteramos sobre a string e, para cada caractere (runa), lançamos esse byte de caractere na string e incrementamos o valor, o valor zero para int é 0, então, por padrão se a chave não estiver presente, será zero, mas é uma faca de dois gumes, nunca sabemos que a chave está presente no mapa com o valor 0 ou a chave não está presente, mas o valor padrão é 0. Para isso, é necessário verificar se a chave existe ou não no mapa.

Para mais detalhes, você pode conferir minha postagem do Golang Maps em detalhes.

Diferença entre declaração e inicialização

Há uma diferença em declarar e inicializar qualquer variável em uma linguagem de programação e tem muito mais a ver com a implementação do tipo subjacente. No caso de tipos de dados primários como int, string, float, etc. existem valores padrão/zero, portanto é o mesmo que a declaração e inicialização das variáveis. Porém, no caso de mapas e fatias, a declaração serve apenas para garantir que a variável esteja disponível para o escopo do programa, porém para inicialização é configurá-la para seu valor padrão/zero ou para o valor real que deve ser atribuído.

Portanto, a declaração simplesmente disponibiliza a variável dentro do escopo do programa. Para mapas e fatias, declarar uma variável sem inicialização define-a como nula, o que significa que ela não aponta para nenhuma memória alocada e não pode ser usada diretamente.

Enquanto a inicialização aloca memória e define a variável para um estado utilizável. Para mapas e fatias, você precisa inicializá-los explicitamente usando sintaxe como myMap = make(map[keyType]valueType) ou slice = []type{}. Sem essa inicialização, a tentativa de usar o mapa ou fatia levará a erros de tempo de execução, como pânico ao acessar ou modificar um mapa ou fatia nula.

Vejamos os valores de um mapa quando ele é declarado/inicializado/não inicializado.

Imagine que você está construindo um gerenciador de configuração que lê as configurações de um mapa. O mapa será declarado globalmente, mas inicializado somente quando a configuração for carregada.

    Declarado, mas não inicializado
O código abaixo demonstra um acesso ao mapa que não foi inicializado.


pacote principal importar ( "fmt" "registro" ) //Mapa global para armazenar definições de configuração var configSettings map[string]string // Declarado mas não inicializado função principal() { // Tenta obter uma configuração antes de inicializar o mapa serverPort := getConfigSetting("porta_servidor") fmt.Printf("Porta do servidor: %s\n", serverPort) } func getConfigSetting(chave string) string { if configSettings == nil { log.Fatal("O mapa de configurações não foi inicializado") } valor, existe := configSettings[chave] se !existe { retornar "Configuração não encontrada" } valor de retorno }
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)
    }
}
$ vá executar main.go Porta do servidor: configuração não encontrada
$ 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
    Declarado e inicializado ao mesmo tempo
O código abaixo demonstra um acesso ao mapa que é inicializado ao mesmo tempo.


pacote principal importar ( "fmt" "registro" ) //Mapa global para armazenar definições de configuração var configSettings = mapa[string]string{ "porta_servidor": "8080", "database_url": "localhost:5432", } função principal() { serverPort := getConfigSetting("porta_servidor") fmt.Printf("Porta do servidor: %s\n", serverPort) } func getConfigSetting(chave string) string { valor, existe := configSettings[chave] se !existe { retornar "Configuração não encontrada" } valor de retorno }
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)
    }
}
$ vá executar main.go Porta do servidor: 8080
$ 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
    Declarado e posteriormente inicializado
O código abaixo demonstra um acesso ao mapa que é inicializado posteriormente.


pacote principal importar ( "fmt" "registro" ) //Mapa global para armazenar definições de configuração var configSettings map[string]string // declarado mas não inicializado função principal() { // Inicializa as configurações inicializeConfigSettings() // se a função não for chamada, o mapa será nulo // Obtenha uma configuração com segurança serverPort := getConfigSetting("porta_servidor") fmt.Printf("Porta do servidor: %s\n", serverPort) } func inicializeConfigSettings() { if configSettings == nil { configSettings = make(map[string]string) // Inicializa corretamente o mapa configSettings["porta_servidor"] = "8080" configSettings["database_url"] = "localhost:5432" fmt.Println("Definições de configuração inicializadas") } } func getConfigSetting(chave string) string { if configSettings == nil { log.Fatal("O mapa de configurações não foi inicializado") } valor, existe := configSettings[chave] se !existe { retornar "Configuração não encontrada" } valor de retorno }
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)
    }
}
$ vá executar main.go Definições de configuração inicializadas Porta do servidor: 8080
$ 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
No código acima, declaramos o mapa global configSettings, mas não o inicializamos naquele ponto, até que quiséssemos acessar o mapa. Inicializamos o mapa na função principal, esta função principal poderia ser outras partes específicas do código, e a variável global configSettings um mapa de outra parte do código, ao inicializá-la no escopo necessário, evitamos que ela cause ponteiro nulo erros de acesso. Só inicializamos o mapa se ele for nulo, ou seja, não foi inicializado em nenhum outro lugar do código. Isso evita substituir o mapa/eliminar o conjunto de configurações de outras partes do escopo.

Armadilhas no acesso a mapas não inicializados

Mas, como lida com ponteiros, ele apresenta suas próprias armadilhas, como acesso nulo a ponteiros quando o mapa não é inicializado.

Vamos dar uma olhada em um exemplo, um caso real onde isso pode acontecer.


pacote principal importar ( "fmt" "rede/url" ) função principal() { var vals url.Valores vals.Add("foo", "bar") fmt.Println(vales) }
$ 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
Isso resultará em pânico no tempo de execução.


$ vá executar main.go pânico: atribuição à entrada no mapa nulo goroutine 1 [em execução]: net/url.Values.Add(...) /usr/local/go/src/net/url/url.go:902 principal.principal() /home/meet/code/playground/go/main.go:10 0x2d status de saída 2
$ 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
Isso ocorre porque url.Values ​​é um mapa de string e uma lista de valores de string. Como o tipo subjacente é um mapa para Valores, e no exemplo, apenas declaramos a variável vals com o tipo url.Values, ela apontará para uma referência nula, daí a mensagem ao adicionar o valor ao tipo. Portanto, é uma boa prática usar make ao declarar ou inicializar um tipo de dados de mapa. Se você não tiver certeza de que o tipo subjacente é map, poderá usar Type{} para inicializar um valor vazio desse tipo.


pacote principal importar ( "fmt" "rede/url" ) função principal() { vals := make(url.Values) // OU // vals := url.Values{} vals.Add("foo", "bar") fmt.Println(vales) }
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)
    }
}
$ vá executar urlvals.go mapa[foo:[barra]] foo=barra
$ 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
Também é recomendado pela equipe golang usar a função make ao inicializar um mapa. Portanto, use make para mapas, fatias e canais ou inicialize a variável de valor vazia com Type{}. Ambos funcionam de forma semelhante, mas o último também é aplicável de forma mais geral a estruturas.

Conclusão

Entender a diferença entre declarar e inicializar mapas em Golang é essencial para qualquer desenvolvedor, não apenas em Golang, mas em geral. Como exploramos, simplesmente declarar uma variável de mapa sem inicializá-la pode levar a erros de tempo de execução, como pânico ao tentar acessar ou modificar um mapa nulo. A inicialização de um mapa garante que ele esteja devidamente alocado na memória e pronto para uso, evitando assim essas armadilhas.

Seguindo as práticas recomendadas, como usar a função make ou inicializar com Type{}, você pode evitar problemas comuns relacionados a mapas não inicializados. Sempre certifique-se de que os mapas e fatias sejam explicitamente inicializados antes do uso para proteger contra desreferências inesperadas de ponteiro nulo

Obrigado por ler esta postagem. Se você tiver dúvidas, comentários e sugestões, sinta-se à vontade para deixá-los nos comentários.

Boa codificação :)

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/mr_destructive/safely-using-maps-in-golang-differences-in-declaration-and-initialization-2jfi?1 Se houver alguma violação, entre em contato com study_golang@163 .com para excluí-lo
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3