struct キーワードは、名前付きのフィールドを 1 つの型にまとめます。Go で注文、ユーザー、HTTP リクエストのボディ、設定ファイルを表すときに書くのが、type Order struct { ... } という宣言です。struct にはコンストラクターも継承もありません。振る舞いはメソッドで追加し、コードの再利用は struct を別の struct に埋め込むことで行い、エンコードの方法は struct タグで制御します(Go spec)。
要約
type Order struct { ... }は struct 型を宣言します。各フィールドは名前と型を持ち、同じ型のフィールドは 1 行にまとめられます。- struct のゼロ値では、すべてのフィールドがそれぞれの型のゼロ値になります。そのため
var o Orderは、準備なしですぐに使えます。 - 複合リテラルは
Order{ID: 1001, Total: 4999}のようにフィールド名を付けて書きます。ほかのパッケージの型をフィールド名なしのリテラルで書くと、go vetが警告します。 encoding/jsonやほかのパッケージから見えるのは、エクスポートされた(大文字で始まる)フィールドだけです。小文字のフィールドは、エラーにならないまま読み飛ばされます。json:"email,omitempty"のような struct タグでエンコードを制御します。time.Timeなどの struct にはomitzero(Go 1.24)を使います。omitemptyでは struct が省略されることはないからです。- ポインタレシーバーのメソッドは struct を変更できます。値レシーバーのメソッドはコピーに対して動作します。
- 型を埋め込むと、そのフィールドとメソッドが外側の struct に昇格されます。Go は継承を使わず、この仕組みでコードを再利用します。
- struct を
==で比較できるのは、すべてのフィールドが比較可能な場合だけです。struct を代入するとコピーされますが、中にある slice や map は共有されたままです。
Go で struct を宣言するには?
type、名前、struct の順に書き、波括弧の中にフィールドを並べます。各フィールドは名前の後ろに型を書きます。
example.gogopackage main import ( "fmt" "time" ) type Order struct { ID int64 CustomerID int64 Status string Total int64 // cents CreatedAt time.Time } type Address struct { Street, City, Country string } func main() { var o Order fmt.Printf("%+v\n", o) fmt.Println(o.Status == "", o.CreatedAt.IsZero()) }
example.texttext{ID:0 CustomerID:0 Status: Total:0 CreatedAt:0001-01-01 00:00:00 +0000 UTC} true true
Address は短い書き方の例です。同じ型のフィールドは、カンマで区切って 1 行にまとめられます。それでも多くのコードベースでは 1 行に 1 フィールドずつ書きます。差分が小さく保たれ、タグやコメントを付ける余地も残るからです。
var o Order にコンストラクターは必要ありません。すべてのフィールドはゼロ値から始まります。数値なら 0、文字列なら ""、ポインタ、slice、map なら nil、CreatedAt ならゼロの time.Time です。ゼロ値については Go の var キーワードで詳しく解説しています。
ほかの型と同じく、struct も、その関数でしか使わないなら関数の中で宣言できます。Go の type キーワード:定義型、型エイリアス、メソッドでは、JSON のボディをデコードするためにローカルな loginRequest struct を使う例を紹介しています。
struct の値を作るには?
たいていは、var によるゼロ値、フィールド名を付けた複合リテラル、& を付けたリテラルへのポインタのどれかを使います。
example.gogokeyed := Order{ID: 1001, CustomerID: 42, Status: "paid", Total: 4999} partial := Order{ID: 1002, Status: "pending"} positional := Order{1003, 42, "paid", 1250} p := &Order{ID: 1004} p.Status = "shipped"
example.texttext{ID:1001 CustomerID:42 Status:paid Total:4999} {ID:1002 CustomerID:0 Status:pending Total:0} {ID:1003 CustomerID:42 Status:paid Total:1250} {ID:1004 CustomerID:0 Status:shipped Total:0}
フィールド名付きのリテラルでは、フィールドを好きな順番で、好きな数だけ書けます。書かなかったフィールドはゼロ値になります。位置指定のリテラルでは、すべてのフィールドを宣言順に書く必要があるので、壊れやすくなります。Order に Currency フィールドを追加すると、位置指定のリテラルはすべてコンパイルできなくなりますが、フィールド名付きのリテラルはそのまま動きます。
ほかのパッケージの struct 型については、go vet が composites アナライザーで位置指定のリテラルを報告します。
example.gogoaddr := net.TCPAddr{net.ParseIP("10.0.0.5"), 5432, ""}
example.texttextmain.go:9:10: net.TCPAddr struct literal uses unkeyed fields
p.Status は、(*p).Status と書かなくてもポインタに対して使えます。struct へのポインタからフィールドにアクセスすると、Go が自動的にデリファレンスするからです。
省略可能なポインタフィールドを 1 行で設定するには?
Go 1.26 からは、new に値を渡せます。PATCH リクエストのボディで「送られていない」と「ゼロとして送られた」を区別するには、ポインタフィールドを使うのが一般的です。Go 1.26 より前は、フィールドごとに一時変数かヘルパー関数が必要でした。
example.gogotype UpdateUser struct { Name *string `json:"name,omitempty"` Email *string `json:"email,omitempty"` Age *int `json:"age,omitempty"` } func main() { patch := UpdateUser{ Email: new("[email protected]"), Age: new(31), } out, _ := json.Marshal(patch) fmt.Println(string(out)) }
example.texttext{"email":"[email protected]","age":31}
new(31) は int を割り当てて 31 を格納し、そのポインタを返します(Go 1.26 リリースノート)。go.mod に go 1.25 と書かれていると、コンパイラはこのコードを拒否します。
example.texttext./main.go:16:10: new("[email protected]") requires go1.26 or later (-lang was set to go1.25; check go.mod)
小文字の struct フィールドが JSON に出てこないのはなぜか?
エクスポートされていないフィールドは、ほかのパッケージから見えないからです。encoding/json もほかのパッケージなので、エラーを出さずにそれらを読み飛ばします。
example.gogotype User struct { ID int64 Email string password string role string } func main() { u := User{ID: 7, Email: "[email protected]", password: "hunter2", role: "admin"} out, err := json.Marshal(u) fmt.Println(string(out), err) var in User err = json.Unmarshal([]byte(`{"ID":8,"Email":"[email protected]","role":"admin"}`), &in) fmt.Printf("%+v %v\n", in, err) }
example.texttext{"ID":7,"Email":"[email protected]"} <nil> {ID:8 Email:[email protected] password: role:} <nil>
どちらの呼び出しもエラーは nil で、入力の role は何の知らせもなく捨てられます。Go の API が {} を返したり、デコード後にフィールドが空のままだったりしたら、まずここを確認してください。直し方は、フィールド名を大文字で始め、タグで JSON 上の小文字の名前を保つことです。
同じルールは、リフレクションでフィールドを読むすべてのパッケージに当てはまります。encoding/xml、database/sql のスキャナー、GORM、YAML ライブラリ、テンプレートの実行などです。この性質は意図して使うこともできます。データベースのハンドルやキャッシュのように、決してエンコードしてはいけないデータは、エクスポートされないフィールドに置けば安全です。
Go の struct タグとは?
struct タグは、フィールドの型の後ろに書く文字列リテラルです。Go 自体はタグを無視し、各パッケージがリフレクションでタグを読んで、そのフィールドの扱い方を決めます。いちばん目にするのは JSON のタグでしょう。
example.gogotype User struct { ID int64 `json:"id"` Email string `json:"email"` Nickname string `json:"nickname,omitempty"` PasswordHash string `json:"-"` DeletedAt time.Time `json:"deleted_at,omitempty"` LastLoginAt time.Time `json:"last_login_at,omitzero"` } func main() { u := User{ID: 7, Email: "[email protected]", PasswordHash: "$2a$10$..."} enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") enc.Encode(u) }
example.texttext{ "id": 7, "email": "[email protected]", "deleted_at": "0001-01-01T00:00:00Z" }
出力をフィールドごとに見ていきます。
json:"id"は、出力でのフィールド名を変えます。omitemptyは、値がfalse、0、""、nil、空の slice や map のときにフィールドを省きます。Nicknameは空なので出力されません。json:"-"は、そのフィールドを決してエンコードしません。PasswordHashはどのレスポンスにも含まれません。omitemptyは struct には効きません。DeletedAtはゼロのtime.Timeですが、0001-01-01T00:00:00Zとして出力されてしまいます。クライアントはこれを実在する日付として解釈しがちです。- Go 1.24 で追加された
omitzeroは、フィールドがその型のゼロ値のときに省きます。time.TimeのようにIsZero() boolメソッドを持つ型では、そのメソッドを呼び出します。LastLoginAtは出力されません(Go 1.24 リリースノート)。
新しいコードでは、struct のフィールドと time.Time には omitzero を使います。文字列、数値、slice、map には、今でも omitempty で問題ありません。
1 つのフィールドに、複数のパッケージ向けのタグをスペース区切りで付けられます。`json:"email" db:"email" validate:"required,email"` のような形です。各パッケージは reflect.StructTag.Get で自分のキーだけを読みます。
example.gogof, _ := reflect.TypeFor[User]().FieldByName("Nickname") fmt.Println(f.Tag.Get("json"))
example.texttextnickname,omitempty
struct タグが無視されるのはなぜか?
たいていは、スペースが入っているか、引用符が抜けているかです。書式は厳密で、key:"value" のようにコロンの後ろにスペースを入れてはいけません。json: "email" は人間には問題なく見えますが、Tag.Get("json") は空文字列を返すので、フィールドは Email としてエンコードされます。コンパイラはどんな文字列もタグとして受け付けるので、何も指摘しません。指摘してくれるのは go vet の structtag アナライザーで、同じ JSON 名を持つ 2 つのフィールドも検出します。
example.gogotype User struct { Email string `json: "email"` Name string `json:"name" db:"name"` ID int64 `json:"id"` Ref int64 `json:"id"` }
example.texttextmain.go:4:2: struct field tag `json: "email"` not compatible with reflect.StructTag.Get: bad syntax for struct tag value main.go:7:2: struct field Ref repeats json tag "id" also at main.go:6
gopls を使うエディターでは、入力中に同じ警告が表示されます。
struct のメソッドはどう動くのか?
メソッドは、名前の前にレシーバーを書いた関数です。レシーバーは値(c Cart)かポインタ(c *Cart)のどちらかで、どちらを選ぶかによって、メソッドが struct を変更できるかどうかが決まります。
example.gogotype Cart struct { Items []string Total int64 } func (c Cart) AddValue(item string, price int64) { c.Items = append(c.Items, item) c.Total += price } func (c *Cart) Add(item string, price int64) { c.Items = append(c.Items, item) c.Total += price } func main() { var c Cart c.AddValue("keyboard", 4999) fmt.Println(len(c.Items), c.Total) c.Add("keyboard", 4999) fmt.Println(len(c.Items), c.Total) }
example.texttext0 0 1 4999
AddValue はコンパイルでき、実行もできますが、カートは空のままです。値レシーバーは struct のコピーなので、メソッドはコピーを更新し、そのまま捨ててしまいます。エラーも警告も出ないため、Go を書き始めたばかりのコードではこのバグがよく起こります。Add はポインタを受け取るので、呼び出し側のカートを変更します。
(&c).Add(...) と書く必要はありません。c がアドレス可能であれば、Go が代わりにアドレスを取ります。逆も同じで、ポインタから値レシーバーのメソッドを呼び出せます。
メソッドが struct を変更する場合、struct が大きい場合、sync.Mutex のようなコピーしてはいけないフィールドを含む場合は、ポインタレシーバーを使います。Money や Point のような小さくて不変な値には、値レシーバーを使います。ある型のメソッドの 1 つでもポインタレシーバーが必要なら、すべてのメソッドをポインタレシーバーにします。2 種類を混ぜると、メソッドセットが把握しにくくなります。すべてのメソッドを持つのは *Cart だけになり、それらすべてを必要とする interface を満たせるのも *Cart だけになるからです(Go spec)。
Go の struct の埋め込みはどう動くのか?
名前を書かずに型だけを書いたフィールドは、埋め込みフィールドです。そのフィールドとメソッドは昇格され、外側の struct に宣言されているかのように使えます。
example.gogotype Timestamps struct { CreatedAt time.Time UpdatedAt time.Time } func (t *Timestamps) Touch(now time.Time) { if t.CreatedAt.IsZero() { t.CreatedAt = now } t.UpdatedAt = now } type Order struct { ID int64 Timestamps } type User struct { ID int64 Email string Timestamps } func main() { now := time.Date(2026, 9, 26, 9, 0, 0, 0, time.UTC) var o Order o.Touch(now) fmt.Println(o.CreatedAt.Format(time.DateOnly), o.Timestamps.UpdatedAt.Format(time.Kitchen)) u := User{ID: 7, Email: "[email protected]", Timestamps: Timestamps{CreatedAt: now}} fmt.Println(u.CreatedAt.Year()) }
example.texttext2026-09-26 9:00AM 2026
o.Touch(now) は o.Timestamps.Touch(now) の省略形です。埋め込みフィールドは型名のフィールドとしてそのまま存在し、リテラルで値を設定するときも Timestamps: Timestamps{...} のように型名を使います。encoding/json は埋め込まれた struct も平坦化するので、上の Order は {"ID":0,"CreatedAt":"2026-09-26T09:00:00Z","UpdatedAt":"2026-09-26T09:00:00Z"} とエンコードされます。
埋め込みは継承ではありません。Order は Timestamps ではないので、Timestamps を受け取る関数に Order は渡せません。Touch が実行されるとき、そのレシーバーは内側の Timestamps です。外側の Order にアクセスする手段はなく、Order が定義するメソッドを呼ぶこともできません。Java や Python のサブクラスならそれができ、「どのオーバーライドが実行されるのか」という疑問はそこから生まれます。Go ではこの疑問は出てきません。
外側の struct が同じ名前のフィールドやメソッドを宣言している場合は、外側が優先されます。埋め込まれた側には、型名を通じて引き続きアクセスできます。
example.gogotype Base struct{ ID int64 } func (Base) Describe() string { return "base" } type Order struct { Base ID string } func (Order) Describe() string { return "order" } func main() { o := Order{Base: Base{ID: 42}, ID: "ord_42"} fmt.Println(o.ID, o.Base.ID) fmt.Println(o.Describe(), o.Base.Describe()) }
example.texttextord_42 42 order base
struct に interface を埋め込めるか?
できます。1 つのメソッドだけを差し替えて型をラップするときによく使う方法です。ステータスコードを記録する HTTP ミドルウェアは http.ResponseWriter を埋め込み、Header と Write をそのまま使いつつ、WriteHeader だけを置き換えます。
example.gogotype statusRecorder struct { http.ResponseWriter status int } func (r *statusRecorder) WriteHeader(code int) { r.status = code r.ResponseWriter.WriteHeader(code) } func logStatus(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK} next.ServeHTTP(rec, req) slog.Info("request", "path", req.URL.Path, "status", rec.status) }) }
example.texttext2026/09/26 09:04:44 INFO request path=/orders/99 status=404
*statusRecorder は 3 つのメソッドをすべて持つので、http.ResponseWriter を満たし、次のハンドラーに渡せます。埋め込んだ interface が nil のままだと、そのメソッドを呼んだ時点で panic になります。リテラルで必ず設定してください。
struct に sync.Mutex を埋め込むべきか?
エクスポートされる型では避けてください。埋め込むと Lock と Unlock が型の公開 API に昇格されるので、呼び出し側の誰もが外からキャッシュをロックできてしまいます。
example.gogotype Cache struct { sync.Mutex items map[string]string }
名前を付けたエクスポートされないフィールドにすれば、ロックは実装の詳細にとどまります。
example.gogotype SafeCache struct { mu sync.Mutex items map[string]string } func (c *SafeCache) Get(key string) (string, bool) { c.mu.Lock() defer c.mu.Unlock() v, ok := c.items[key] return v, ok }
sync.Mutex のゼロ値はロックされていない mutex なので、SafeCache のロックには準備が要りません。ただし、map は最初の書き込みの前に作成しておく必要があります。
Go で struct を比較、コピーできるか?
すべてのフィールドが比較可能であれば、2 つの struct を == で比較できます。比較はフィールドごとに行われます。そのため小さな struct は、値型としても map のキーとしても使いやすくなります。
example.gogotype Money struct { Amount int64 Currency string } type RouteKey struct { Method string Path string } func main() { fmt.Println(Money{4999, "EUR"} == Money{4999, "EUR"}) hits := map[RouteKey]int{} hits[RouteKey{"GET", "/orders"}]++ hits[RouteKey{"GET", "/orders"}]++ hits[RouteKey{"POST", "/orders"}]++ fmt.Println(hits[RouteKey{"GET", "/orders"}], len(hits)) }
example.texttexttrue 2 2
RouteKey のような複合キーは、"GET /orders" のように文字列をつなげるよりもすっきりしていて、パスにスペースが含まれていても衝突しません。ここで位置指定のリテラルを使っても問題ないのは、RouteKey が同じパッケージで宣言されていて、フィールドが 2 つだけの見てわかる構造だからです。
slice、map、関数は比較できないので、それらを含む struct も比較できません。コンパイラがそう教えてくれます。
example.gogotype Order struct { ID int64 Items []string } func main() { a := Order{ID: 1} b := Order{ID: 1} fmt.Println(a == b) }
example.texttext./main.go:13:14: invalid operation: a == b (struct containing []string cannot be compared)
このような struct は、フィールドごとに比較します。slice には slices.Equal を使い、テストでは reflect.DeepEqual も使えます。
struct を代入するとコピーされるのか?
されます。ただし、浅いコピーです。すべてのフィールドがコピーされますが、slice や map のフィールドは、同じデータを指したままのヘッダーとしてコピーされます。
example.gogotype Order struct { ID int64 Items []string Meta map[string]string } func main() { original := Order{ID: 1, Items: []string{"keyboard"}, Meta: map[string]string{"source": "web"}} copied := original copied.ID = 2 copied.Items[0] = "mouse" copied.Meta["source"] = "api" fmt.Println(original.ID, original.Items, original.Meta) }
example.texttext1 [mouse] map[source:api]
copied.ID を変更しても、もとの値はそのままです。一方、copied.Items の要素や copied.Meta のキーを変更すると、もとの値も変わります。独立したコピーが必要なら、slices.Clone と maps.Clone でそれらのフィールドを自分で複製します。
struct を値として関数に渡すときも、同じコピーが起こります。sync.Mutex を含む struct では、このコピーはバグになります。コピーが自分専用のロックを持ってしまうからです。go vet は copylocks アナライザーでこれを報告します。
example.gogotype Counter struct { mu sync.Mutex hits int } func report(c Counter) { fmt.Println(c.hits) }
example.texttextmain.go:13:15: report passes lock by value: shop.Counter contains sync.Mutex main.go:19:9: call of report copies lock value: shop.Counter contains sync.Mutex
直し方は func report(c *Counter) とすることです。
Go の無名 struct とは?
名前を付けずにその場で書く struct 型のことです。一度しか必要のない形に使います。よく見かけるのは、テーブル駆動テストと、JSON レスポンスの一部だけをデコードする場面です。
example.gogovar resp struct { Data struct { Status string `json:"status"` } `json:"data"` } body := `{"data":{"status":"shipped","carrier":"DHL"},"meta":{"request_id":"abc"}}` if err := json.Unmarshal([]byte(body), &resp); err != nil { panic(err) } fmt.Println(resp.Data.Status) tests := []struct { name string input string want string }{ {"trims spaces", " [email protected] ", "[email protected]"}, {"already clean", "[email protected]", "[email protected]"}, } for _, tt := range tests { got := strings.ToLower(strings.TrimSpace(tt.input)) fmt.Println(tt.name, got == tt.want) }
example.texttextshipped trims spaces true already clean true
デコーダーは struct に宣言されたフィールドだけを埋めるので、carrier と meta は無視されます。フィールドを 1 つ読むだけのレスポンスに、名前付きの型は必要ありません。同じ形が 2 か所に登場したら、名前を付けます。
struct{} は何に使うのか?
struct{} はフィールドを 1 つも持たない struct です。サイズが 0 バイトなので、何かが存在するかどうかだけが重要な場面で、値の型として選ばれます。よくあるのは、map で作る集合と、合図を送るためだけの channel の 2 つです。
example.gogoseen := map[string]struct{}{} for _, id := range []string{"evt_1", "evt_2", "evt_1"} { if _, dup := seen[id]; dup { fmt.Println("skipping duplicate", id) continue } seen[id] = struct{}{} } fmt.Println(len(seen), unsafe.Sizeof(struct{}{})) done := make(chan struct{}) go func() { defer close(done) }() <-done fmt.Println("worker finished")
example.texttextskipping duplicate evt_1 2 0 worker finished
map[string]struct{} は、map[string]bool よりも「文字列の集合」であることがはっきり伝わります。map[string]bool では、false が何を意味するのかを読み手が考えなければなりません。chan struct{} は、channel でデータが送られることはなく、伝わるのは close されたという事実だけであることを示します。ctx.Done() が <-chan struct{} を返すのも同じ理由です。
フィールドの順序で struct のサイズは変わるのか?
変わります。各フィールドはそのサイズに合わせてアラインされるので、小さなフィールドの後ろに大きなフィールドが続くと、コンパイラはその間にパディングを挿入します。64 ビットのマシンでは、同じ 5 つのフィールドでも、順序が悪いと 2 倍のメモリを使います。
example.gogotype EventLoose struct { Active bool ID int64 Retried bool Attempts int32 Urgent bool } type EventPacked struct { ID int64 Attempts int32 Active bool Retried bool Urgent bool } func main() { fmt.Println(unsafe.Sizeof(EventLoose{}), unsafe.Sizeof(EventPacked{})) }
example.texttext32 16
EventLoose では、ID が 8 バイト境界から始まるように Active の後ろに 7 バイトのパディングが入り、Retried と Urgent の後ろにもさらにパディングが入ります。EventPacked はフィールドを大きい順に並べているので、無駄になるのは末尾の 1 バイトだけです。
ほとんどの struct ではこの違いは問題にならず、意味ごとにフィールドをまとめたほうが読みやすくなります。問題になり始めるのは、何百万もの値を slice やキャッシュに保持するときです。golang.org/x/tools の fieldalignment アナライザーは、もっと小さくできる struct を報告してくれます。
Go の struct とクラスの違いは?
Go の struct はデータを持ち、メソッドも持てるので、Java、C#、Python のクラスが担う役割のほとんどをカバーします。それ以外の点は、意図的に違う設計になっています。
| Go の struct | クラス(Java、C#、Python) | |
|---|---|---|
| コンストラクター | なし。ゼロ値か、慣例として NewX 関数を使う | 専用のコンストラクターメソッド |
| 再利用 | 埋め込み(コンポジション) | 継承とコンポジション |
| ポリモーフィズム | interface(暗黙的に満たす) | 基底クラスと宣言された interface |
| 可視性 | パッケージ単位、大文字かどうかで決まる | メンバーごとの public、private、protected |
| メソッド | 型の外側、パッケージ内のどこにでも宣言する | クラス本体の中で宣言する |
| コピーの意味論 | デフォルトは値、ポインタには & を使う | デフォルトは参照 |
struct に検証やデフォルト値が必要なときは、それを返す普通の関数を書きます。慣例として名前は NewX にし、あわせてエラーを返すのも一般的です。
example.gogotype Client struct { baseURL string http *http.Client retries int } func NewClient(baseURL string) (*Client, error) { if baseURL == "" { return nil, errors.New("baseURL is required") } return &Client{ baseURL: baseURL, http: &http.Client{Timeout: 10 * time.Second}, retries: 3, }, nil }
フィールドがエクスポートされていないので、パッケージの外の呼び出し側は、デフォルト値が適用された Client を NewClient 経由でしか取得できません。Go には super もオーバーライドもないので、メソッドの動きを知るためにクラス階層をたどる必要はありません。struct の振る舞いは、自身のメソッドと埋め込んだ型のメソッドだけで、すべて 1 つのパッケージの中で読めます。Go のコードベースが大きくなったとき、これが大いに役立ちます。
LevelUpGo で学ぶ
LevelUpGo では、ブラウザ上で本物の Go コードを実行する演習を通じて Go を学べます。Composite Types では、最初の struct、コンストラクター関数、メソッド、埋め込みを段階的に作り、それらを小さなプロジェクトで使います。Pointers & Memory では、struct へのポインタ、new、値レシーバーとポインタレシーバーの違いを扱い、最後に struct をその場で更新するフィーチャーフラグのストアを作ります。Interfaces & Polymorphism では、struct が宣言なしで interface を満たす仕組みを学びます。Training Ground には、コースの外で練習できる短い単独の演習があります。残り 24 個の予約語については、Go のキーワード一覧:全 25 個の予約語を解説をご覧ください。
よくある質問
Go で struct はキーワードですか?
はい。struct は Go の 25 個の予約語の 1 つなので、変数名や関数名には使えません。struct 型を始めるキーワードで、type Order struct { ... } のように、ほとんどの場合 type で名前を付けて使います。無名 struct や空の struct struct{} にも登場します。
Go にクラスはありますか?
ありません。代わりに、メソッドを持つ struct、interface、埋め込みがあります。struct がデータを持ち、メソッドが振る舞いを加え、interface がポリモーフィズムを提供し、埋め込みがコンポジションによってコードを再利用します。継承もコンストラクター用のキーワードもありません。その役割は、慣例として NewX 関数が担います。
値レシーバーとポインタレシーバーのどちらを使うべきですか?
メソッドが struct を変更する場合、struct が大きい場合、sync.Mutex を含む場合は、ポインタレシーバーを使います。Money や Point のように、変更されることのない小さな値には値レシーバーを使います。ある型のメソッドの 1 つでもポインタレシーバーが必要なら、型の振る舞いをそろえるために、すべてをポインタレシーバーにします。
json.Marshal が struct に対して空のオブジェクトを返すのはなぜですか?
struct のフィールドがエクスポートされていないからです。encoding/json から見えるのは大文字で始まるフィールドだけで、それ以外はエラーを出さずに読み飛ばされます。email を Email に変え、`json:"email"` を付ければ、JSON 上では小文字の名前のままにできます。
omitempty と omitzero の違いは何ですか?
omitempty は false、0、""、nil、空の slice や map を省きますが、struct は決して省かないので、ゼロの time.Time は 0001-01-01T00:00:00Z として出力されます。Go 1.24 で追加された omitzero は、その型のゼロ値を持つフィールドをすべて省き、型に IsZero メソッドがあればそれを使って判定します。
出典
- The Go Programming Language Specification, Struct types: https://go.dev/ref/spec#Struct_types
- The Go Programming Language Specification, Composite literals: https://go.dev/ref/spec#Composite_literals
- The Go Programming Language Specification, Method sets: https://go.dev/ref/spec#Method_sets
- The Go Programming Language Specification, Comparison operators: https://go.dev/ref/spec#Comparison_operators
- encoding/json, Marshal: https://pkg.go.dev/encoding/json#Marshal
- reflect, StructTag: https://pkg.go.dev/reflect#StructTag
- Go 1.24 Release Notes, omitzero: https://go.dev/doc/go1.24
- Go 1.26 Release Notes, new with an expression: https://go.dev/doc/go1.26
- go vet analyzers (composites, structtag, copylocks): https://pkg.go.dev/cmd/vet
