Goの変数とデータ型

変数はプログラムの基礎となる要素です。Go言語では、最小限の構文を用いて、変数宣言を明確かつ安全に行えるようにしています。

Goの型システムの設計思想は、「明示的優先、暗黙的後回し」です。これは、すべての変数に明確に定義された型が割り当てられることを意味し、これによりコンパイラはコンパイル時に90%以上のエラーを検出できるようになります。このレッスンでは、Goにおける変数の宣言に関するすべての構文について学びます。

1. 学習内容


2. フルスタックエンジニアの実話

(1) 課題:動的型付けに起因する深夜のデバッグ作業

アリスは、PythonからGoへ移行したエンジニアです。彼女は最近この会社に入社し、ユーザー登録モジュールの引き継ぎを行いました:

「PMは次のように述べた。『サービス開始後、ユーザーデータはめちゃくちゃだった。誕生日に2024年1月1日と入力した人もいれば、「昨日」と入力した人もいれば、nullと入力した人もいて、データベース内のデータの半分は解析できなかった。』」

彼女はコードを調べてみると、Pythonの動的型付けが原因であることに気づいた:

PYTHON
# Python:変数の型はいつでも変更可能
birthday = "1990-01-01"
birthday = 1990  # 整数が突然合法になった
birthday = None  # None それも合法だ
birthday = ["today"]  # リストも有効です!

同じ変数が、コードの異なる箇所で文字列、整数、None、あるいはリストとして扱われる可能性があります。この混同を防ぐ仕組みは存在しません。

(2) Go言語での解決策

アリスはユーザーモデルをGoで書き直しました:

GO
// user.go
package main

import "fmt"

type User struct {
    Name      string  // 文字列
    Age       int     // 整数
    Height    float64 // 浮動小数点
    IsActive  bool    // ブール
    Birthday  string  // ISO 8601 日付文字列
}

func main() {
    user := User{
        Name:     "Alice",
        Age:      28,
        Height:   165.5,
        IsActive: true,
        Birthday: "1996-05-12",
    }

    // Want to change `Age` to a string? You'll get a compile-time error!
    // user.Age = "twenty-eight"  // ❌ cannot use "twenty-eight" as int

    fmt.Printf("User: %s, Age: %d, Height: %.1fcm\n",
        user.Name, user.Age, user.Height)
}

出力:

TEXT
User: Alice, Age: 28, Height: 165.5cm

(3) 利点:コンパイル時にエラーの90%を検出できる

次元 Python(動的型付け) Go(静的かつ強型付け)
コンパイル時の型チェック ❌ なし ✅ 厳格
実行時タイプエラー 頻繁 まれ
IDEの自動補完 弱い 強い
リファクタリングの安全性
ヌル値トラップ (None/null) 一般的 ゼロ値メカニズムによる保護
💡 ヒント: Goの「ゼロ値メカニズム」により、すべての変数は宣言時に妥当なデフォルト値が設定されるため、ヌルポインタ関連の問題の80%が解消されます。


3. 変数の宣言:var および :=

Go では、変数を宣言する方法として、var キーワード(標準)と := 短縮宣言(省略形)の 2 つの方法が用意されています。

(1) var キーワードの宣言(標準的な方法)

var は関数内やパッケージレベルで使用でき、記述方法は3通りあります:

GO
// Method 1: Declare a variable with an explicit type
var name string = "Alice"

// Method 2: Declare a variable and omit the type (the type is inferred from the value)
var name = "Alice"

// Method 3: Declare only; initialize to zero
var name string  // name = ""

▶ サンプル:var の3つの表記方法の比較

GO
package main

import "fmt"

func main() {
    // Method 1: Explicit Type + Initialization
    var userName string = "Alice"
    var userAge int = 28

    // Method 2: Type Inference
    var userEmail = "alice@example.com"

    // Method 3: Declare only; rely on the default value
    var userBio string  // ""

    fmt.Println(userName, userAge, userEmail, userBio)
}
▶ 試してみよう

出力:

TEXT
Alice 28 alice@example.com

(2) := 簡潔な宣言(最も一般的)

:= は Go における構文糖衣であり、関数内でのみ使用可能で、型を自動的に推論します:

GO
package main

import "fmt"

func main() {
    // Short declaration: variable_name := value
    name := "Alice"       // 推定されるのは string
    age := 28             // 推定されるのは int
    height := 165.5       // 推定されるのは float64
    isActive := true      // 推定されるのは bool

    fmt.Printf("%s is %d years old, height %.1f, active: %v\n",
        name, age, height, isActive)
}

出力:

TEXT
Alice is 28 years old, height 165.5, active: true

(3) var と :=――どちらを選べばいいですか?

シナリオ 推奨されるアプローチ 理由
スコープレベルの変数 var := 関数の外では使用できない
関数内のローカル変数 := 簡潔で、4文字節約できる
ゼロでなければならない var x int := 同時に初期化する必要がある
明示的な型変換が必要 var x int = int(3.14) 型変換がはっきりと確認できる
一括宣言 var (...) := は一括宣言に対応していません

4. Goの基本データ型

Goには14種類の基本データ型があり、その用途に応じて4つの主要なカテゴリに分類されます。

(1) 整数型

100%
graph TB
    A[整数型] --> B[符号付き int]
    A --> C[符号なし uint]
    A --> D[バイト型 byte]
    A --> E[特殊 rune]

    B --> B1[int<br/>少なくとも 32 位]
    B --> B2[int8 / int16 / int32 / int64]

    C --> C1[uint<br/>少なくとも 32 位]
    C --> C2[uint8 / uint16 / uint32 / uint64]

    D --> D1[byte = uint8<br/>強調するのは「バイト」です]
    E --> E1[rune = int32<br/>強調は Unicode コードポイント]
種類 サイズ 適用範囲 代表的な用途
int 32ビットまたは64ビット(プラットフォームに依存) -2^31 から 2^31-1 またはそれ以上 デフォルトの整数型
int8 8ビット -128~127 範囲の狭い整数
int32 32ビット -2^31 ~ 2^31-1 クロスプラットフォームの固定長
int64 64ビット -2^63 ~ 2^63-1 大きな整数、タイムスタンプ
uint 32ビットまたは64ビット 0 から 2^32-1 またはそれ以上 デフォルトでは符号なし
byte 8ビット 0~255 バイト、ASCII文字
rune 32ビット ユニコードコードポイント 単一のユニコード文字

▶ サンプル:intint64 の違い

GO
package main

import (
    "fmt"
    "unsafe"
)

func main() {
    var a int = 100
    var b int64 = 200

    fmt.Printf("a (int) size: %d bytes\n", unsafe.Sizeof(a))
    fmt.Printf("b (int64) size: %d bytes\n", unsafe.Sizeof(b))

    // You cannot directly add `int` and `int64`!
    // sum := a + b  // ❌ mismatched types int and int64

    // An explicit conversion is required
    sum := a + int(b)
    fmt.Println("sum:", sum)
}
▶ 試してみよう

出力(64ビットプラットフォームの場合):

TEXT
a (int) size: 8 bytes
b (int64) size: 8 bytes
sum: 300

(2) 浮動小数点型

種類 サイズ 精度 代表的な用途
float32 32ビット 有効桁数約7桁 科学計算、グラフィックス
float64 64ビット 有効桁数約15 デフォルトの浮動小数点型

(3) ブール型

GO
var isActive bool = true
var hasPermission bool  // ゼロ値 = false

(4) 文字列型

GO
var greeting string = "Hello, Go!"
var emptyString string  // ゼロ値 = ""(空の文字列)
var multiLine string = `1行目
2行目
3行目`  // バッククォートは複数行に対応しています

(5) 14種類のクイックリファレンス表

カテゴリ タイプ ゼロ値
整数 int / int8 / int16 / int32 / int64 0
符号なし整数 uint / uint8 / uint16 / uint32 / uint64 0
バイト/文字 byte (=uint8) / rune (=int32) 0
浮動小数点 float32 / float64 0.0
ブール値 bool false
文字列 string ""

5. Go言語特有のゼロ値の仕組み

Goには「初期化されていない」変数は存在しません。すべての変数は、宣言された時点で妥当なデフォルト値を持っています。

▶ サンプル:すべての型の値がゼロの場合

GO
package main

import "fmt"

func main() {
    var i int
    var f float64
    var b bool
    var s string
    var p *int  // 指針のゼロ値は nil

    fmt.Printf("int: %d\n", i)
    fmt.Printf("float64: %f\n", f)
    fmt.Printf("bool: %v\n", b)
    fmt.Printf("string: %q\n", s)  // %q 引用符で囲まれた文字列を表示する,空の文字列の表示 ""
    fmt.Printf("pointer: %v\n", p)
}
▶ 試してみよう

出力:

TEXT
int: 0
float64: 0.000000
bool: false
string: ""
pointer: <nil>

(1) ゼロ値と他の言語との比較

ヌル値 Java Python C/C++
整数 0 0 存在しない 未定義(ゴミ値)
浮動小数点 0.0 0.0 存在しない 未定義
ブール値 false false False 未定義
文字列 "" null "" 未定義
ポインタ nil null None NULL(ただし、不正なポインタになりやすい)
🔥 よくある間違い: C/C++では、初期化されていない変数にはメモリ上にランダムな値が格納されます。これが、バッファオーバーフローのバグの90%の根本原因となっています。Goは、ゼロ値メカニズムを通じて、言語レベルでこの問題を解消しています。


6. const 定数と iota 列挙型

(1) const を使用して定数を宣言する

定数はコンパイル時に決定され、変更することはできません。また、基本型でなければなりません

GO
const Pi = 3.14159
const AppName = "web-tutorial"
const MaxConnections = 1000

(2) Iota列挙:Goの洗練された列挙方法

iota は、Go における定数カウンターであり、0 から始まり、各行ごとに自動的に 1 ずつ増加します:

GO
package main

import "fmt"

const (
    Sunday    = iota  // 0
    Monday            // 1
    Tuesday           // 2
    Wednesday         // 3
    Thursday          // 4
    Friday            // 5
    Saturday          // 6
)

func main() {
    fmt.Printf("Sunday=%d Monday=%d Saturday=%d\n", Sunday, Monday, Saturday)
}

出力:

TEXT
Sunday=0 Monday=1 Saturday=6

▶ サンプル:カスタム・イオタ増分(HTTPステータスコード)

GO
package main

import "fmt"

const (
    StatusOK       = iota * 100  // 0
    StatusRedirect               // 100
    StatusClientError            // 200
    StatusServerError            // 300
)

func main() {
    fmt.Printf("OK=%d Redirect=%d ClientError=%d ServerError=%d\n",
        StatusOK, StatusRedirect, StatusClientError, StatusServerError)
}
▶ 試してみよう

出力:

TEXT
OK=0 Redirect=100 ClientError=200 ServerError=300

(3) iotaの応用:ビットマスク(ファイルのアクセス権)

GO
const (
    ReadPermission   = 1 << iota  // 1 << 0 = 1
    WritePermission               // 1 << 1 = 2
    ExecutePermission             // 1 << 2 = 4
)

func main() {
    // Permission Combination: Read + Write = 1 | 2 = 3
    rw := ReadPermission | WritePermission
    fmt.Printf("Read=%d Write=%d Execute=%d RW=%d\n",
        ReadPermission, WritePermission, ExecutePermission, rw)
}

出力:

TEXT
Read=1 Write=2 Execute=4 RW=3
💡 ヒント: iota は、Go において「enum」に最も近い構文ですが、Python や Java の enum 型ほど厳密ではありません。本番環境でより強力な型安全性を必要とする場合は、独自の型を定義することができます。


7. 明示的な型変換

Goでは暗黙的な型変換は許可されていません。すべての変換は明示的に宣言する必要があります。これは、Goの「暗黙的よりも明示的」という原則を反映したものです。

(1) 数値型の変換

GO
package main

import "fmt"

func main() {
    var i int = 42
    var f float64 = float64(i)      // int → float64
    var u uint = uint(f)            // float64 → uint

    fmt.Printf("i=%d f=%v u=%d\n", i, f, u)

    // Truncation example: Converting a float to an int results in the loss of the decimal part
    var pi float64 = 3.14159
    var truncated = int(pi)
    fmt.Printf("pi=%.5f truncated=%d\n", pi, truncated)
}

出力:

TEXT
i=42 f=42 u=42
pi=3.14159 truncated=3

(2) 文字列の変換:strconv パッケージ

GO
package main

import (
    "fmt"
    "strconv"
)

func main() {
    // String → Number
    n, err := strconv.Atoi("123")
    fmt.Printf("n=%d err=%v\n", n, err)

    // Numbers → Strings
    s := strconv.Itoa(456)
    fmt.Printf("s=%s (type: %T)\n", s, s)

    // String → float
    f, _ := strconv.ParseFloat("3.14", 64)
    fmt.Printf("f=%f\n", f)

    // float → string (specified precision)
    fmt.Println(strconv.FormatFloat(3.14159, 'f', 2, 64))  // "3.14"
}

出力:

TEXT
n=123 err=<nil>
s=456 (type: string)
f=3.140000
3.14

(3) 暗黙の型変換と明示的な型変換

シナリオ Java/C++ Go
int + float 自動的にfloat型に変換される コンパイルエラー;明示的に指定する必要がある
int32 + int64 自動的に int64 に変換されました コンパイルエラー
int から string 暗黙的 strconv.Itoa() が必要
⚠️ 注: Goの厳格な型変換は冗長に見えるかもしれませんが、実際には「丸め誤差」や「整数オーバーフロー」など、JavaやC++でよく見られる典型的なバグを防ぐ役割を果たしています。


8. fmt: 出力の書式設定

(1) よく使われる書式設定を表す動詞

動詞 意味 適用可能なタイプ
%d 10進整数 int, int8/16/32/64
%f 浮動小数点数 float32, float64
%s 文字列 文字列
%v すべてのタイプのデフォルト形式 すべてのタイプ
%T タイプ 任意のタイプ
%t ブール型 bool
%q 引用された文字列 文字列、ルーン
%x 16進数 int, []byte
%p ポインタアドレス ポインタ
%% パーセント記号そのもの

▶ サンプル:fmt.Printf の全機能を紹介するデモ

GO
package main

import "fmt"

func main() {
    name := "Alice"
    age := 28
    height := 165.5
    isActive := true
    hobbies := []string{"coding", "reading", "hiking"}

    fmt.Printf("Name: %s\n", name)              // %s 文字列
    fmt.Printf("Age: %d\n", age)                // %d 整数
    fmt.Printf("Height: %.1f cm\n", height)     // %.1f 浮動小数点数の精度 1 位
    fmt.Printf("Active: %t\n", isActive)        // %t ブール
    fmt.Printf("Hobbies: %v\n", hobbies)        // %v デフォルトの書式
    fmt.Printf("Name type: %T\n", name)         // %T 種類
    fmt.Printf("100%% complete!\n")             // %% 文字通り %

    // %v Advanced usage: %+v displays the field name (struct), %#v displays Go syntax
    fmt.Printf("hobbies: %#v\n", hobbies)
}
▶ 試してみよう

出力:

TEXT
Name: Alice
Age: 28
Height: 165.5 cm
Active: true
Hobbies: [coding reading hiking]
Name type: string
100% complete!
hobbies: []string{"coding", "reading", "hiking"}

(2) Print、Println、Printf の違い

機能 出力の終端処理 使用例
fmt.Print() 改行なし 連続出力
fmt.Println() 自動改行 + スペース区切り 簡単なデバッグ
fmt.Printf() 改行を追加しない;\n が必要 書式付き出力
fmt.Sprintf() 文字列を返す。出力は行わない 文字列を連結する

9. 完全な例:ユーザー登録フォーム

このレッスンで学んだ重要な概念をすべてまとめ、ユーザー登録データ収集プログラムを作成してください:

GO
// main.go
package main

import (
    "fmt"
    "strconv"
)

// Defining User Role Enumerations Using Iota
const (
    RoleGuest = iota // 0 訪問者
    RoleUser         // 1 一般ユーザー
    RoleAdmin        // 2 管理者
    RoleSuperAdmin   // 3 スーパー管理者
)

func main() {
    // 1. Use the := short declaration for user information
    name := "Alice"
    age := 28
    height := 165.5
    isActive := true
    role := RoleUser

    // 2. Explicit Type Conversion
    ageAsFloat := float64(age)
    heightAsString := strconv.FormatFloat(height, 'f', 1, 64)

    // 3. const Constants
    const MaxLoginAttempts = 5

    // 4. fmt: Formatted Output
    fmt.Println("========================================")
    fmt.Println("      User Registration Form")
    fmt.Println("========================================")
    fmt.Printf("Name:           %s\n", name)
    fmt.Printf("Age:            %d (%.0f years old)\n", age, ageAsFloat)
    fmt.Printf("Height:         %s cm\n", heightAsString)
    fmt.Printf("Active:         %t\n", isActive)
    fmt.Printf("Role:           %d (User)\n", role)
    fmt.Printf("Max Attempts:   %d\n", MaxLoginAttempts)
    fmt.Printf("Type Check:     name is %T, age is %T\n", name, age)
    fmt.Println("========================================")

    // 5. Demonstration of Zero Values for Variables
    var uninitializedAge int
    fmt.Printf("\nUninitialized age (zero value): %d\n", uninitializedAge)
}

期待される出力:

TEXT
========================================
      User Registration Form
========================================
Name:           Alice
Age:            28 (28 years old)
Height:         165.5 cm
Active:         true
Role:           1 (User)
Max Attempts:   5
Type Check:     name is string, age is int
========================================

Uninitialized age (zero value): 0
🔥 よくある間違い: strconv.Atoi("abc") を実行するとエラーが発生しますが、このエラーは 決して無視してはいけません。正しい構文は n, err := strconv.Atoi("abc"); if err != nil { ... } です。第8課では、エラー処理についてさらに詳しく解説します。


❓ よくある質問

Q var:=、どちらが良いですか?
A 関数内では := の使用をお勧めします。簡潔であり、初期化が強制されるためです。パッケージレベルの変数には var を使用する必要があります。どちらによって生成されるバイトコードもまったく同じであり、単なる構文上の糖衣に過ぎません。
Q Goのintのサイズは、32ビットプラットフォームと64ビットプラットフォームで異なります。これによりどのような問題が発生する可能性がありますか?
A 64ビットプラットフォームでは8バイト、32ビットプラットフォームでは4バイトです。データをシリアライズする必要がある場合(例:JSONやデータベースへの保存など)、クロスプラットフォームの互換性の問題を避けるために、int32またはint64を明示的に指定することをお勧めします。
Q Goにはenumというキーワードがありません。列挙型はどのように作成すればよいですか?
A const + iotaを使用してください。Javaのenum classほど厳密ではありませんが、十分です。型安全性を確保したい場合は、type Role intのようなカスタム型と一連の定数を組み合わせて使用することができます。
Q iota の値は 0 から始まりますが、1 から始めることはできますか?
A はい。_ = iota を使用すると 0 をスキップできます。また、iota + 1: const (A = iota + 1; B; C) と指定すると、A=1、B=2、C=3 となります。これは、HTTP ステータスコードや月など、値を 1 から開始する必要がある場面で一般的に使用されます。
Q なぜGoは暗黙的な型変換をサポートしていないのですか?
A Goチームは、「明示的な方が暗黙的なものより優れている」と考えています。暗黙的な変換は、JavaやC++における多くのバグの根本原因となっています(例えば、int演算とlong演算を混在させた際の精度損失など)。Goの厳格な型変換により、コードの可読性が高まり、バグの発生も減少します。
Q string[]byte の相互変換はどのように行いますか?
A []byte(s) は文字列をバイトスライスに変換し(データをコピーします)、string(b) はバイトスライスを文字列に変換します。これらは、ネットワーク I/O や暗号化の場面で一般的に使用されます。注:[]byteは変更に適しており、stringは読み取り専用での使用に適しています。
Q 変数名に漢字を使用できますか?
A 技術的には可能です(GoはUnicode識別子をサポートしています)が、強く推奨されません。漢字の変数名を使用すると、国際的なチームでのコード共同作業が困難になるほか、一部のツール(リンターやIDEなど)がそれらを認識できない場合があります。常に英語の命名規則(キャメルケースまたはアンダースコア)を使用してください。
Q 定数のアドレスを取得することはできますか?
A いいえ。定数はコンパイル時に定義されるため、実行時のメモリアドレスを持ちません。&Pi を実行すると、コンパイル時にエラーが発生します。

📖 まとめ


📝 練習問題

  1. 基本演習(難易度 ⭐)var および := の両方の構文を使用して、5つの変数(name/age/height/isStudent/gpa)を宣言し、それらの値と型を出力してください。

  2. 応用演習(難易度 ⭐⭐)iota を使用して 4 つの HTTP ステータスコード定数(200/404/500/503)を定義し、ステータスコードに応じて対応するテキスト(例:「OK」、 「Not Found」、「Internal Server Error」、または「Service Unavailable」)を返すhandleRequest(code int)関数を記述してください。

  3. チャレンジ問題(難易度 ⭐⭐⭐)単位変換プログラムを作成してください。華氏温度を入力として受け取り、摂氏温度を出力します(計算式:C = (F - 32) × 5/9)。要件:(1) 入力の読み取りには fmt.Scanf を使用すること;(2) 型変換には strconv.ParseFloat を使用すること;(3) 無効な入力への対処を行うこと(エラーを返す)。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%