ブログに戻る

Go のキーワード一覧:全 25 個の予約語を解説

Go のキーワードは 25 個で、Go 1.0 から変わっていません。各キーワードの役割を実例付きで解説し、キーワードではない any や len などの事前宣言された識別子も紹介します。

Go のキーワード一覧:全 25 個の予約語を解説

Go(Golang)のキーワードは 25 個です。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 です。これらは予約語なので、変数名、関数名、型名には使えません。この一覧は 2012 年の Go 1.0 から変わっておらず、Go 1.27 でも同じ 25 個です(Go spec)。Python のキーワードは 35 個、Java は 51 個、C++ は 80 個を超えます。この少なさは意図的なものです。Go は、言語全体を頭の中に収められるほど小さく設計されています。

要約

  • Go のキーワードはちょうど 25 個です。 一覧は Go の言語仕様に載っており、Go 1.0 から変わっていません。
  • キーワードは 4 つのグループに分かれます。 宣言(package、import、const、var、type、func)、複合型(struct、interface、map、chan)、制御フロー(if から return までの 12 個)、そして並行処理と後片付け(go、select、defer)です。
  • ループのキーワードは 1 つだけです。 for は、回数を決めたループ、while ループ、無限ループをすべて担います。range と組み合わせれば、slice、map、channel、整数、イテレータ関数の反復処理もできます。
  • any、int、string、error、nil、true、len、make はキーワードではありません。 これらは事前宣言された識別子(predeclared identifier)なので、シャドーイングできます。ただし、通常は避けるべきです。
  • ジェネリクスでキーワードは増えていません。 Go 1.18 は型パラメータを導入しましたが、新しい予約語ではなく事前宣言された名前(any、comparable)を使っています。

Go のキーワード一覧

キーワード分類役割
package宣言ファイルが属するパッケージの名前を指定する
import宣言ほかのパッケージを読み込む
const宣言コンパイル時の定数を宣言する
var宣言変数を宣言する
type宣言名前付きの型またはエイリアスを宣言する
func宣言関数、メソッド、関数リテラルを宣言する
struct複合型名前付きのフィールドを 1 つの型にまとめる
interface複合型メソッドの集合(または型制約)を宣言する
map複合型ハッシュマップ型を宣言する
chan複合型channel 型を宣言する
if制御フロー条件が真のときにブロックを実行する
else制御フローif の条件が偽のときにブロックを実行する
for制御フローGo で唯一のループ
range制御フローslice、map、文字列、channel、整数、イテレータを反復処理する
switch制御フロー複数の分岐から 1 つを選ぶ
case制御フローswitch や select の分岐の 1 つ
default制御フローどの case にも一致しないときに実行される分岐
fallthrough制御フローswitch の次の case に処理を続ける
break制御フローfor、switch、select を抜ける
continue制御フローループの次の反復に進む
goto制御フロー同じ関数内のラベルにジャンプする
return制御フロー関数を終了する(値を返すこともできる)
go並行処理関数を新しい goroutine で開始する
select並行処理複数の channel 操作を待つ
defer後片付け関数が戻るときに呼び出しを実行する

Go の宣言キーワードとは?

Go の宣言キーワードは 6 つあり、次の小さな設定ローダーはその 6 つをすべて使っています。

example.gogo
package main

import (
	"fmt"
	"time"
)

const defaultTimeout = 5 * time.Second

var maxRetries = 3

type Config struct {
	Addr    string
	Timeout time.Duration
}

func newConfig(addr string) Config {
	return Config{Addr: addr, Timeout: defaultTimeout}
}

func main() {
	cfg := newConfig(":8080")
	fmt.Println(cfg.Addr, cfg.Timeout, maxRetries)
}

package

Go のファイルはすべて package 句から始まります。同じディレクトリにあるファイルは、すべて同じパッケージ名を共有します。main という名前のパッケージは特別で、実行ファイルとしてビルドされ、その main 関数がプログラムの開始点になります。それ以外の名前では、ほかのパッケージから import されるライブラリとしてビルドされます。命名規則、可視性、_test パッケージについては Go の package キーワードで詳しく解説しています。

import

import は、fmt.Println や http.ListenAndServe のような、ほかのパッケージがエクスポートしている名前をファイル内で使えるようにします。import は括弧でまとめられるほか、名前を変えて読み込んだり(yaml "gopkg.in/yaml.v3")、_ を使って副作用のためだけにパッケージを読み込んだりもできます。使われていない import があるファイルを Go はコンパイルしないので、ファイルが使っていないパッケージを列挙することはありません。エイリアス、ブランク import とドット import、import の循環については Go の import キーワードで詳しく解説しています。

const

const は、タイムアウトやヘッダー名のように、コンパイル時に決まる値を宣言します。定数には型なし(untyped)のものもあります。const maxBodyBytes = 1 << 20 は使われるまで型を持たないので、変換なしで int としても int64 としても使えます。const ブロックの中では、事前宣言された識別子 iota が 0 から数え上げます。Go ではこれで列挙型を書きます。型付き定数と型なし定数、iota、列挙型については Go の const キーワードで詳しく解説しています。

var

var は、明示的な型、初期値、またはその両方を指定して変数を宣言します。変数はすべて、その型のゼロ値(0、""、false、nil)から始まります。関数の中では、多くの Go コードが代わりに短い形式 name := value を使います。それでも、パッケージレベルの宣言や、var buf bytes.Buffer のように意図してゼロ値を使いたい場合は var が適切です。ゼロ値、:=、シャドーイング、パッケージレベル変数については Go の var キーワードで詳しく解説しています。

type

type は、struct、interface、あるいは type UserID int64 のような既存の型に名前を付けた型など、新しい名前付きの型を宣言します。新しい型には独自のメソッドを持たせられます。= を付けると代わりにエイリアスを宣言し(type Handler = http.Handler)、これは同じ型に 2 つ目の名前を付けるだけです。定義型、型エイリアス、メソッド、ジェネリック型については Go の type キーワードで詳しく解説しています。

func

func は関数とメソッドを宣言し、関数リテラル(クロージャ)も作ります。メソッドは名前の前にレシーバーを持ちます(func (s *Server) Start() error)。Go の関数は複数の値を返せるので、エラーはその仕組みで報告されます(func parse(body []byte) (Request, error))。メソッド、クロージャ、可変長引数関数、functional options については Go の func キーワードで詳しく解説しています。

複合型を作る Go のキーワードは?

4 つのキーワードが、ほかの型から新しい型を作ります。配列、slice、ポインタも複合型ですが、キーワードではなく記号([N]T、[]T、*T)を使います。

example.gogo
type Notifier interface {
	Notify(ctx context.Context, msg string) error
}

type SlackNotifier struct {
	Channel string
}

func (s SlackNotifier) Notify(ctx context.Context, msg string) error {
	fmt.Printf("#%s: %s\n", s.Channel, msg)
	return nil
}

func main() {
	var n Notifier = SlackNotifier{Channel: "alerts"}

	statusCounts := map[int]int{200: 41, 500: 2}
	events := make(chan string, 10)

	events <- fmt.Sprintf("%d server errors", statusCounts[500])
	n.Notify(context.Background(), <-events)
}

struct

struct は、名前付きのフィールドを 1 つの型にまとめます。Go ではクラスの代わりになるもので、struct 型にメソッドを付け、振る舞いは継承ではなく struct を別の struct に埋め込むことで再利用します。json:"email" のような struct タグは、encoding/json などのパッケージに各フィールドのエンコード方法を伝えます。struct タグ、値レシーバーとポインターレシーバー、埋め込み、struct の比較とコピーについては Go の struct キーワードで詳しく解説しています。

interface

interface はメソッドの集合を宣言します。型は、そのメソッドを持っているだけで interface を満たします。implements の宣言は不要なので、上の SlackNotifier は何も宣言せずに Notifier になっています。Go 1.18 以降、interface は interface{ ~int | ~float64 } のように、ジェネリクスの型制約も表します。暗黙的な実装、nil の interface、型アサーション、型制約については Go の interface キーワードで詳しく解説しています。

map

map はハッシュマップ型を宣言し、map[KeyType]ValueType と書きます。リテラルか make で作成し、nil の map に書き込むと panic が発生します。存在しないキーを読むとゼロ値が返り、2 値形式の count, ok := statusCounts[404] を使えばキーが存在したかどうかがわかります。言語仕様は反復の順序を定めておらず、ランタイムは意図的に順序をランダムにしています。nil の slice では問題ないのに、nil の map への書き込みで panic が起きる理由は、Go の var と make の違いで解説しています。comma-ok イディオム、反復の順序、セット、並行アクセス、maps パッケージについては Go の map キーワードで詳しく解説しています。

chan

chan は channel 型を宣言します。channel は、goroutine どうしが値を受け渡すためのパイプです。chan<- Job は送信専用、<-chan Job は受信専用で、関数のシグネチャでどちらの方向を使うかを示せます。channel は make で作成し、バッファ付きの channel(make(chan string, 10))は、誰かが受信するまで値を保持します。バッファあり・なしの channel、クローズ、デッドロック、select のパターンについては Go の chan キーワードで詳しく解説しています。

プログラムの制御フローを担う Go のキーワードは?

12 個のキーワードが分岐とループを担います。Go には while、do、try、catch、三項演算子がありません。エラーは普通の戻り値で、if でチェックします。

example.gogo
func parse(body []byte) (Request, error) {
	var req Request
	if err := json.Unmarshal(body, &req); err != nil {
		return Request{}, fmt.Errorf("decode request: %w", err)
	}
	if req.Email == "" {
		return Request{}, errors.New("email is required")
	}
	return req, nil
}

func countErrors(lines []string) int {
	errs := 0
	for _, line := range lines {
		if strings.HasPrefix(line, "#") {
			continue
		}
		if line == "EOF" {
			break
		}
		if strings.Contains(line, "level=error") {
			errs++
		}
	}
	return errs
}

if

if は、条件が真のときにブロックを実行します。条件に括弧は不要ですが、波括弧は必須です。if は短い文から始めることができ、そこで宣言した変数は if とその else 分岐の中でだけ有効です。if err := json.Unmarshal(body, &req); err != nil のように、Go のコードではこの形が頻繁に使われます。bool だけを受け付ける条件、初期化文、シャドーイング、早期リターン、三項演算子がない理由については Go の if キーワードで詳しく解説しています。

else

else は if の条件が偽のときに実行され、else if でさらに条件をつなげられます。Go らしいコードでは、ほかの多くの言語に比べて else を使う場面が少なくなります。if の分岐が return で終わるなら、関数の残りの部分がすでに else の役割を果たします。そのため Go のコードでは、正常系のパスをインデントせずに書き、エラーのときは早めに return します。else if の連鎖、if 文で宣言した変数のスコープ、早期 return、三項演算子がない理由については Go の else キーワードで詳しく解説しています。

for

for は Go で唯一のループのキーワードです。for i := 0; i < n; i++ は古典的な回数指定のループ、for queue.Len() > 0 は while ループ、for { ... } は何かで抜けるまで永遠に繰り返すループです。Go 1.22 以降、go.mod で go 1.22 以降を宣言しているモジュールでは、反復ごとにループ変数のコピーが作られます。そのため、ループ内で開始したクロージャや goroutine が 1 つの変数を共有することはなくなりました。3 つのループの形、range、Go 1.22 のループ変数、ラベル、ループでよくある間違いについては Go の for キーワードで詳しく解説しています。

range

range は、for ループでコレクションを反復処理させます。slice と配列ではインデックスと値、map ではキーと値、文字列ではバイトオフセットと rune、channel では受信した値が得られます。Go 1.22 では整数に対する range が追加され(for attempt := range 3 は 0、1、2 で実行されます)、Go 1.23 では maps.Keys(m) が返すようなイテレータ関数に対する range が追加されました。

switch

switch は複数の分岐から 1 つを選びます。C や Java と違い、Go の switch は一致した case の後で止まるので、各 case の最後に break を書く必要はありません。式のない switch は if/else if の連鎖をすっきり書いたものとして使えます。型 switch(switch v := x.(type))は、interface の値の動的な型で分岐します。

case

case は switch や select の分岐の 1 つを示します。1 つの case に複数の値を並べることもできます(case "GET", "HEAD":)。switch では case が上から順にチェックされ、最初に一致したものが選ばれます。

default

default は、どの case にも一致しないときに実行される分岐です。switch のどこに置いても構いませんが、慣習では最後に置きます。select では、default の case があると文全体がブロックしなくなります。準備のできた channel がなければ、すぐに default が実行されます。

fallthrough

fallthrough は、次の case の条件をチェックせずに、その case の本体へ switch の処理を続けさせます。実際のコードではまれで、Go の switch が C のように振る舞う唯一の場所です。段階的な権限は、これが読みやすく書けるケースの 1 つです。

example.gogo
func permissions(role string) []string {
	var perms []string
	switch role {
	case "admin":
		perms = append(perms, "delete")
		fallthrough
	case "editor":
		perms = append(perms, "write")
		fallthrough
	case "viewer":
		perms = append(perms, "read")
	default:
		return nil
	}
	return perms
}

permissions("editor") は [write read] を返し、permissions("admin") は [delete write read] を返します。fallthrough は case の最後の文でなければならず、型 switch では使えません。

break

break は、最も内側の for、switch、select を抜けます。最後の点はよくある落とし穴です。select の中の break は select を抜けるだけで、それを囲むループからは抜けません。ループを抜けるには、ループにラベルを付けて break loop と書きます。

example.gogo
func drain(ctx context.Context, events <-chan string) {
loop:
	for {
		select {
		case ev, ok := <-events:
			if !ok {
				break loop
			}
			fmt.Println("event:", ev)
		case <-ctx.Done():
			break loop
		}
	}
	fmt.Println("drained")
}

continue

continue は現在の反復の残りを飛ばして、次の反復を始めます。上の countErrors では、コメント行を飛ばすのに使っています。break と同じくラベルを付けられるので、入れ子になったループの内側から外側のループを continue できます。

goto

goto は同じ関数内のラベルにジャンプします。ブロックの中へのジャンプや、変数宣言を飛び越えるジャンプはできないので、C の goto が抱える問題のほとんどは起こりません。自分で書くことはめったになく、主に生成されたコードや、標準ライブラリの一部の低レベルな箇所で見かけます。LevelUpGo 自身のバックエンドには、テストとシードデータを除いて約 30,000 行の Go コードがあります。そこで goto と fallthrough は一度も使われていません。一方で、range ループは 250 以上、defer の呼び出しは 40 以上あります。

return

return は関数を終了し、その結果を返します。Go の関数は結果と error の 2 つの値を返すことが多く、呼び出し側はすぐにエラーをチェックします。名前付きの戻り値パラメータを使うと、値を指定しない return でその時点の値が返されます。ただし、多くのスタイルガイドではこの書き方を短い関数に限っています。

並行処理と後片付けを担う Go のキーワードは?

最後の 3 つのキーワードは go、select、defer です。chan と合わせて、Go のサーバーが処理を並行に実行し、その後片付けをするために使うものです。

example.gogo
func startWorkers(ctx context.Context, n int, jobs <-chan string, results chan<- []byte) {
	for range n {
		go worker(ctx, jobs, results)
	}
}

func worker(ctx context.Context, jobs <-chan string, results chan<- []byte) {
	for {
		select {
		case url, ok := <-jobs:
			if !ok {
				return
			}
			body, err := fetch(ctx, url)
			if err != nil {
				log.Printf("fetch %s: %v", url, err)
				continue
			}
			results <- body
		case <-ctx.Done():
			return
		}
	}
}

func fetch(ctx context.Context, url string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	return io.ReadAll(resp.Body)
}

go

go は関数呼び出しを新しい goroutine で開始し、その完了を待たずに先へ進みます。上の startWorkers はループの中で go worker(ctx, jobs, results) を実行しており、これでワーカープールができます。goroutine は数 KB のスタックで始まるので、1 台のサーバーで数十万個を動かせます。go 文は関数の戻り値を破棄するので、結果は channel を通じて受け取り、処理の完了は sync.WaitGroup や errgroup で待ちます。

select

select は複数の channel 操作を待ち、最初に準備ができたものを実行します。同時に複数の準備ができている場合は、ランダムに 1 つを選びます。上のワーカーは、次のジョブを受け取るか、context がキャンセルされたら停止するために select を使っています。ブロックする処理にタイムアウトやキャンセルを加えるとき、Go のコードでは channel を ctx.Done() や time.After と組み合わせます。

defer

defer は、囲んでいる関数が戻るときに呼び出しが実行されるよう予約します。正常に戻った場合でも、panic した場合でも実行されます。これにより、後片付けのコードをそれが必要なコードのすぐそばに置けます(defer resp.Body.Close()、defer mu.Unlock()、defer cancel())。defer された呼び出しは後入れ先出し(LIFO)の順に実行され、その引数は最後ではなく defer の行が実行された時点で評価されます。ループ内の defer は関数全体が戻るときにしか実行されません。これは早めに知っておきたい Go のよくある間違いの 1 つです。

Go でキーワードではないものは?

組み込みに見える名前の多くは、キーワードではなく事前宣言された識別子です。Go の言語仕様では、これらはすべてのパッケージを囲むユニバースブロックに定義されています(Go spec)。

種類事前宣言された識別子
型any, bool, byte, comparable, complex64, complex128, error, float32, float64, int, int8, int16, int32, int64, rune, string, uint, uint8, uint16, uint32, uint64, uintptr
定数true, false, iota
ゼロ値nil
関数append, cap, clear, close, complex, copy, delete, imag, len, make, max, min, new, panic, print, println, real, recover

この違いが重要なのは、事前宣言された識別子は内側のスコープで再宣言できるからです。次のコードはコンパイルできます。

example.gogo
func logAddrChange(cur, next Config) {
	old, new := cur.Addr, next.Addr
	if old != new {
		log.Printf("addr changed from %s to %s", old, new)
	}
}

func countLines(lines []string) int {
	len := len(lines)
	return len
}

古い値と新しい値を比べるコードで変数に new と名付けるのはよくあることで、問題なく動きます。しかし len := len(lines) の後では、その関数内で len(...) を呼び出すと invalid operation: cannot call len (variable of type int): int is not a function というエラーになります。キーワードはこのような使い方がまったくできません。var := 3 は syntax error: unexpected :=, expected name で失敗します。golangci-lint の predeclared のような linter は、シャドーイングされた組み込みの名前を検出します。countLines のようなコードはコンパイルできても、次に読む人を混乱させるからです。

これらの名前をキーワードの一覧に入れないことで、Go は既存のプログラムを壊さずに言語を拡張できます。any(Go 1.18)や min、max、clear(Go 1.21)は事前宣言された識別子として追加されたので、すでにこれらの名前を変数として使っていたコードもそのままコンパイルできました。

LevelUpGo で学ぶ

LevelUpGo では、ブラウザ上で本物の Go コードを実行する演習を通じて Go を学べます。Go Basics コースでは、var や const から switch や for まで、宣言と制御フローのキーワードを扱います。Concurrency Fundamentals では go、chan、select、defer を扱い、Simplification では if の連鎖より switch のほうが読みやすくなる場面を紹介します。Training Ground には、コースの外でこれらを練習できる短い単独の演習があります。

よくある質問

Go のキーワードはいくつありますか?

Go のキーワードは 25 個です。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 です。この一覧は Go の言語仕様で定義されており、Go 1.0 から変わっていません。

Go で any はキーワードですか?

いいえ。any は事前宣言された識別子で、Go 1.18 で追加された interface{} のエイリアスです。int、string、error、nil、true、false、iota、そして len、make、append などの組み込み関数も同じです。これらは内側のスコープで再宣言できますが、そうすると linter が警告を出します。

Go のキーワードを変数名に使えますか?

いいえ。キーワードは予約語なので、var := 3 や func type() は構文エラーになります。よくある回避策は、少し変えた短い名前を使うことです。Go のコードでは、type の代わりに typ や kind、package の代わりに pkg、func の代わりに fn がよく使われます。

Go 1.0 以降、キーワードは追加されましたか?

いいえ。Go 1.27 の 25 個のキーワードは、2012 年 3 月にリリースされた Go 1.0 と同じ 25 個です。ジェネリクス、整数に対する range、イテレータ関数、そして Go 1.27 のジェネリックメソッドは、いずれも新しいキーワードなしで導入されました。Go 1 の互換性の約束があるため、新しいキーワードが追加される可能性は低いと言えます。新しい予約語を追加すると、それを名前として使っているプログラムがすべて壊れてしまうからです。

なぜ Go のキーワードはこれほど少ないのですか?

設計者たちがそう意図したからです。Go FAQ には、設計者たちが「煩雑さと複雑さを減らそうとした」こと、そして Go の「構文はすっきりしていて、キーワードも少ない」ことが書かれています。ループのキーワードが 3 つではなく 1 つなのも、エラーを try/catch ではなく戻り値で扱うのもそのためです。その結果、Go は短期間で習得でき、異なるコードベースの Go コードもほぼ同じように読めます。

出典

シニアエンジニアのように Go を書く

ブラウザで学べるインタラクティブなレッスン。最初のレッスンは無料です。

無料レッスンを試すまたは無料アカウントを作成