>  기사  >  백엔드 개발  >  Golang에서 지도를 안전하게 사용하기: 선언과 초기화의 차이점

Golang에서 지도를 안전하게 사용하기: 선언과 초기화의 차이점

王林
王林원래의
2024-08-31 22:31:081064검색

Safely using Maps in Golang: Differences in declaration and initialization

소개

이번 주에 저는 golang용 API 래퍼 패키지 중 하나를 작업하고 있었는데, 그 패키지에서는 URL 인코딩 값이 포함된 게시물 요청 보내기, 쿠키 설정 및 모든 재미있는 작업을 다루었습니다. 하지만 본문을 구성하는 동안 url.Value 유형을 사용하여 본문을 구성하고 이를 사용하여 키-값 쌍을 추가하고 설정했습니다. 그러나 일부 부분에서 유선 nil 포인터 참조 오류가 발생했습니다. 이는 제가 수동으로 설정한 일부 변수 때문이라고 생각했습니다. 하지만 자세히 디버깅하면서 유형을 선언하기만 하고 초기화하여 nil 참조 오류를 일으키는 일반적인 함정이나 나쁜 습관을 발견했습니다.

이번 게시물에서는 맵이 무엇인지, 맵을 만드는 방법, 특히 맵을 올바르게 선언하고 초기화하는 방법을 다루겠습니다. golang에서 맵이나 유사한 데이터 유형의 선언과 초기화를 적절하게 구분하세요.

Golang의 지도란 무엇인가요?

golang의 맵 또는 해시맵은 키-값 쌍을 저장할 수 있는 기본 데이터 유형입니다. 내부적으로는 기본적으로 버킷 배열(연속 메모리)에 대한 포인터인 버킷을 보유하는 헤더 맵과 같은 데이터 구조입니다. 실제 키-값 쌍을 저장하는 해시 코드와 현재 키 수가 오버플로되는 경우 새 버킷에 대한 포인터가 있습니다. 이는 거의 지속적인 시간 액세스를 제공하는 정말 스마트한 데이터 구조입니다.

Golang에서 지도를 만드는 방법

golang에서 간단한 맵을 생성하려면 문자열과 정수의 맵을 사용하여 문자 빈도 카운터의 예를 들 수 있습니다. 지도는 문자를 키로, 빈도를 값으로 저장합니다.

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

따라서 지도를 map[string]int{}로 초기화하면 빈 지도를 얻게 됩니다. 그런 다음 키와 값을 채우는 데 사용할 수 있으며 문자열을 반복하고 각 문자(룬)에 대해 해당 문자 바이트를 문자열로 캐스팅하고 값을 증가시킵니다. int의 0 값은 0이므로 기본적으로 키가 없으면 0이 됩니다. 하지만 이는 양날의 검입니다. 하지만 키가 맵에 값 0으로 존재하는지, 키가 없지만 기본값이 0인지 알 수 없습니다. 그러기 위해서는 지도에 키가 존재하는지 확인해야 합니다.

자세한 내용은 제 Golang 지도 포스팅에서 자세히 확인하실 수 있습니다.

선언과 초기화의 차이점

프로그래밍 언어에서는 변수를 선언하고 초기화하는 데 차이가 있으며 기본 유형 구현을 통해 더 많은 작업을 수행해야 합니다. int, string, float 등과 같은 기본 데이터 유형의 경우 기본값/0 값이 있으므로 이는 변수 선언 및 초기화와 동일합니다. 그러나 맵과 슬라이스의 경우 선언은 프로그램 범위에서 변수를 사용할 수 있는지 확인하는 것일 뿐이지만 초기화를 위해서는 변수를 기본값/0 값 또는 할당해야 하는 실제 값으로 설정합니다.

따라서 선언은 단순히 프로그램 범위 내에서 변수를 사용할 수 있게 만드는 것입니다. 맵과 슬라이스의 경우 초기화 없이 변수를 선언하면 nil로 설정됩니다. 즉, 할당된 메모리가 없음을 가리키며 직접 사용할 수 없음을 의미합니다.

반면 초기화는 메모리를 할당하고 변수를 사용 가능한 상태로 설정합니다. 맵과 슬라이스의 경우 myMap = make(map[keyType]valueType) 또는 Slice = []type{}과 같은 구문을 사용하여 명시적으로 초기화해야 합니다. 이 초기화 없이 맵이나 슬라이스를 사용하려고 하면 nil 맵이나 슬라이스에 액세스하거나 수정할 때 패닉과 같은 런타임 오류가 발생합니다.

맵이 선언/초기화/비초기화되었을 때의 값을 살펴보겠습니다.

지도에서 설정을 읽는 구성 관리자를 구축한다고 상상해 보세요. 지도는 전역적으로 선언되지만 구성이 로드될 때만 초기화됩니다.

  1. 선언되었으나 초기화되지 않음

아래 코드는 초기화되지 않은 지도 액세스를 보여줍니다.

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. 선언과 초기화를 동시에

아래 코드는 동시에 초기화되는 지도 접근을 보여줍니다.

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. 선언했다가 나중에 초기화

아래 코드는 나중에 초기화되는 지도 액세스를 보여줍니다.

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

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)
}

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

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)
}
$ go run urlvals.go
map[foo:[bar]]
foo=bar

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 :)

위 내용은 Golang에서 지도를 안전하게 사용하기: 선언과 초기화의 차이점의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.