📚 背景知識(読んでから問題へ)
Goは静的型付け言語です。変数の型はコンパイル時に確定し、実行中に変わることはありません。Python/JavaScriptのような動的型付け言語に慣れていると最初は窮屈に感じますが、これはGoの設計思想である「明示性(explicitness)」の表れです。型が曖昧なまま実行時エラーになる事態を防ぎ、コードを読むだけで変数が何を表しているかが分かるようにするという狙いがあります。
Goの変数宣言には主に2つの書き方があります。
var による明示的宣言
var age int = 30
var name string = "Gopher"
パッケージレベル(関数の外)でも使える唯一の宣言方法です。型を明示したいとき、あるいはゼロ値のまま宣言だけしたいときに使います。
:= による短縮宣言(型推論)
age := 30
name := "Gopher"
右辺の値からコンパイラが型を推論します。関数内でのみ使え、シンプルで簡潔です。Go言語で最も多用される書き方です。
ゼロ値(Zero Value)という考え方
Goでは変数を初期化せずに宣言すると、自動的に「ゼロ値」で初期化されます。C言語のように未初期化のメモリを参照してしまうバグが原理的に起きません。
| 型 | ゼロ値 |
|---|---|
int | 0 |
float64 | 0 |
string | ""(空文字列) |
bool | false |
基本型(int, float64, string, bool)
int: プラットフォーム依存の符号付き整数(64bit環境では通常64bit)float64: 64bit浮動小数点数(Goではfloatという型は存在せず、float32かfloat64を明示する)string: UTF-8バイト列(イミュータブル)bool:true/falseのみ
なぜGoは暗黙的な型変換をしないのか
Goではintとfloat64を直接演算することすらできません。必ずfloat64(i)のように明示的な変換が必要です。これは「暗黙のうちに精度が落ちる/意図しない変換が起きる」というバグの温床を排除するための設計判断です。C/Java/JavaScriptなどで頻発する「暗黙変換由来のバグ」をGoは言語レベルで防いでいます。
📝 問題
タスク1(概念理解):
以下の問いに簡潔に答えてください。
varと:=の違いは何ですか。それぞれをどんな場面で使い分けるべきですか。- 変数を宣言だけして初期化しなかった場合、Goでは何が起きますか。
int,float64,string,boolそれぞれのゼロ値を答えてください。 - Goはなぜ
intからfloat64への変換を自動で行わず、明示的な変換(float64(i))を要求するのでしょうか。
タスク2(実装):
以下の要件を満たすmain.goを実装してください。
varを使ってint型のage(30),float64型のprice(19.99),string型のname("Gopher"),bool型のisActive(true) を宣言し、値を出力する:=を使って、同じ意味を持つ別の変数を型推論で宣言し、値を出力する- 初期化せずに宣言した
int,float64,string,bool型の変数を出力し、ゼロ値であることを確認する int型の変数を明示的にfloat64型へ変換してから割り算を行い、結果を出力する%Tフォーマット指定子を使って、各変数の実際の型を出力する
🔍 ヒント(段階的開示)
ヒント1 — 方向性
package mainとfunc main()から始まる、go runでそのまま実行できる完全なプログラムにしてください- 出力には
fmt.Printlnまたはfmt.Printfを使います - ゼロ値を確認するには、単に
var x intのように初期化せず宣言し、そのまま出力するだけです
ヒント2 — アプローチ
- 型変換は
float64(変数名)のように、変換したい型を関数のように呼び出す構文です %Tはfmt.Printfの中で使うと、その変数の型名を文字列として出力してくれます(例:fmt.Printf("%T\n", age)→int)string型のゼロ値は空文字列なので、%q(クォート付き文字列)で出力すると""が見えて分かりやすいです
ヒント3 — コード骨格
package main
import "fmt"
func main() {
// 1. varによる明示的宣言
var age int = 30
// price, name, isActive も同様に宣言...
// 2. := による型推論宣言
count := 10
// rate, label, enabled も同様に宣言...
// 3. ゼロ値の確認
var zeroInt int
// zeroFloat, zeroString, zeroBool も同様に宣言...
// 4. 型変換
var i int = 42
var f float64 = float64(i) / 5
// 5. %T で型を確認
fmt.Printf("age: %T\n", age)
}
✅ 模範解答
package main
import "fmt"
func main() {
// --- 1. var による明示的宣言 ---
var age int = 30
var price float64 = 19.99
var name string = "Gopher"
var isActive bool = true
fmt.Println("--- var による明示的宣言 ---")
fmt.Println(age, price, name, isActive)
// --- 2. := による型推論宣言 ---
count := 10
rate := 0.05
label := "concurrent"
enabled := false
fmt.Println("--- := による型推論宣言 ---")
fmt.Println(count, rate, label, enabled)
// --- 3. ゼロ値の確認(初期化せずに宣言) ---
var zeroInt int
var zeroFloat float64
var zeroString string
var zeroBool bool
fmt.Println("--- ゼロ値の確認 ---")
fmt.Printf("int: %d, float64: %v, string: %q, bool: %v\n", zeroInt, zeroFloat, zeroString, zeroBool)
// --- 4. 型変換(int → float64) ---
var i int = 42
var f float64 = float64(i) / 5
fmt.Println("--- 型変換 ---")
fmt.Printf("i=%d を float64 に変換して割り算 → f=%v\n", i, f)
// --- 5. %T で型を確認 ---
fmt.Println("--- 型の確認 (%T) ---")
fmt.Printf("age: %T, price: %T, name: %T, isActive: %T\n", age, price, name, isActive)
}
▶ 実行結果を見る(go run main.go)
--- var による明示的宣言 ---
30 19.99 Gopher true
--- := による型推論宣言 ---
10 0.05 concurrent false
--- ゼロ値の確認 ---
int: 0, float64: 0, string: "", bool: false
--- 型変換 ---
i=42 を float64 に変換して割り算 → f=8.4
--- 型の確認 (%T) ---
age: int, price: float64, name: string, isActive: bool
🪜 Step-by-Step 解説
var age int = 30
var 変数名 型 = 値 という構文です。パッケージレベル変数はこの形式でしか宣言できないため、まずこの基本形を押さえます。型を書くことで、コードを読む人(未来の自分を含む)が一目で「これは整数だ」と分かります。
count := 10
右辺の10はint型のリテラルなので、Goコンパイラはcountをintと推論します。関数内のローカル変数はほとんどこの書き方が使われます。「型を書かなくていい」のではなく「コンパイラが型を決めてくれる」だけであり、countは依然として静的にint型として扱われます。
var zeroInt int
値を代入しなくても、Goはメモリを必ずゼロ値で初期化します。nilポインタ参照のような「未定義動作」が起きないのは、この仕組みのおかげです。
var f float64 = float64(i) / 5
iはint型なので、そのまま5(int)と割り算すればint同士の整数除算になってしまいます。float64(i)で明示的に変換することで、小数点以下を含む結果を得られます。この「明示しないと変換されない」制約が、意図しない精度落ちを防ぎます。
fmt.Printf("age: %T\n", age)
%Tはデバッグ時に非常に便利です。変数の型を思い違いしていないかをその場で確認できます。
💡 設計思想・なぜこう書くのか
Goの型システムは「シンプルさ」と「安全性」を両立させるために設計されています。
"5" + 3が"53"になるような「暗黙の型強制」による予測不能な挙動を、Goは言語レベルで禁止しています。バグの原因を減らすことが、開発速度を上げることにつながるという思想です。if err != nilのようなイディオムでも「未初期化かもしれない」という余計な心配をせずに済みます。:=、パッケージレベルやゼロ値宣言はvar」という慣習がgofmt/go vetの思想とともに広く共有されています。書き方に選択の余地を残しつつ、実務では自然と統一されるのがGoらしいところです。🌐 他言語との比較
| 観点 | Go | Java | Python | TypeScript |
|---|---|---|---|---|
| 型付け | 静的(コンパイル時確定) | 静的 | 動的(実行時に決まる) | 静的(構造的型付け、コンパイル時のみ) |
| 型推論 | :=で可能(ローカルのみ) | var(Java 10+、ローカルのみ) | 型注釈自体が任意 | let x = 5で標準的に推論 |
| 未初期化変数 | ゼロ値で自動初期化 | コンパイルエラー(ローカル変数) | そもそも概念がない(代入で生成) | コンパイルエラー(strict時) |
| 暗黙の型変換 | 一切なし(すべて明示) | 一部あり(int→long等のwidening) | 動的なので都度解決 | 緩い(JSの挙動を引き継ぐ) |
JavaのvarはGoの:=と似ていますが、GoはJavaよりも歴史的に先行してこの構文を持ち、かつvar(明示的宣言)と:=(推論)が明確に役割分担している点が特徴です。Pythonのように「型を書かなくてもいい」のではなく「型は必ず存在するが、書く手間を省ける」という発想の違いを意識すると理解が早まります。
🏆 実務での使いどころ
- 設定値やAPIのレスポンス構造体のフィールドには
varで型を明示し、コードレビュー時に型の意図を明確にする - 関数内のループ変数や一時変数はほぼ
:=で書かれる(実際のOSSコード、たとえばKubernetesやDockerのソースでも大部分がこのスタイル) - JSON/DBから受け取った数値をビジネスロジックで扱う際、
intとfloat64の変換ミスは金額計算のバグに直結するため、明示的な型変換が求められる場面は極めて多い - ゼロ値の仕組みは、構造体の初期化(
var u Userだけで安全に使えるフィールドが揃う)にも直結しており、Go全体の設計の土台になっている
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
:=はいつでも使える | Pythonなど動的型付け言語の感覚を引きずる | :=は関数内(ローカルスコープ)でのみ使用可能。パッケージレベルではvar必須 |
intとfloat64をそのまま演算できると思う | C/Javaは暗黙のwidening変換があるため | Goではintとfloat64の混在演算はコンパイルエラー。必ずfloat64(i)等で明示変換する |
ゼロ値とnilを混同する | どちらも「空っぽ」に見えるため | intのゼロ値は0、stringのゼロ値は""。nilはポインタ・スライス・マップ・インターフェースなど参照型のゼロ値であり、基本型には存在しない |
:=で既存の変数を再宣言しようとしてエラーになる | Pythonの再代入感覚 | :=は左辺に新しい変数が最低1つ必要。既存変数のみへの再代入は=を使う |
🚀 次のステップ
- 発展: 複数変数の同時宣言(
var a, b, c = 1, 2.0, "x")や、constブロックとの違いを触ってみましょう - 次回予告: Day 002 — 定数とiota(
constとiotaによる列挙パターン)