ホームページ バックエンド開発 Golang Golang でマップを安全に使用する: 宣言と初期化の違い

Golang でマップを安全に使用する: 宣言と初期化の違い

Aug 31, 2024 pm 10:31 PM

Safely using Maps in Golang: Differences in declaration and initialization

導入

今週、私は golang の API ラッパー パッケージの 1 つに取り組んでいました。これは、URL エンコードされた値を含む投稿リクエストの送信、Cookie の設定、その他すべての楽しいことを処理していました。ただし、本文を構築している間、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 であるかはわかりません。そのためには、キーがマップ内に存在するかどうかを確認する必要があります。

さらに詳しくは、私の Golang マップの投稿をご覧ください。

宣言と初期化の違い

プログラミング言語での変数の宣言と初期化には違いがあり、基礎となる型の実装でさらに多くのことを行う必要があります。 int、string、float などのプライマリ データ型の場合は、デフォルト/ゼロ値があるため、変数の宣言と初期化と同じになります。ただし、マップとスライスの場合、宣言は変数がプログラムのスコープで使用できることを確認するだけですが、初期化では変数をデフォルト/ゼロ値、または割り当てる必要がある実際の値に設定します。

つまり、宣言は単にプログラムのスコープ内で変数を使用できるようにするだけです。マップとスライスの場合、初期化せずに変数を宣言すると、その変数が nil に設定されます。これは、変数が割り当てられたメモリを指さず、直接使用できないことを意味します。

一方、初期化ではメモリが割り当てられ、変数が使用可能な状態に設定されます。マップとスライスの場合は、myMap = make(map[keyType]valueType) またはスライス = []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 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

Debian OpenSSLの脆弱性は何ですか Debian OpenSSLの脆弱性は何ですか Apr 02, 2025 am 07:30 AM

OpenSSLは、安全な通信で広く使用されているオープンソースライブラリとして、暗号化アルゴリズム、キー、証明書管理機能を提供します。ただし、その歴史的バージョンにはいくつかの既知のセキュリティの脆弱性があり、その一部は非常に有害です。この記事では、Debian SystemsのOpenSSLの共通の脆弱性と対応測定に焦点を当てます。 Debianopensslの既知の脆弱性:OpenSSLは、次のようないくつかの深刻な脆弱性を経験しています。攻撃者は、この脆弱性を、暗号化キーなどを含む、サーバー上の不正な読み取りの敏感な情報に使用できます。

フロントエンドからバックエンドの開発に変身すると、JavaやGolangを学ぶことはより有望ですか? フロントエンドからバックエンドの開発に変身すると、JavaやGolangを学ぶことはより有望ですか? Apr 02, 2025 am 09:12 AM

バックエンド学習パス:フロントエンドからバックエンドへの探査の旅は、フロントエンド開発から変わるバックエンド初心者として、すでにNodeJSの基盤を持っています...

Beego ormのモデルに関連付けられているデータベースを指定する方法は? Beego ormのモデルに関連付けられているデータベースを指定する方法は? Apr 02, 2025 pm 03:54 PM

Beegoormフレームワークでは、モデルに関連付けられているデータベースを指定する方法は?多くのBEEGOプロジェクトでは、複数のデータベースを同時に操作する必要があります。 Beegoを使用する場合...

Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか? Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか? Apr 02, 2025 pm 05:09 PM

Golandのカスタム構造ラベルが表示されない場合はどうすればよいですか?ゴーランドを使用するためにGolandを使用する場合、多くの開発者はカスタム構造タグに遭遇します...

GOの浮動小数点番号操作に使用されるライブラリは何ですか? GOの浮動小数点番号操作に使用されるライブラリは何ですか? Apr 02, 2025 pm 02:06 PM

GO言語の浮動小数点数操作に使用されるライブラリは、精度を確保する方法を紹介します...

Go's Crawler Collyのキュースレッドの問題は何ですか? Go's Crawler Collyのキュースレッドの問題は何ですか? Apr 02, 2025 pm 02:09 PM

Go Crawler Collyのキュースレッドの問題は、Go言語でColly Crawler Libraryを使用する問題を調査します。 �...

DebianでMongoDB自動拡張を構成する方法 DebianでMongoDB自動拡張を構成する方法 Apr 02, 2025 am 07:36 AM

この記事では、自動拡張を実現するためにDebianシステムでMongodbを構成する方法を紹介します。主な手順には、Mongodbレプリカセットとディスクスペース監視のセットアップが含まれます。 1。MongoDBのインストール最初に、MongoDBがDebianシステムにインストールされていることを確認してください。次のコマンドを使用してインストールします。sudoaptupdatesudoaptinstinstall-yymongodb-org2。mongodbレプリカセットMongodbレプリカセットの構成により、自動容量拡張を達成するための基礎となる高可用性とデータ冗長性が保証されます。 Mongodbサービスを開始:Sudosystemctlstartmongodsudosys

Redisストリームを使用してGO言語でメッセージキューを実装する場合、user_idタイプの変換の問題を解決する方法は? Redisストリームを使用してGO言語でメッセージキューを実装する場合、user_idタイプの変換の問題を解決する方法は? Apr 02, 2025 pm 04:54 PM

redisstreamを使用してGo言語でメッセージキューを実装する問題は、GO言語とRedisを使用することです...

See all articles