이번 주에 저는 golang용 API 래퍼 패키지 중 하나를 작업하고 있었는데, 그 패키지에서는 URL 인코딩 값이 포함된 게시물 요청 보내기, 쿠키 설정 및 모든 재미있는 작업을 다루었습니다. 하지만 본문을 구성하는 동안 url.Value 유형을 사용하여 본문을 구성하고 이를 사용하여 키-값 쌍을 추가하고 설정했습니다. 그러나 일부 부분에서 유선 nil 포인터 참조 오류가 발생했습니다. 이는 제가 수동으로 설정한 일부 변수 때문이라고 생각했습니다. 하지만 자세히 디버깅하면서 유형을 선언하기만 하고 초기화하여 nil 참조 오류를 일으키는 일반적인 함정이나 나쁜 습관을 발견했습니다.
이번 게시물에서는 맵이 무엇인지, 맵을 만드는 방법, 특히 맵을 올바르게 선언하고 초기화하는 방법을 다루겠습니다. 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 맵이나 슬라이스에 액세스하거나 수정할 때 패닉과 같은 런타임 오류가 발생합니다.
맵이 선언/초기화/비초기화되었을 때의 값을 살펴보겠습니다.
지도에서 설정을 읽는 구성 관리자를 구축한다고 상상해 보세요. 지도는 전역적으로 선언되지만 구성이 로드될 때만 초기화됩니다.
아래 코드는 초기화되지 않은 지도 액세스를 보여줍니다.
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
아래 코드는 동시에 초기화되는 지도 접근을 보여줍니다.
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
아래 코드는 나중에 초기화되는 지도 액세스를 보여줍니다.
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.
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.
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!