Go言語(Golang)でプログラムを書いていると、複数のデータをひとまとめにしたい場面がよくあります。
そんなときに便利なのが「構造体(struct)」です。
この記事では、Go言語の構造体について、基本の使い方から、埋め込み(Go独自の機能)やメソッドとの連携まで、ステップバイステップで説明します。
構造体って何?基本の考え方

構造体(struct)とは
構造体の基本
構造体は、複数の違う型の値をまとめて扱うための型です。C言語の構造体と似ており、Goではオブジェクト指向の代わりによく使われます。
身近な例で考えてみよう
人の情報を管理することを考えてみましょう。
- 名前:文字列
- 年齢:数値
- メールアドレス:文字列
これらの情報をバラバラに管理するより、ひとまとめにできたら便利ですよね。
基本的な定義方法
続きの漢字かな混じり文を以下に提示します。
簡単な使い方
構造体の作成と使い方
package main
import "fmt"
type Person struct {
Name string
Age int
}
func main() {
// 構造体の作成
p := Person{Name: "太郎", Age: 25}
// フィールドにアクセス
fmt.Println(p.Name) // 太郎
fmt.Println(p.Age) // 25
}
他の言語との比較
JavaやPythonのクラスとの違い
// Java
class Person {
String name;
int age;
}
# Python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
Goの構造体の特徴
- シンプルな文法
- クラスのような複雑さがない
- でも必要な機能はそろっている
構造体は「名前付きの箱」のようなもので、複数の値を整理して保持できます。
次は、構造体の初期化方法を詳しく見ていきましょう。
構造体の初期化と値の取り出し

いろいろな初期化方法
方法1:フィールド指定で初期化(推奨)
p := Person{Name: "花子", Age: 30}
方法2:部分的な初期化
p := Person{Name: "次郎"} // Ageは0(デフォルト値)になる
方法3:空の構造体を作って後から設定
var p Person
p.Name = "三郎"
p.Age = 22
方法4:順番指定で初期化(非推奨)
p := Person{"四郎", 35} // フィールドの順番で指定
なぜ順番指定は非推奨?
- フィールドの順番が変わるとバグの原因になる
- コードが読みづらい
- 保守性が悪い
デフォルト値について
Goの型別デフォルト値
string
:空文字列""
int
:0bool
:false- ポインタ:nil
例
type Product struct {
Name string
Price int
Stock bool
}
var p Product
fmt.Printf("%+v\n", p) // {Name: Price:0 Stock:false}
フィールドへのアクセスと変更
値の取り出し
fmt.Println(p.Name) // 値を読み取り
fmt.Println(p.Age) // 値を読み取り
値の変更
p.Age = 26 // 値を変更
p.Name = "新しい名前" // 値を変更
構造体全体の表示
fmt.Printf("%+v\n", p) // フィールド名も表示
fmt.Println(p) // 値のみ表示
実用的な例
ユーザー情報の管理
type User struct {
ID int
Username string
Email string
Active bool
}
func main() {
// 新しいユーザーを作成
user := User{
ID: 1,
Username: "yamada",
Email: "yamada@example.com",
Active: true,
}
// ユーザー情報を表示
fmt.Printf("ユーザーID: %d\n", user.ID)
fmt.Printf("ユーザー名: %s\n", user.Username)
// アクティブ状態を変更
user.Active = false
fmt.Printf("アクティブ: %t\n", user.Active)
}
構造体とメソッドの連携

メソッドって何?
メソッドの基本
メソッドは、特定の型に関連づけられた関数です。その型の値に対して操作を行うことができます。
他の言語との比較
// Java
class Person {
String name;
void greet() {
System.out.println("こんにちは、" + name + "です");
}
}
// Go
type Person struct {
Name string
}
func (p Person) Greet() {
fmt.Printf("こんにちは、%sです\n", p.Name)
}
基本的なメソッドの定義
値レシーバーのメソッド
type Person struct {
Name string
Age int
}
func (p Person) Greet() {
fmt.Printf("こんにちは、%sです。%d歳です。\n", p.Name, p.Age)
}
func (p Person) IsAdult() bool {
return p.Age >= 20
}
使い方
p := Person{Name: "田中", Age: 25}
p.Greet() // こんにちは、田中です。25歳です。
fmt.Println(p.IsAdult()) // true
ポインタレシーバーで値を変更
値レシーバーの問題点
func (p Person) Birthday() {
p.Age++ // これは元の構造体を変更しない!
}
p := Person{Name: "佐藤", Age: 30}
p.Birthday()
fmt.Println(p.Age) // まだ30のまま
ポインタレシーバーでの解決
func (p *Person) Birthday() {
p.Age++ // ポインタなので元の構造体を変更する
}
p := Person{Name: "佐藤", Age: 30}
p.Birthday()
fmt.Println(p.Age) // 31に変更される
いつポインタレシーバーを使う?
ポインタレシーバーを使う場面
- 構造体の値を変更したいとき
- 構造体が大きくて、コピーのコストを避けたいとき
- 一貫性を保ちたいとき(すべてのメソッドで同じレシーバー型)
値レシーバーを使う場面
- 値を変更しないとき
- 構造体が小さいとき
- イミュータブル(不変)な設計にしたいとき
実用的なメソッドの例
銀行口座の例
type BankAccount struct {
owner string
balance int
}
// 残高を確認(値レシーバー)
func (a BankAccount) GetBalance() int {
return a.balance
}
// 入金(ポインタレシーバー)
func (a *BankAccount) Deposit(amount int) {
if amount > 0 {
a.balance += amount
}
}
// 出金(ポインタレシーバー)
func (a *BankAccount) Withdraw(amount int) bool {
if amount > 0 && a.balance >= amount {
a.balance -= amount
return true
}
return false
}
// 口座情報を表示(値レシーバー)
func (a BankAccount) String() string {
return fmt.Sprintf("口座名義: %s, 残高: %d円", a.owner, a.balance)
}
使い方
account := BankAccount{owner: "山田太郎", balance: 10000}
fmt.Println(account.String()) // 口座名義: 山田太郎, 残高: 10000円
account.Deposit(5000) // 5000円入金
fmt.Println(account.GetBalance()) // 15000
if account.Withdraw(3000) { // 3000円出金
fmt.Println("出金成功")
}
fmt.Println(account.String()) // 口座名義: 山田太郎, 残高: 12000円
埋め込み構造体と継承風の使い方

Goには継承がない!
他の言語の継承
// Java
class Animal {
String name;
}
class Dog extends Animal {
String breed;
}
Goのアプローチ
Goには継承がありませんが、「構造体の埋め込み」により、似たようなことが実現できます。これを**コンポジション(構成)**といいます。
基本的な埋め込み
基本構造体の定義
type Person struct {
Name string
Age int
}
type Address struct {
City string
Country string
}
埋め込み構造体の定義
type Employee struct {
Person // 埋め込み(フィールド名がない)
Address // 埋め込み
ID int
Salary int
}
埋め込みの使い方
初期化
e := Employee{
Person: Person{Name: "佐藤恵子", Age: 35},
Address: Address{City: "大阪", Country: "日本"},
ID: 1001,
Salary: 500000,
}
フィールドへのアクセス
// 直接アクセス(埋め込みの恩恵)
fmt.Println(e.Name) // 佐藤恵子
fmt.Println(e.City) // 大阪
// 明示的なアクセス
fmt.Println(e.Person.Name) // 佐藤恵子
fmt.Println(e.Address.City) // 大阪
メソッドの継承風動作
基本構造体にメソッドを定義
func (p Person) Greet() {
fmt.Printf("こんにちは、%sです\n", p.Name)
}
func (p Person) GetAge() int {
return p.Age
}
埋め込んだ構造体でもメソッドが使える
e := Employee{
Person: Person{Name: "田中", Age: 28},
ID: 2001,
}
e.Greet() // こんにちは、田中です
fmt.Println(e.GetAge()) // 28
メソッドのオーバーライド
独自のメソッドを定義
func (e Employee) Greet() {
fmt.Printf("こんにちは、社員番号%dの%sです\n", e.ID, e.Name)
}
func (e Employee) GetInfo() string {
return fmt.Sprintf("ID: %d, 名前: %s, 給与: %d円", e.ID, e.Name, e.Salary)
}
使い方
e := Employee{
Person: Person{Name: "山田", Age: 30},
ID: 3001,
Salary: 600000,
}
e.Greet() // こんにちは、社員番号3001の山田です
fmt.Println(e.GetAge()) // 30(Personのメソッド)
fmt.Println(e.GetInfo()) // ID: 3001, 名前: 山田, 給与: 600000円
複数の埋め込み
複数の構造体を埋め込む
type Contact struct {
Email string
Phone string
}
type Manager struct {
Employee // 社員情報
Contact // 連絡先情報
TeamSize int // チームサイズ
}
使い方
m := Manager{
Employee: Employee{
Person: Person{Name: "鈴木", Age: 40},
ID: 4001,
Salary: 800000,
},
Contact: Contact{
Email: "suzuki@company.com",
Phone: "090-1234-5678",
},
TeamSize: 5,
}
fmt.Println(m.Name) // 鈴木
fmt.Println(m.Email) // suzuki@company.com
fmt.Println(m.TeamSize) // 5
名前の衝突を避ける
問題のある例
type A struct {
Name string
}
type B struct {
Name string
}
type C struct {
A
B
}
// c.Name とアクセスできない(どちらのNameか不明)
解決方法
c := C{
A: A{Name: "AのName"},
B: B{Name: "BのName"},
}
fmt.Println(c.A.Name) // AのName
fmt.Println(c.B.Name) // BのName
構造体の埋め込みは、Goの設計哲学「構成による再利用」を体現するものです。
次は「構造体タグとJSONとの連携」について紹介します。
構造体タグとJSONとの連携

構造体タグって何?
タグの基本
構造体タグは、フィールドにメタデータを付加するためのしくみです。
主にシリアライゼーション(データの変換)で使われます。
基本的な書き方
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
JSONとの連携
JSON変換の例
package main
import (
"encoding/json"
"fmt"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email"`
}
func main() {
// 構造体からJSONへ
u := User{Name: "直樹", Age: 28, Email: "naoki@example.com"}
jsonData, err := json.Marshal(u)
if err != nil {
fmt.Println("エラー:", err)
return
}
fmt.Println(string(jsonData)) // {"name":"直樹","age":28,"email":"naoki@example.com"}
// JSONから構造体へ
jsonStr := `{"name":"美咲","age":25,"email":"misaki@example.com"}`
var user User
err = json.Unmarshal([]byte(jsonStr), &user)
if err != nil {
fmt.Println("エラー:", err)
return
}
fmt.Printf("%+v\n", user) // {Name:美咲 Age:25 Email:misaki@example.com}
}
いろいろなタグオプション
フィールドを無視
type User struct {
Name string `json:"name"`
Password string `json:"-"` // JSONに含めない
Age int `json:"age"`
}
空の値を省略
type User struct {
Name string `json:"name"`
Age int `json:"age,omitempty"` // 0の場合は省略
Email string `json:"email,omitempty"` // 空文字の場合は省略
}
フィールド名を変更
type User struct {
ID int `json:"user_id"`
FullName string `json:"full_name"`
Age int `json:"age"`
}
API開発での活用
レスポンス用の構造体
type APIResponse struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
}
type UserProfile struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Created string `json:"created_at"`
}
使い方
user := UserProfile{
ID: 1,
Username: "yamada",
Email: "yamada@example.com",
Created: "2024-01-01T00:00:00Z",
}
response := APIResponse{
Status: "success",
Data: user,
}
jsonData, _ := json.Marshal(response)
fmt.Println(string(jsonData))
// {"status":"success","data":{"id":1,"username":"yamada","email":"yamada@example.com","created_at":"2024-01-01T00:00:00Z"}}
以下は「他の形式との連携」セクションの漢字かな混じり文です。
他の形式との連携
YAMLとの連携
type Config struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
DB struct {
Name string `yaml:"name"`
User string `yaml:"user"`
} `yaml:"database"`
}
YAMLでは、構造体タグに yaml:"..."
を使うことで、設定ファイルの読み書きが可能になります。
たとえば、以下のようなYAMLファイルと対応できます:
host: localhost
port: 5432
database:
name: mydb
user: admin
XMLとの連携
type Book struct {
Title string `xml:"title"`
Author string `xml:"author"`
ISBN string `xml:"isbn,attr"` // 属性として出力
}
XMLでは xml:"フィールド名"
というタグを使い、attr
オプションで属性として出力することもできます。
データベースとの連携(GORM)
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex"`
Age int `gorm:"default:0"`
}
GORMなどのORMライブラリでは、タグを使ってフィールドの制約やデータベースの仕様を定義できます:
primaryKey
: 主キーsize
: カラムのサイズnot null
: NULLを許容しないuniqueIndex
: 一意制約default
: デフォルト値
実際のWebアプリケーションでの例
ユーザー登録APIの例
type RegisterRequest struct {
Username string `json:"username" validate:"required,min=3"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
}
type RegisterResponse struct {
UserID int `json:"user_id"`
Message string `json:"message"`
}
ハンドラー関数
func registerHandler(w http.ResponseWriter, r *http.Request) {
var req RegisterRequest
// JSONをパース
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// ユーザー作成処理(例)
userID := createUser(req.Username, req.Email, req.Password)
// レスポンスを返す
resp := RegisterResponse{
UserID: userID,
Message: "User created successfully",
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
このように、構造体タグはAPI通信・ファイル保存・DB連携など、実践的な開発で大きな役割を果たします。
より高度な使い方

インターフェースとの組み合わせ
インターフェースの定義
type Speaker interface {
Speak() string
}
type Walker interface {
Walk() string
}
type Animal interface {
Speaker
Walker
}
Goでは、インターフェースを使うことで、構造体に共通のふるまい(関数の集合)を持たせることができます。
上記の例では、Animal
インターフェースが Speaker
と Walker
を内包しており、複数のふるまいをまとめた抽象型として機能します。
構造体での実装
type Dog struct {
Name string
Breed string
}
func (d Dog) Speak() string {
return fmt.Sprintf("%sがワンワンと鳴いています", d.Name)
}
func (d Dog) Walk() string {
return fmt.Sprintf("%sが散歩しています", d.Name)
}
type Cat struct {
Name string
Color string
}
func (c Cat) Speak() string {
return fmt.Sprintf("%sがニャーニャーと鳴いています", c.Name)
}
func (c Cat) Walk() string {
return fmt.Sprintf("%sがそっと歩いています", c.Name)
}
使い方
func makeAnimalSpeak(a Animal) {
fmt.Println(a.Speak())
fmt.Println(a.Walk())
}
dog := Dog{Name: "ポチ", Breed: "柴犬"}
cat := Cat{Name: "たま", Color: "白"}
makeAnimalSpeak(dog)
// → ポチがワンワンと鳴いています
// → ポチが散歩しています
makeAnimalSpeak(cat)
// → たまがニャーニャーと鳴いています
// → たまがそっと歩いています
このように、インターフェースを使うと型に依存しない汎用的な関数を定義できます。
ファクトリーパターン
コンストラクタ関数
type User struct {
id int
username string
email string
created time.Time
}
func NewUser(username, email string) *User {
return &User{
id: generateID(),
username: username,
email: email,
created: time.Now(),
}
}
func (u *User) GetUsername() string {
return u.username
}
func (u *User) GetEmail() string {
return u.email
}
使い方
user := NewUser("yamada", "yamada@example.com")
fmt.Println(user.GetUsername()) // yamada
このような「New◯◯関数」を使うことで、構造体の生成処理を共通化し、安全な初期化・一貫性ある管理が可能になります。
とくに、ID生成・時刻の自動設定など、毎回必要な処理をカプセル化できるのが利点です。
以下は最終セクション「まとめ:構造体でGoプログラミングをマスター」の漢字かな混じり文です。
まとめ:構造体でGoプログラミングをマスター
Go言語における構造体は、オブジェクトのようにデータをまとめて扱える便利な機能です。
この記事のポイント
- 定義・初期化:フィールド名を明示して安全に初期化
- メソッドの追加:値レシーバーとポインタレシーバーの使い分け
- 構造体の埋め込み:継承の代わりにコンポジションを使う
- タグと外部連携:JSONなどとのスムーズなデータ交換
コメント