Go言語入門:よくある質問 -Changes from C Vol.1-

スポンサーリンク
Go言語入門:よくある質問 -Changes from C Vol.1- ノウハウ
Go言語入門:よくある質問 -Changes from C Vol.1-
この記事は約18分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、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つの解釈が可能:

  1. xyの掛け算(式)
  2. 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言語と異なる理由
  1. 軽量な構文
    • キーワードの削減
    • 繰り返しの排除
    • セミコロン不要
  2. 解析の容易さ
    • シンボルテーブル不要
    • 左から右に読める
    • 曖昧さなし
  3. ツールエコシステム
    • 豊富なツール
    • 優れたIDEサポート
    • 自動化が簡単
実際の影響
観点C言語Go
読みやすさ⭐⭐⭐⭐⭐⭐⭐
書きやすさ⭐⭐⭐⭐⭐⭐⭐⭐
解析の容易さ⭐⭐⭐⭐⭐
ツールの質⭐⭐⭐⭐⭐⭐⭐
学習曲線⭐⭐⭐⭐⭐⭐⭐⭐
設計の本質
C言語の互換性 < 長期的な読みやすさ
過去との整合性 < 将来の保守性
慣習 < 明確さ

Goの哲学:

「正しいことを正しく表現する」
「ツールが理解しやすい = 人間も理解しやすい」
「シンプルであることは、簡単であることより良い」
あなたへのアドバイス
  1. 最初の違和感は正常
    • C言語脳からの切り替えが必要
    • 1-2週間で慣れる
  2. 左から右に読む習慣
    • Goの宣言は自然な読み順
    • すぐに直感的になる
  3. ツールを活用
    • gofmt で整形
    • gopls で補完
    • 構文を気にする必要が減る
  4. Goらしく書く
    • C言語の慣習にこだわらない
    • Goのイディオムを学ぶ
    • コミュニティの標準に従う

結論:

Goの構文がC言語と異なるのは、短期的な慣れやすさより、長期的な読みやすさ・保守性・ツールサポートを優先した賢明な設計判断です。最初は違和感があっても、慣れれば「Goの方が読みやすい!」と感じるはずです! 🎯

おわりに 

本日は、Go言語のよくある質問について解説しました。

よっしー
よっしー

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

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました