Menu

Go言語の構造体(struct):定義、初期化、比較、タグ

構造体は名前付きのフィールドを1つの型にまとめます。定義と初期化の方法、ゼロ値、構造体へのポインタ、無名構造体、比較、構造体タグ、そしてNewXというコンストラクタの慣習を解説します。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

構造体を定義して作る

構造体型は名前付きのフィールドのリストです。type Name struct { ... } で宣言し、複合リテラルで値を作ります。

出力:

Ana 31
{Name:Ana Email:ana@example.com Age:32 Admin:false}
{Name: Email: Age:0 Admin:false}
main.User{Name:"Bob", Email:"", Age:0, Admin:false}

%+v はフィールド名を、%#v はGoの構文を表示します。どちらもデバッグ中に構造体を調べる最も手っ取り早い方法です。

同じ型のフィールドは1行にまとめられます:X, Y float64。大文字で始まるフィールド名は公開され(他のパッケージや encoding/json から見えます)、小文字のものは公開されません。

リテラルの書き方

User{Name: "Ana", Age: 31}        // named fields: order-free, omitted fields are zero
User{"Ana", "a@x.com", 31, false} // positional: every field, in order
&User{Name: "Ana"}                // pointer to a new struct
new(User)                         // pointer to a zeroed struct

フィールド名を指定する形を使いましょう。位置指定のリテラルは、誰かがフィールドを追加したり並べ替えたりすると壊れます。他のパッケージの構造体型に対しては go vet が警告します(たとえば net/url.Error struct literal uses unkeyed fields)。Point{1, 2} のような小さなローカルの型なら位置指定でも問題ありません。

デフォルト値はない:ゼロ値とコンストラクタ

Goにはフィールドのデフォルト値も、言語としてのコンストラクタもありません。すべてのフィールドは型のゼロ値(0""falsenil)から始まります。この穴を埋めるイディオムが2つあります。

ゼロ値を役に立つものにする。 sync.Mutexbytes.Bufferstrings.Builder はどれも初期化なしで動きます。型をそう設計できるなら、そうしましょう。

NewX 関数を書く。 フィールドにゼロでないデフォルトが必要なときや、マップのように何かを確保する必要があるときに使います。

NewServer がポインタを返すのは、サーバーが自身を変更するメソッドを持ち、共有されるからです。小さな不変の型では構造体を値で返すのも一般的です(time.Datetime.Time を返します)。

構造体へのポインタ

Goはフィールドへのアクセスのときに構造体へのポインタを自動で参照外しします。p := &User{} なら、(*p).Name ではなく p.Name と書きます。

出力:

{11 12}
{11 12} {0 12}
{11 0}

構造体は値です。代入や関数の引数ではコピーされ、ポインタは1つの構造体を共有します。フィールドごとのコピーは浅いコピーで、コピーのスライスやマップのフィールドは同じデータを指したままです。どちらを使うべきかはポインタのページを参照してください。

無名構造体

構造体型は名前を付けずにその場で書けます。一度きりのまとまり、テーブル駆動テスト、一度だけ読むJSONのデコードに便利です。

[]struct{ in string; want int } のテーブルは、Goのテストの標準的な形です。

構造体を比較する

すべてのフィールドが比較可能なら、構造体は == で比較できます。等しいとは、すべてのフィールドが等しいことです。比較可能な構造体はマップのキーにもなれます。

スライスやマップのフィールドを持つ構造体には、Equal メソッドを書くか、テストでは reflect.DeepEqual で比較します。

構造体タグ

タグはフィールドの型の後に置く文字列リテラルです。言語はこれを無視し、ライブラリがリフレクションで読み取ります。最もよく使うのは encoding/json です。

これは {"id":7,"name":"Mug"} と表示します。タグがフィールド名を変え、omitempty がゼロの Price を落とし、非公開のフィールドはエンコーダーから見えません。タグは key:"value" の慣習に従い、複数のキーは空白で区切ります:`json:"name" db:"user_name"`。形式の間違ったタグも問題なくコンパイルされて黙って無視されますが、go vet が検出します。オプションはjsonのページで扱っています。

空の構造体とフィールドの順序

struct{} はフィールドを持たず、0バイトしか使いません。集合の値(map[string]struct{})や、出来事だけが重要なチャネルでの合図(chan struct{})に使われます。

フィールドの順序はメモリレイアウトに影響します。コンパイラはフィールドをアラインメントに合わせてパディングします。bool, int64, bool の構造体は64ビットシステムで24バイトを使いますが、int64, bool, bool なら16バイトです。これが問題になるのは、大量に保存される型だけです。

よくある間違い

  • コピーを変更する。 for _, u := range users { u.Age++ } は毎回コピーを変更しています。インデックスを使います:users[i].Age++
  • マップのフィールドの初期化を忘れる。 構造体のゼロ値はnilマップを持っており、書き込むとpanicします。コンストラクタで確保します。
  • sync.Mutex を持つ構造体をコピーする。 コピーは独自のロック状態を持ちます。そうした構造体はポインタで渡します。go vet がコピーを報告します。
  • 他のパッケージの型に位置指定のリテラルを使う。 フィールドが追加されると壊れます。

よくある質問

Goで構造体はどう初期化しますか?

複合リテラルを使います。普通はフィールド名を指定する形で、u := User{Name: "Ana", Age: 31} のように書き、省略したフィールドはゼロ値になります。User{} はすべてゼロ値、&User{...} はポインタ、new(User) はゼロ値の構造体へのポインタを作ります。

Goは構造体のフィールドのデフォルト値をサポートしていますか?

していません。すべてのフィールドは型のゼロ値から始まります。イディオムは、func NewServer() *Server { return &Server{Port: 8080} } のようなコンストラクタ関数か、sync.Mutexbytes.Buffer のように、ゼロ値のままで役に立つよう型を設計することです。

Goで2つの構造体を比較するには?

すべてのフィールドが比較可能なら == で比較できます。2つの構造体の値は、対応するすべてのフィールドが等しいときに等しくなります。スライス、マップ、関数のフィールドを含む構造体は == で比較できません(コンパイルエラー)。フィールドを自分で比較するか、テストでは reflect.DeepEqual を使います。

Goの構造体タグとは何ですか?

`json:"name,omitempty"` のように、フィールドの型の後に置く文字列リテラルです。コンパイラはこれを無視し、encoding/json、データベースドライバ、バリデータなどのパッケージがリフレクションで読み取り、フィールドのエンコード、名前、チェックの方法を制御します。

Goで構造体をフィールド名付きで表示するには?

fmt.Printf("%+v\n", s) を使うと {Name:Ana Age:31} と表示されます。%v は値だけを表示し、%#v は型名を含むGoの構文 main.User{Name:"Ana", Age:31} を表示します。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める