
こんにちは。よっしーです(^^)
本日は、Go言語のよくある質問 について解説しています。
背景
Go言語を学んでいると「なんでこんな仕様になっているんだろう?」「他の言語と違うのはなぜ?」といった疑問が湧いてきませんか。Go言語の公式サイトにあるFAQページには、そんな疑問に対する開発チームからの丁寧な回答がたくさん載っているんです。ただ、英語で書かれているため読むのに少しハードルがあるのも事実で、今回はこのFAQを日本語に翻訳して、Go言語への理解を深めていけたらと思い、これを読んだ時の内容を備忘として残しました。
Changes from C
なぜ構文がC言語とそんなに異なるのか?
宣言構文以外では、違いは大きくなく、2つの願望に由来しています。第一に、構文は軽量に感じられるべきで、必須のキーワード、繰り返し、難解さが多すぎないようにすべきです。第二に、言語は解析しやすいように設計されており、シンボルテーブルなしでパースできます。これにより、デバッガ、依存関係アナライザー、自動ドキュメント抽出ツール、IDEプラグインなどのツールを構築することがはるかに簡単になります。C言語とその子孫は、この点で悪名高く困難です。
解説
この問題は何について説明しているの?
Goを初めて見た人は、こう思うかもしれません:
// C言語
int *p;
int (*fp)(int, int);
// Go言語
var p *int
var fp func(int, int) int
「あれ? 順番が逆? なぜC言語と違うの?」
この質問は、なぜGoの構文はC言語と異なるのか、そしてその設計判断の理由を説明しています。
基本的な用語
- 宣言構文: 変数や関数を定義する書き方
- シンボルテーブル: コンパイラが変数や関数の情報を記録する表
- パーサー: ソースコードを解析して構造を理解するプログラム
- 文法の曖昧性: 1つの文が複数の意味に解釈できる問題
- IDE: 統合開発環境(Visual Studio Code、GoLandなど)
Goの設計の2つの願望
願望1: 軽量で読みやすい構文
目標:
- キーワードが少ない
- 繰り返しが少ない
- 難解な記号が少ない
例: C言語の問題点
// C言語: 繰り返しが多い
struct Point {
int x;
int y;
};
struct Point createPoint(int x, int y) {
struct Point p; // "struct Point" を何度も書く
p.x = x;
p.y = y;
return p;
}
struct Point p1; // また "struct Point"
struct Point p2; // また...
Goの改善:
// Go: シンプル
type Point struct {
x int
y int
}
func createPoint(x, y int) Point { // 型名だけでOK
return Point{x: x, y: y}
}
p1 := Point{1, 2} // 短い!
p2 := Point{3, 4}
願望2: 解析が容易
目標:
- シンボルテーブルなしでパース可能
- ツールが作りやすい
- IDEのサポートが簡単
これが非常に重要です。
C言語の解析の難しさ
問題1: 宣言が読みにくい
C言語の複雑な宣言:
int *p; // intへのポインタ
int *a[10]; // intへのポインタの配列
int (*pa)[10]; // int配列へのポインタ
int *f(); // intへのポインタを返す関数
int (*pf)(); // intを返す関数へのポインタ
int (*(*x[3])())[5]; // ??? (読めない!)
最後の例、読めますか? これは:
「5個のintの配列へのポインタを返す関数へのポインタの配列(要素数3)」
右から左、左から右へ読む必要があります。
Goの宣言:
var p *int // intへのポインタ
var a [10]*int // intへのポインタの配列
var pa *[10]int // int配列へのポインタ
var f func() *int // intへのポインタを返す関数
var pf *func() int // intを返す関数へのポインタ
var x [3]*func() *[5]int // 左から右に読める!
左から右に自然に読めます!
問題2: 曖昧な構文
C言語の悪名高い問題:
// これは何?
x * y;
2つの解釈が可能:
xとyの掛け算(式)yという名前の、x型へのポインタ宣言
どっち? → シンボルテーブルを見ないと分からない!
// xが型名なら:
typedef struct { ... } x;
x * y; // ポインタ宣言
// xが変数なら:
int x = 5;
x * y; // 掛け算
Goの解決:
// 掛け算
result := x * y
// ポインタ宣言
var y *x
// 曖昧さなし! 見ればわかる!
問題3: 型修飾子の位置
C言語:
const int *p; // 定数intへのポインタ
int const *p; // 同じ意味
int * const p; // intへの定数ポインタ
const int * const p; // 定数intへの定数ポインタ
// 混乱する!
Goの解決:
// Goにはconstはない(定数は別の方法)
var p *int // シンプル
// 定数が必要なら
const x = 42 // 定数値
Goの宣言構文の設計
基本原則: 左から右に読む
パターン:
名前 型
例:
// 変数
var name string // nameは文字列
var age int // ageは整数
var price float64 // priceは64ビット浮動小数点
// 関数
func add(x int, y int) int { // xとyはint、返り値もint
return x + y
}
// 複数の同じ型
func add(x, y int) int { // xとyはint(省略形)
return x + y
}
ポインタ
C言語:
int *p, *q; // pとqはポインタ
int *p, q; // pはポインタ、qはint (混乱しやすい!)
Go:
var p, q *int // pとqは両方ポインタ
var p *int // pはポインタ
var q int // qはint
// 明確!
配列とスライス
C言語:
int a[10]; // 10個のintの配列
int *pa[10]; // ポインタの配列
int (*ap)[10]; // 配列へのポインタ
Go:
var a [10]int // 10個のintの配列
var pa [10]*int // ポインタの配列
var ap *[10]int // 配列へのポインタ
// 左から右に読める!
関数
C言語:
int (*fp)(int, int); // 関数ポインタ
int (*(*fp)(int))(int); // ??? (読めない)
Go:
var fp func(int, int) int // 関数型
var fp func(int) func(int) int // 関数を返す関数
// 自然に読める!
なぜ解析が容易なのが重要か?
ツールエコシステム
解析が難しい(C言語):
複雑な構文
↓
解析に完全なコンパイラが必要
↓
ツール開発が困難
↓
ツールが少ない/質が低い
解析が容易(Go):
シンプルな構文
↓
軽量なパーサーで解析可能
↓
ツール開発が簡単
↓
豊富な高品質ツール
Goの豊富なツール群
標準で付いてくるツール:
# フォーマット
gofmt main.go
# import整理
goimports -w main.go
# リント(静的解析)
go vet main.go
# ドキュメント生成
go doc fmt.Println
# リファクタリング
gorename -from '"pkg".OldName' -to NewName
# コールグラフ生成
go-callvis main.go
これらすべてが簡単に作れるのは、Goの構文が解析しやすいから!
IDEサポート
C/C++のIDE:
複雑なパーサー
↓
重い、遅い
↓
誤った補完やエラー検出
↓
フラストレーション
GoのIDE:
シンプルなパーサー
↓
軽い、速い
↓
正確な補完とエラー検出
↓
快適な開発体験
具体的な構文比較
変数宣言
C言語:
int x = 5;
int y = 10;
int z;
// ポインタ
int *p1, *p2; // 両方ポインタ
int *p3, q; // p3はポインタ、qはint (注意!)
Go:
var x int = 5
var y int = 10
var z int
// 型推論
x := 5 // 短い!
y := 10
// ポインタ
var p1, p2 *int // 明確
var p3 *int // 明確
var q int // 明確
関数宣言
C言語:
// 返り値の型が先
int add(int x, int y) {
return x + y;
}
// 複数の返り値? 不可能
// ポインタや構造体で回避
void divide(int a, int b, int *quot, int *rem) {
*quot = a / b;
*rem = a % b;
}
Go:
// 返り値の型が後
func add(x, y int) int {
return x + y
}
// 複数の返り値が簡単!
func divide(a, b int) (int, int) {
return a / b, a % b
}
// 名前付き返り値
func divide(a, b int) (quot, rem int) {
quot = a / b
rem = a % b
return // 自動的にquotとremを返す
}
構造体
C言語:
// 定義
struct Point {
int x;
int y;
};
// 使用(structキーワード必須)
struct Point p1;
struct Point p2;
// typedefで短縮
typedef struct Point Point;
Point p3; // やっと短く書ける
Go:
// 定義
type Point struct {
x int
y int
}
// 使用(シンプル)
var p1 Point
p2 := Point{1, 2}
p3 := Point{x: 3, y: 4}
インターフェース
C言語:
// インターフェースの概念なし
// 関数ポインタで模倣
struct Animal {
void (*speak)(void);
void (*move)(void);
};
// 使いにくい...
Go:
// インターフェース定義
type Animal interface {
Speak() string
Move()
}
// 実装(暗黙的)
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
func (d Dog) Move() {
// 移動処理
}
// Dogは自動的にAnimalインターフェースを実装
軽量な構文の例
繰り返しの削減
C言語:
struct Employee {
char *name;
int age;
float salary;
};
struct Employee emp1;
struct Employee emp2;
struct Employee emp3;
// "struct Employee" を何度も...
Go:
type Employee struct {
name string
age int
salary float64
}
emp1 := Employee{"Alice", 30, 50000}
emp2 := Employee{"Bob", 25, 45000}
emp3 := Employee{"Charlie", 35, 60000}
// 型名だけ!
キーワードの削減
C言語のキーワード:
auto, break, case, char, const, continue, default, do,
double, else, enum, extern, float, for, goto, if,
int, long, register, return, short, signed, sizeof, static,
struct, switch, typedef, union, unsigned, void, volatile, while
合計: 32個
Goのキーワード:
break, case, chan, const, continue, default, defer, else,
fallthrough, for, func, go, goto, if, import, interface,
map, package, range, return, select, struct, switch, type, var
合計: 25個
しかも、Goの方が機能が多い!
セミコロン不要
C言語:
int x = 5; // セミコロン必須
int y = 10; // セミコロン必須
if (x > 0) {
printf("positive"); // セミコロン必須
} // セミコロン不要(でも{ }は必須)
Go:
x := 5 // セミコロン不要!
y := 10 // セミコロン不要!
if x > 0 {
fmt.Println("positive") // セミコロン不要!
}
Goでは、コンパイラが自動的にセミコロンを挿入します。
解析の容易さの実例
例1: 簡単なパーサー
Goコードの解析:
// これを解析したい
var x int = 42
// パーサーの処理
1. "var" → 変数宣言の開始
2. "x" → 変数名
3. "int" → 型
4. "=" → 初期化
5. "42" → 値
シンプル! 前方の情報不要!
C言語の解析:
// これを解析したい
x * y;
// パーサーの処理
1. "x" → これは何? 型? 変数?
2. シンボルテーブルを確認
- xが型 → ポインタ宣言
- xが変数 → 掛け算
3. やっと判断
複雑! シンボルテーブル必須!
例2: エディタの自動補完
Go:
type Person struct {
Name string
Age int
}
p := Person{}
p. // ← ここで "." を入力
// エディタ: "Name と Age が候補です!"
// 簡単に分かる!
C++:
template<typename T>
class Container {
T value;
public:
T get() { return value; }
};
Container<int> c;
c. // ← ここで "." を入力
// エディタ: "テンプレートを展開して...
// 型を解決して...
// 複雑!"
なぜこの設計判断が重要か?
開発者体験
軽量な構文
↓
書きやすい
↓
読みやすい
↓
メンテナンスしやすい
ツールエコシステム
解析が容易
↓
ツールが豊富
↓
IDEサポートが優秀
↓
生産性向上
チーム開発
一貫した構文
↓
スタイルの統一(gofmt)
↓
コードレビューが簡単
↓
チーム全体の生産性向上
C言語との互換性より読みやすさを優先
トレードオフ
C言語に似せる:
- ✅ C言語プログラマーが慣れやすい
- ❌ C言語の問題点を引き継ぐ
- ❌ ツールが作りにくい
新しい構文を採用:
- ✅ 読みやすい
- ✅ 解析しやすい
- ✅ ツールが作りやすい
- ❌ 最初は慣れが必要
Goの選択: 新しい構文
理由:
短期的な学習コスト < 長期的なメンテナンス性
慣れれば自然に
最初(C言語脳)
// 違和感
var p *int // えっ、* が後ろ?
func add(x, y int) int // 返り値が後ろ?
1週間後
// 少し慣れた
var p *int // まあ、読めるかな
func add(x, y int) int // 左から右に読めばいいのね
1ヶ月後
// 完全に自然
var p *int // わかりやすい!
func add(x, y int) int // 読みやすい!
3ヶ月後
// C言語を見ると...
int *p; // あれ、読みにくい...
int add(int x, int y) // 返り値が先頭? わかりにくい...
Goの構文の利点まとめ
読みやすさ
// 一目瞭然
var users []User
var handler func(http.ResponseWriter, *http.Request)
var results chan Result
vs
// 解読が必要
User* users[10];
void (*handler)(Response*, Request*);
// channel? 無理...
書きやすさ
// タイプ数が少ない
x := 42
users := []User{}
result, err := doSomething()
一貫性
// すべて同じパターン
var name 型
func name(引数) 返り値
type name 型定義
ツールフレンドリー
# すべて高速で動作
gofmt
goimports
go vet
gopls (言語サーバー)
まとめ
C言語と異なる理由
- 軽量な構文
- キーワードの削減
- 繰り返しの排除
- セミコロン不要
- 解析の容易さ
- シンボルテーブル不要
- 左から右に読める
- 曖昧さなし
- ツールエコシステム
- 豊富なツール
- 優れたIDEサポート
- 自動化が簡単
実際の影響
| 観点 | C言語 | Go |
|---|---|---|
| 読みやすさ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 書きやすさ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 解析の容易さ | ⭐ | ⭐⭐⭐⭐⭐ |
| ツールの質 | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 学習曲線 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
設計の本質
C言語の互換性 < 長期的な読みやすさ
過去との整合性 < 将来の保守性
慣習 < 明確さ
Goの哲学:
「正しいことを正しく表現する」
「ツールが理解しやすい = 人間も理解しやすい」
「シンプルであることは、簡単であることより良い」
あなたへのアドバイス
- 最初の違和感は正常
- C言語脳からの切り替えが必要
- 1-2週間で慣れる
- 左から右に読む習慣
- Goの宣言は自然な読み順
- すぐに直感的になる
- ツールを活用
- gofmt で整形
- gopls で補完
- 構文を気にする必要が減る
- Goらしく書く
- C言語の慣習にこだわらない
- Goのイディオムを学ぶ
- コミュニティの標準に従う
結論:
Goの構文がC言語と異なるのは、短期的な慣れやすさより、長期的な読みやすさ・保守性・ツールサポートを優先した賢明な設計判断です。最初は違和感があっても、慣れれば「Goの方が読みやすい!」と感じるはずです! 🎯
おわりに
本日は、Go言語のよくある質問について解説しました。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント