Go言語(Golang)とは、Googleが設計した「シンプルな文法・速いコンパイル・軽い並行処理」が特長の静的型付け言語です。 クラスや例外がない代わりに、構造体とインターフェース、戻り値のエラー、goroutine といった少数の道具を組み合わせて書きます。文法が小さいので、他言語の経験があれば1〜2日で読み書きできるようになります。

この記事は、インストールから go mod init → go run → go build → go test の流れ、変数・スライス・マップ・構造体・インターフェース・ジェネリクス・イテレータ関数(range over func)・エラー・defer・パッケージ構成・gofmt/go vet までを、すべてコピペできるコードと Go 1.27.1(linux/amd64)で実際に実行した結果つきで解説します。Java・Python・JavaScript との違いの早見表、標準コマンドの一覧、実際のコンパイルエラー文つきの「よくあるつまずき」も載せました。エラー処理と並行処理はここでは概要に留め、詳しくは別記事で扱います。

Go言語の基本文法の早見表

やりたいこと 書き方 補足
変数を宣言する var n int / n := 10 := は関数の中だけ。型は推論される
定数を作る const Pi = 3.14 / iota で連番 型なし定数は使う場所に合わせて型が決まる
型を変換する float64(n) / strconv.Atoi(s) 暗黙の型変換はない。必ず明示する
条件分岐 if x := f(); x > 0 { } 条件の前に初期化文を書ける。括弧は不要
ループ for i := 0; i < n; i++ / for cond / for / for i := range 10 ループ構文は for だけ。range 10 は 1.22〜
分岐(switch) switch v { case 1, 2: ... default: ... } break 不要。式なし switch も可
可変長の配列 s := []int{1, 2} / s = append(s, 3) スライス。len・cap・s[low:high]
連想配列 m := map[string]int{} / v, ok := m["k"] make かリテラルで初期化してから書き込む
構造体とメソッド type T struct{...} / func (t *T) M() {} ポインタレシーバで書き換え可能
インターフェース type S interface { Area() float64 } メソッドを持てば自動で満たす(宣言不要)
ジェネリクス func Map[T, U any](xs []T, f func(T) U) []U 1.18〜。1.27 からメソッドも型パラメータを持てる
イテレータ func Seq() iter.Seq[int] を for v := range Seq() 1.23〜。slices.Values・maps.Keys も返す
並べ替え・検索 slices.Sort(s) / slices.Contains(s, v) / min(a, b) slices・maps パッケージと組み込み min/max/clear
エラーを返す return 0, fmt.Errorf("失敗: %w", err) 例外はない。最後の戻り値が error
後始末 defer f.Close() 関数を抜けるとき LIFO で実行
公開/非公開 大文字始まり=公開、小文字始まり=パッケージ内 internal/ 配下は外部モジュールから読めない
整形・静的チェック gofmt -w . / go vet ./... フォーマットは1種類に統一される
実行・ビルド・テスト go run . / go build -o app . / go test ./... go mod init でモジュールを作ってから

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Go言語入門|1.27対応の基本文法を実行結果つきで解説」

Go言語の特徴と、Java・Python・JavaScriptとの違い

Goは「書き方が1通りに収束するように設計された言語」です。 継承・例外・三項演算子・ジェネリクスの高度な機能を意図的に持たず、その代わりにコンパイラとツール(gofmt・go vet)が強く支えます。他言語との違いを前向きに整理すると次のとおりです。

観点 Go Java Python JavaScript / TypeScript
型付け 静的・コンパイル時に検査 静的 動的(型ヒントは任意) 動的(TSで静的)
実行形態 単一バイナリにコンパイル JVM 上で実行 インタプリタ ランタイム(Node/ブラウザ)
クラス ない。構造体+メソッド+埋め込みで合成 ある(継承) ある(多重継承) ある(プロトタイプ)
インターフェース 暗黙(メソッドを持てば満たす) 明示(implements) ダックタイピング TSは構造的型付け
例外 ない。戻り値で error を返す try/catch try/except try/catch
null nil(ポインタ・スライス・マップ等のゼロ値) null None null/undefined
ジェネリクス 1.18〜(型パラメータ+制約) ある 不要(動的) TSにある
並行処理 goroutine+channel(言語組み込み) スレッド/Executor asyncio/threading Promise/async
未使用変数・import コンパイルエラー 警告 何も起きない 何も起きない(lintで検出)
メモリ管理 GC あり、ポインタはあるが演算は不可 GC GC(参照カウント) GC
フォーマッタ gofmt が標準(設定なし) 各種 black 等 Prettier 等

「クラスがない」「例外がない」は不便に見えて、実際にはコードの読みやすさに直結しています。 どの Go コードを開いても、エラーは if err != nil で処理され、型の関係は構造体の埋め込みとインターフェースで表現され、書式は gofmt で揃っています。チーム開発でレビューが速くなるのはこのためです。

インストールから go run・go build・go test までの流れ

1. インストールと確認

公式サイト(go.dev/dl)のインストーラを使うか、macOS なら brew install go、Windows なら winget install GoLang.Go で入ります。確認は次のコマンドです。

$ go version
go version go1.27.1 linux/amd64

$ go env GOOS GOARCH GOVERSION GOPATH
linux
amd64
go1.27.1
/root/go

GOPATH の設定は今は不要です。 Go 1.11 以降はモジュールモードが標準で、プロジェクトはどのフォルダに置いても動きます。

2. go mod init でモジュールを作る

Go のプロジェクト単位は「モジュール」で、go.mod ファイルで定義します。 モジュール名は example.com/hello のように「ドメイン/名前」の形が慣例です(公開しないなら実在しなくて構いません)。

$ mkdir hello && cd hello
$ go mod init example.com/hello
go: creating new go.mod: module example.com/hello
go: to add module requirements and sums:
    go mod tidy

$ cat go.mod
module example.com/hello

go 1.27.1

3. go run と go build

main.go を作ります。実行できるプログラムは package main と func main() が必須です。

package main

import "fmt"

func main() {
    fmt.Println("Hello, Go 1.27!")
}
$ go run .          # コンパイルして即実行(バイナリは残らない)
Hello, Go 1.27!

$ go build -o hello .   # 単一バイナリを生成
$ ./hello
Hello, Go 1.27!

$ GOOS=windows GOARCH=amd64 go build -o hello.exe .   # クロスコンパイルも環境変数だけ

生成された hello は約 2.3MB の単一ファイルで、Go がインストールされていないマシンでもそのまま動きます。

4. go test でテストを書く

テストは _test.go で終わるファイルに TestXxx(t *testing.T) を書くだけで、追加ライブラリは不要です。Go では「テーブル駆動テスト(入力と期待値の表をループで回す)」が定番です。

// greet.go
package main

import "fmt"

// Greet は名前を受け取り、あいさつ文を返します。
func Greet(name string) string {
    if name == "" {
        name = "World"
    }
    return fmt.Sprintf("Hello, %s!", name)
}
// greet_test.go
package main

import "testing"

func TestGreet(t *testing.T) {
    tests := []struct {
        name string
        in   string
        want string
    }{
        {"名前あり", "Go", "Hello, Go!"},
        {"空文字", "", "Hello, World!"},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Greet(tt.in); got != tt.want {
                t.Errorf("Greet(%q) = %q, want %q", tt.in, got, tt.want)
            }
        })
    }
}
$ go test ./...
ok      example.com/hello   0.003s

$ go test -v ./...
=== RUN   TestGreet
=== RUN   TestGreet/名前あり
=== RUN   TestGreet/空文字
--- PASS: TestGreet (0.00s)
    --- PASS: TestGreet/名前あり (0.00s)
    --- PASS: TestGreet/空文字 (0.00s)
PASS
ok      example.com/hello   0.003s

$ go test -run 'TestGreet/空文字' -v    # サブテストを名前で絞り込み

Goの標準コマンド早見表

コマンド 役割 よく使う形
go run コンパイルして実行 go run . / go run ./cmd/app
go build バイナリを生成 go build -o bin/app ./cmd/app
go test テスト実行 go test ./... / -v / -run 名前 / -race / -cover
go mod init モジュール作成 go mod init example.com/app
go mod tidy 依存の追加・不要分の削除 go mod tidy(go.mod/go.sum を整える)
go get 依存の追加・更新 go get example.com/lib@v1.2.3
go fmt / gofmt 整形 gofmt -l .(未整形ファイル一覧)/ gofmt -w .
go vet 静的チェック go vet ./...(Printf の引数不一致など)
go doc ドキュメント表示 go doc strings.Cut(関数の説明)/ go doc strings.ExampleCut(1.27〜:例の本文も表示)
go env 環境変数の表示 go env GOOS GOARCH GOMODCACHE
go list パッケージ一覧 go list ./...
go install ツールをインストール go install golang.org/x/tools/cmd/goimports@latest
go clean キャッシュ削除 go clean -cache -modcache

変数・定数・型

Go の変数は var か := で宣言し、宣言だけで必ず「ゼロ値」(0・""・false・nil)が入ります。 := は「宣言と代入を同時に行う」演算子で、関数の中でしか使えません。

package main

import "fmt"

const Pi = 3.14159 // 型なし定数

type Weekday int

const (
    Sunday Weekday = iota // 0
    Monday                // 1
    Tuesday               // 2
)

func main() {
    var count int          // ゼロ値 0
    var name string        // ゼロ値 ""
    var ok bool            // ゼロ値 false
    var price float64 = 1980
    total := 3             // 型推論(int)。関数の中だけで使える
    fmt.Println(count, name == "", ok, price, total)

    // 型変換は明示的に書く(暗黙変換はない)
    var n int = 7
    var f float64 = float64(n) / 2
    fmt.Println(f)

    // 複数代入と入れ替え
    a, b := 1, 2
    a, b = b, a
    fmt.Println(a, b)

    // 文字列は不変。len はバイト数、文字数は []rune に変換して数える
    s := "Go言語"
    fmt.Println(len(s), len([]rune(s)))
    for i, r := range s {
        fmt.Printf("%d:%c ", i, r)
    }
    fmt.Println()

    fmt.Println(Pi*2, Monday, Tuesday)
    fmt.Printf("%T %v %q %5.2f\n", total, total, s, Pi)
}
$ go run .
0 true false 1980 3
3.5
2 1
8 4
0:G 1:o 2:言 5:語 
6.28318 1 2
int 3 "Go言語"  3.14
型の分類 主な型 ゼロ値
整数 int int64 uint8(byte)int32(rune) 0
浮動小数 float64 float32 0
文字列・論理 string bool "" false
複合 配列 [3]int、構造体 各要素のゼロ値
参照的な型 スライス []T、マップ map[K]V、ポインタ *T、関数、チャネル、インターフェース nil

fmt.Printf の書式は %v(値)、%T(型)、%q(クォート付き文字列)、%d・%s・%.2f を覚えれば十分です。文字列の len はバイト数なので、日本語の文字数は len([]rune(s)) か utf8.RuneCountInString(s) で数えます。

if・for・switch(range over int も)

Go のループは for だけで、while も do-while もありません。 if と switch は条件の前に初期化文を書けます。

package main

import "fmt"

func main() {
    // if は条件の前に「初期化文」を書ける(スコープが if 内に限定される)
    if n := 42; n%2 == 0 {
        fmt.Println("偶数:", n)
    } else {
        fmt.Println("奇数:", n)
    }

    // for は 3 通り(Go にはループ構文が for しかない)
    for i := 0; i < 3; i++ { // ① C 言語スタイル
        fmt.Print(i, " ")
    }
    fmt.Println()

    i := 0
    for i < 3 { // ② while の代わり
        i++
    }
    fmt.Println("i =", i)

    for { // ③ 無限ループ(break で抜ける)
        i++
        if i >= 5 {
            break
        }
    }
    fmt.Println("i =", i)

    // Go 1.22 から整数を range できる(0 〜 n-1)
    for i := range 3 {
        fmt.Print("range:", i, " ")
    }
    fmt.Println()

    // switch は break 不要(自動で抜ける)。複数の値・条件式も書ける
    switch day := "sat"; day {
    case "sat", "sun":
        fmt.Println("週末")
    default:
        fmt.Println("平日")
    }
    score := 85
    switch { // 式なし switch = if-else の連鎖
    case score >= 90:
        fmt.Println("A")
    case score >= 80:
        fmt.Println("B")
    default:
        fmt.Println("C")
    }
}
$ go run .
偶数: 42
0 1 2 
i = 3
i = 5
range:0 range:1 range:2 
週末
B

for i := range 3 は Go 1.22 で追加された書き方で、for i := 0; i < 3; i++ と同じです。同じ 1.22 で「ループ変数は反復ごとに新しく作られる」仕様になり、goroutine やクロージャでループ変数を使うときの定番のつまずきが解消されました。

スライスとマップ

スライスは「配列の一部を指すビュー」で、append で伸ばせます。マップは make かリテラルで初期化してから使います。

package main

import "fmt"

func main() {
    // 配列は固定長、スライスは可変長(実務ではほぼスライスを使う)
    arr := [3]int{1, 2, 3}
    nums := []int{10, 20, 30}
    nums = append(nums, 40, 50)
    fmt.Println(arr, nums, len(nums), cap(nums))

    // スライス式 s[low:high] は low 以上 high 未満
    fmt.Println(nums[1:3], nums[:2], nums[3:])

    // make で長さ・容量を指定して作る
    buf := make([]string, 0, 4)
    buf = append(buf, "a", "b")
    fmt.Println(buf, len(buf), cap(buf))

    // 注意:スライス式で作った部分スライスは元と同じ配列を共有する
    sub := nums[:2]
    sub[0] = 999
    fmt.Println("nums:", nums, "sub:", sub)

    // マップ:宣言は make か リテラルで。存在確認は「カンマ ok」
    stock := map[string]int{"apple": 3, "banana": 0}
    stock["cherry"] = 12
    v, ok := stock["banana"]
    fmt.Println(v, ok)
    v, ok = stock["melon"] // ない場合はゼロ値と false
    fmt.Println(v, ok)
    delete(stock, "apple")
    fmt.Println(len(stock), stock)

    // range:スライスは (index, value)、マップは (key, value)。順序は保証されない
    for i, n := range nums {
        fmt.Print(i, "=", n, " ")
    }
    fmt.Println()
    for k, v := range stock {
        fmt.Print(k, ":", v, " ")
    }
    fmt.Println()
}
$ go run .
[1 2 3] [10 20 30 40 50] 5 6
[20 30] [10 20] [40 50]
[a b] 2 4
nums: [999 20 30 40 50] sub: [999 20]
0 true
0 false
2 map[banana:0 cherry:12]
0=999 1=20 2=30 3=40 4=50 
banana:0 cherry:12 
操作 スライス マップ
作る []int{} / make([]int, len, cap) map[string]int{} / make(map[string]int)
追加 s = append(s, v)(戻り値を必ず受ける) m[k] = v
取り出し s[i](範囲外は panic) v, ok := m[k](ないときはゼロ値)
削除 slices.Delete(s, i, j) delete(m, k)
長さ len(s) / cap(s) len(m)
存在確認 slices.Contains(s, v) _, ok := m[k]
コピー slices.Clone(s) / copy(dst, src) maps.Clone(m)
順序 挿入順 保証されない(slices.Sorted(maps.Keys(m)) で並べる)

nums[:2] のような部分スライスは元のスライスと同じ配列を共有します。 上の実行結果で sub[0] = 999 が nums にも反映されているのはこのためで、独立させたいときは slices.Clone を使います(後述の「よくあるつまずき」も参照)。

構造体・メソッド・インターフェース

Go にクラスはありませんが、「構造体に関数(メソッド)を付ける」「インターフェースはメソッドを持てば自動で満たす」「埋め込みで合成する」の3つでオブジェクト指向的な設計はすべて表現できます。

package main

import "fmt"

// 構造体:フィールドをまとめた型。クラスはないが、型にメソッドを付けられる
type Item struct {
    Name  string
    Price int
    Qty   int
}

// 値レシーバ:コピーに対して動く(読み取り専用の処理向き)
func (it Item) Subtotal() int {
    return it.Price * it.Qty
}

// ポインタレシーバ:呼び出し元の値を書き換えられる
func (it *Item) AddQty(n int) {
    it.Qty += n
}

// fmt.Stringer インターフェースを満たすと Println の表示を変えられる
func (it Item) String() string {
    return fmt.Sprintf("%s x%d = %d円", it.Name, it.Qty, it.Subtotal())
}

// インターフェース:メソッドの集合。implements の宣言は不要(構造的型付け)
type Shape interface {
    Area() float64
}

type Rect struct{ W, H float64 }
type Square struct{ S float64 }

func (r Rect) Area() float64   { return r.W * r.H }
func (s Square) Area() float64 { return s.S * s.S }

// 埋め込み:継承ではなく「合成」でメソッドを引き継ぐ
type Logger struct{ Prefix string }

func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) }

type Service struct {
    Logger // 埋め込み
    Name   string
}

func main() {
    it := Item{Name: "ノート", Price: 120, Qty: 2}
    it.AddQty(3) // ポインタレシーバでも Go が自動で &it にしてくれる
    fmt.Println(it)

    p := &it // ポインタ経由でも . でアクセスできる
    fmt.Println(p.Subtotal())

    shapes := []Shape{Rect{3, 4}, Square{5}}
    for _, s := range shapes {
        fmt.Printf("%T の面積 = %.1f\n", s, s.Area())
    }

    // 型アサーションと型 switch
    var s Shape = Rect{2, 2}
    if r, ok := s.(Rect); ok {
        fmt.Println("Rect の幅:", r.W)
    }
    switch v := s.(type) {
    case Rect:
        fmt.Println("Rect", v.W, v.H)
    case Square:
        fmt.Println("Square", v.S)
    }

    svc := Service{Logger: Logger{Prefix: "[svc]"}, Name: "orders"}
    svc.Log("started " + svc.Name) // 埋め込んだ Logger のメソッドをそのまま呼べる
}
$ go run .
ノート x5 = 600円
600
main.Rect の面積 = 12.0
main.Square の面積 = 25.0
Rect の幅: 2
Rect 2 2
[svc] started orders
判断 値レシーバ func (t T) ポインタレシーバ func (t *T)
フィールドを書き換える できない(コピーに書く) できる
大きな構造体 毎回コピーされる コピーされない
迷ったら 小さくて不変な型 それ以外(1つの型で混在させない)

インターフェースは「小さく」定義するのが Go 流です。 標準ライブラリの io.Reader(Read の1メソッド)や fmt.Stringer(String の1メソッド)がその代表で、メソッドが1つなら多くの型が自然に満たせます。

ジェネリクス(型パラメータ)

ジェネリクスは Go 1.18 で入った「型を引数にとる仕組み」で、[T any] のように角括弧で型パラメータを宣言します。 使える型を絞る「制約」は interface で書き、cmp.Ordered(大小比較できる型)など標準の制約も使えます。Go 1.27 からはメソッド自身も型パラメータを持てるようになりました(公式リリースノートの言語変更で確認)。

package main

import (
    "cmp"
    "fmt"
    "strings"
)

// 型パラメータ [T any] で、どの型でも使える関数を書ける(Go 1.18〜)
func Map[T, U any](xs []T, f func(T) U) []U {
    out := make([]U, 0, len(xs))
    for _, x := range xs {
        out = append(out, f(x))
    }
    return out
}

// 制約(constraint):使える型を制限する。~int は「int を基底型とする型も含む」
type Number interface {
    ~int | ~int64 | ~float64
}

func Sum[T Number](xs []T) T {
    var total T
    for _, x := range xs {
        total += x
    }
    return total
}

// cmp.Ordered は < で比較できる型(整数・浮動小数・文字列)
func MaxOf[T cmp.Ordered](a, b T) T {
    if a > b {
        return a
    }
    return b
}

// ジェネリック型:型パラメータを持つ構造体
type Stack[T any] struct{ items []T }

func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    v := s.items[len(s.items)-1]
    s.items = s.items[:len(s.items)-1]
    return v, true
}

// Go 1.27 から:メソッド自身が型パラメータを持てる(ジェネリックメソッド)
type Labels struct{ prefix string }

func (l Labels) Format[T any](v T) string {
    return fmt.Sprintf("%s%v", l.prefix, v)
}

func main() {
    words := []string{"go", "gopher"}
    fmt.Println(Map(words, strings.ToUpper))     // 型引数は推論される
    fmt.Println(Map(words, func(s string) int { return len(s) }))
    fmt.Println(Sum([]int{1, 2, 3}), Sum([]float64{0.5, 0.25}))
    fmt.Println(MaxOf(3, 7), MaxOf("apple", "banana"))

    var st Stack[string]
    st.Push("a")
    st.Push("b")
    v, ok := st.Pop()
    fmt.Println(v, ok)

    l := Labels{prefix: "#"}
    fmt.Println(l.Format(42), l.Format("tag"))
}
$ go run .
[GO GOPHER]
[2 6]
6 0.75
7 banana
b true
#42 #tag

go.mod の go ディレクティブが 1.26 以前だと、ジェネリックメソッドの行で次のエラーになります(-gcflags=-lang=go1.26 で確認)。

generics/main.go:56:24: generic method requires go1.27 or later (-lang was set to go1.26; check go.mod)

イテレータ関数(range over func)と slices・maps・min・max

Go 1.23 から、「yield 関数を受け取る関数」を for range で回せます(イテレータ関数)。 標準ライブラリの slices.Values・maps.Keys などもこの形(iter.Seq)を返すので、slices.Sorted(maps.Keys(m)) のように組み合わせられます。

package main

import (
    "fmt"
    "iter"
    "maps"
    "slices"
)

// イテレータ関数(Go 1.23〜):yield を呼ぶ関数を返すと for range で回せる
// iter.Seq[V] = func(yield func(V) bool)
func Countdown(from int) iter.Seq[int] {
    return func(yield func(int) bool) {
        for i := from; i >= 1; i-- {
            if !yield(i) { // 呼び出し側が break したら false が返る
                return
            }
        }
    }
}

func main() {
    for n := range Countdown(3) {
        fmt.Print(n, " ")
    }
    fmt.Println()

    // 標準ライブラリもイテレータを返す
    stock := map[string]int{"pen": 3, "note": 5, "clip": 1}
    keys := slices.Sorted(maps.Keys(stock)) // maps.Keys は iter.Seq[string]
    fmt.Println(keys)
    for v := range slices.Values([]int{7, 8}) {
        fmt.Print(v, " ")
    }
    fmt.Println()

    // slices.Collect でイテレータをスライスに戻す
    evens := slices.Collect(func(yield func(int) bool) {
        for i := range 10 {
            if i%2 == 0 && !yield(i) {
                return
            }
        }
    })
    fmt.Println(evens)
}
$ go run .
3 2 1 
[clip note pen]
7 8 
[0 2 4 6 8]

(key, value) の2値を返すイテレータは iter.Seq2[K, V] で、maps.All(m) や slices.All(s) がこの形を返します。 並べ替え・検索・最大最小は、自分でループを書かずに slices・maps パッケージと組み込み関数 min・max・clear を使います。

package main

import (
    "fmt"
    "maps"
    "slices"
    "strings"
)

type Task struct {
    Title    string
    Priority int
}

func main() {
    nums := []int{5, 2, 9, 1}
    slices.Sort(nums) // 破壊的にソート
    fmt.Println(nums, slices.Contains(nums, 9), slices.Index(nums, 2))
    fmt.Println(slices.Max(nums), slices.Min(nums))

    // 組み込み関数 min / max(Go 1.21〜)、clear(1.21〜)
    fmt.Println(min(3, 1, 2), max(2.5, 7.0), min("b", "a"))

    tasks := []Task{{"設計", 2}, {"実装", 1}, {"レビュー", 3}}
    slices.SortFunc(tasks, func(a, b Task) int { return a.Priority - b.Priority })
    fmt.Println(tasks)

    m := map[string]int{"a": 1, "b": 2}
    m2 := maps.Clone(m)
    m2["c"] = 3
    fmt.Println(len(m), len(m2), maps.Equal(m, m2))
    clear(m2)
    fmt.Println(len(m2))

    // strings もよく使う。Go 1.27 で CutLast が追加
    before, after, ok := strings.CutLast("path/to/file.go", "/")
    fmt.Println(before, after, ok)
}
$ go run .
[1 2 5 9] true 1
9 1
1 7 a
[{実装 1} {設計 2} {レビュー 3}]
2 3 false
0
path/to file.go true
やりたいこと 関数
ソート slices.Sort(s) / slices.SortFunc(s, cmp) / slices.Sorted(seq)
検索 slices.Contains / slices.Index / slices.IndexFunc / slices.BinarySearch
最大・最小 slices.Max / slices.Min / 組み込み max(a, b, ...) min(...)
複製・比較 slices.Clone / slices.Equal / maps.Clone / maps.Equal
挿入・削除 slices.Insert(s, i, v...) / slices.Delete(s, i, j) / slices.Compact
マップのキー・値 maps.Keys(m) / maps.Values(m)(イテレータ。slices.Collect でスライスに)
全消去 clear(s)(要素をゼロ値に)/ clear(m)(全削除)

エラーと defer(概要)

Go には例外がなく、失敗しうる関数は最後の戻り値として error を返します。呼び出し側は if err != nil で必ず確認します。 defer は「関数を抜けるときに実行する処理」の予約で、ファイルのクローズなどの後始末に使います。

package main

import (
    "errors"
    "fmt"
    "os"
    "strconv"
)

// Go には例外がない。失敗しうる関数は「最後の戻り値」で error を返す
func parseQty(s string) (int, error) {
    n, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("数量の解析に失敗: %w", err) // %w で元のエラーを包む
    }
    if n < 0 {
        return 0, errors.New("数量は 0 以上にしてください")
    }
    return n, nil
}

func readConfig(path string) (err error) {
    f, err := os.Open(path)
    if err != nil {
        return err
    }
    defer f.Close() // 関数を抜けるとき必ず実行される(return の後、panic 時も)
    fmt.Println("opened:", f.Name())
    return nil
}

func main() {
    for _, s := range []string{"3", "abc", "-1"} {
        n, err := parseQty(s)
        if err != nil {
            fmt.Println("エラー:", err)
            continue
        }
        fmt.Println("数量:", n)
    }

    // defer は後入れ先出し(LIFO)で実行される
    for i := range 3 {
        defer fmt.Println("defer", i)
    }

    if err := readConfig("/no/such/file.toml"); err != nil {
        fmt.Println("readConfig:", err)
        // os.ErrNotExist かどうかは errors.Is で判定できる
        fmt.Println("存在しない:", errors.Is(err, os.ErrNotExist))
    }
    fmt.Println("main の最後")
}
$ go run .
数量: 3
エラー: 数量の解析に失敗: strconv.Atoi: parsing "abc": invalid syntax
エラー: 数量は 0 以上にしてください
readConfig: open /no/such/file.toml: no such file or directory
存在しない: true
main の最後
defer 2
defer 1
defer 0

fmt.Errorf の %w は「元のエラーを包む」書式で、errors.Is/errors.As で元のエラーを判定できるようになります。 panic は「プログラムを続行できない状態」専用で、通常のエラーには使いません。エラーのラップ・判定・errors.Join・panic/recover の使い分けは別記事「Goエラーハンドリング」で詳しく扱います。

パッケージ構成と internal

Go のコードは「フォルダ=パッケージ」で、大文字始まりの名前だけがパッケージの外から見えます。 実行ファイルは cmd/<名前>/main.go、外部から使わせたくないコードは internal/ に置く構成が広く使われています。

inventory/
  go.mod                        module example.com/inventory
  cmd/inventory/main.go         package main(実行ファイル)
  internal/stock/stock.go       package stock(このモジュール内からのみ import 可)
  internal/stock/stock_test.go
  pkg/format/format.go          package format(他モジュールからも import 可)
// internal/stock/stock.go
// Package stock は在庫の増減を扱います。
package stock

import "errors"

// ErrInsufficient は在庫が足りないときに返されます(大文字始まり = 公開)。
var ErrInsufficient = errors.New("stock: insufficient quantity")

// Store は品目ごとの在庫数を保持します。
type Store struct {
    items map[string]int // 小文字始まり = パッケージ内だけで使える
}

func New() *Store { return &Store{items: map[string]int{}} }

func (s *Store) Add(name string, n int) { s.items[name] += n }

// Take は在庫を n 個減らします。足りなければ ErrInsufficient を返します。
func (s *Store) Take(name string, n int) error {
    if s.items[name] < n {
        return ErrInsufficient
    }
    s.items[name] -= n
    return nil
}

func (s *Store) Count(name string) int { return s.items[name] }
// cmd/inventory/main.go
package main

import (
    "fmt"

    "example.com/inventory/internal/stock"
    "example.com/inventory/pkg/format"
)

func main() {
    s := stock.New()
    s.Add("pen", 5)
    if err := s.Take("pen", 2); err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Println(format.Line("pen", s.Count("pen")))
}
$ go run ./cmd/inventory
pen   :   3

$ go test ./...
?       example.com/inventory/cmd/inventory [no test files]
ok      example.com/inventory/internal/stock    0.002s
?       example.com/inventory/pkg/format    [no test files]

$ go build -o bin/inventory ./cmd/inventory

別のモジュールから internal 配下を import しようとすると、ビルド時に止まります。

$ go build .
package example.com/other
    main.go:3:8: use of internal package example.com/inventory/internal/stock not allowed
ルール 内容
1フォルダ=1パッケージ 同じフォルダのファイルは同じ package 名にする
公開の基準 識別子の先頭が大文字なら公開、小文字なら非公開
internal/ その親ディレクトリ以下からしか import できない
cmd/ 実行ファイル(package main)を置く慣例
import パス モジュール名/フォルダ(example.com/inventory/internal/stock)
ドキュメントコメント 公開する識別子の直前に // Name は ... と書くと go doc に表示される

gofmt と go vet で「揃える」「気づく」

gofmt はインデント(タブ)や空白を1つのスタイルに揃え、go vet はコンパイルは通るが動作がおかしい典型パターンを見つけます。 どちらも標準添付で、設定ファイルは不要です。

package main

import "fmt"

func main() {
    name := "Go"
      count := 3
    fmt.Printf("%s は %d 個\n", name)
    fmt.Printf("%d 個\n", name, count)
}
$ gofmt -l .            # 整形が必要なファイルを列挙
fmtvet/main.go

$ gofmt -d main.go      # 差分を表示(-w で上書き)
-    name := "Go"
-      count := 3
+   name := "Go"
+   count := 3

$ go vet .
fmtvet/main.go:8:24: fmt.Printf format %d reads arg #2, but call has 1 arg
fmtvet/main.go:9:17: fmt.Printf format %d has arg name of wrong type string

エディタの保存時フォーマット(VS Code の Go 拡張なら既定で有効)と、CI での gofmt -l と go vet ./... を組み合わせるのが定番です。import の自動整理まで欲しい場合は goimports、より多くのチェックが欲しい場合は staticcheck や golangci-lint を追加します。

Go 1.27 の主な変更点

公式リリースノート(go.dev/doc/go1.27)から、入門段階でも関係する変更を抜粋します。言語仕様の変更は「ジェネリックメソッド」が中心で、既存コードはそのまま動きます(Go 1 互換性保証)。

分類 変更
言語 メソッドが独自の型パラメータを持てる(ジェネリックメソッド)。構造体リテラルのキーにフィールドセレクタを使える
新パッケージ encoding/json/v2(より厳密な既定:不正な UTF-8 や重複キーを拒否)、encoding/json/jsontext、uuid(RFC 9562)、crypto/mldsa
標準ライブラリ strings.CutLast/bytes.CutLast、net/url の URL.Clone、net/http/httptest.NewTestServer など
ツール go doc で pkg@version 指定と -ex(例の表示)、go test が stdversion チェックを既定で実行、go mod tidy が require ブロックを整理
ランタイム 小さなメモリ割り当ての高速化、goroutine リークのプロファイル(goroutineleak)が正式機能に
対応環境 macOS は 13 Ventura 以降
package main

import (
    "encoding/json/v2" // Go 1.27 で追加された新しい JSON パッケージ
    "fmt"
    "uuid" // Go 1.27 で追加(RFC 9562)
)

type Item struct {
    ID   string `json:"id"`
    Name string `json:"name"`
}

func main() {
    id := uuid.NewV7() // 時刻順に並ぶ UUID v7
    fmt.Println(len(id.String()), id.String()[14:15]) // 15 文字目がバージョン

    b, err := json.Marshal(Item{ID: id.String(), Name: "ノート"})
    fmt.Println(string(b), err)

    var it Item
    err = json.Unmarshal([]byte(`{"id":"x","name":"a","name":"b"}`), &it) // 重複キーは既定で拒否
    fmt.Println(it, err)
}
$ go run .
36 7
{"id":"01a0f2db-600f-7262-ac06-dd3ae7aaebdb","name":"ノート"} <nil>
{x a} jsontext: duplicate object member name "name"

よくあるつまずきと実際のエラー文

Go のコンパイラは「使っていない変数・import」をエラーにします。 最初は驚きますが、消し忘れのコードが残らないための仕様です。以下はすべて Go 1.27.1 で実際に出たメッセージです。

1. 未使用の変数・import がコンパイルエラーになる

package main

import (
    "fmt"
    "os"
)

func main() {
    x := 10
    fmt.Println("hello")
}
$ go run .
./main.go:5:2: "os" imported and not used
./main.go:9:2: declared and not used: x

→ 使わない変数は削除するか、一時的に _ = x で「使った」ことにします。import は goimports を使うと自動で整理されます。

2. := と = の使い分け

状況 エラー文 直し方
関数の外で count := 0 syntax error: non-declaration statement outside function body var count = 0 にする
同じスコープで n := 1 の後にもう一度 n := 2 no new variables on left side of := 2回目は n = 2
宣言していない変数に total = 5 undefined: total total := 5 か var total int

もう1つ注意したいのが「シャドウイング」です。 if や for の中で := を使うと、外側と同名の「新しい変数」が作られ、外側の値は変わりません。コンパイルは通るので気づきにくいパターンです。

package main

import "fmt"

func find(key string) (string, error) { return "value-of-" + key, nil }

func main() {
    v := "初期値"
    if key := "k1"; key != "" {
        v, err := find(key) // := で新しい v が作られ、外の v は変わらない
        _ = err
        fmt.Println("内側の v =", v)
    }
    fmt.Println("外側の v =", v)
}
$ go run .
内側の v = value-of-k1
外側の v = 初期値

→ 外側の変数に入れたいときは var err error を先に宣言して v, err = find(key) と = で代入します。

3. スライスの共有で、意図しない要素が書き換わる

package main

import (
    "fmt"
    "slices"
)

func main() {
    base := []int{1, 2, 3, 4}
    part := base[:2]        // 同じ配列を共有
    part = append(part, 99) // 容量に余裕があるので base[2] を上書きしてしまう
    fmt.Println("base:", base, "part:", part)

    // 独立させたいときは slices.Clone(または copy)を使う
    base2 := []int{1, 2, 3, 4}
    part2 := slices.Clone(base2[:2])
    part2 = append(part2, 99)
    fmt.Println("base2:", base2, "part2:", part2)
}
$ go run .
base: [1 2 99 4] part: [1 2 99]
base2: [1 2 3 4] part2: [1 2 99]

→ 部分スライスに append するときは、元のデータを守りたいなら slices.Clone で複製してから行います。

4. nil マップへの代入は panic になる

package main

import "fmt"

func main() {
    var counts map[string]int // nil マップ(読み取りはできる)
    fmt.Println(counts["a"], len(counts))
    counts["a"] = 1 // 書き込みは panic
}
$ go run .
0 0
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
    /path/to/main.go:8 +0xa7

→ counts := map[string]int{} または counts = make(map[string]int) で初期化してから書き込みます。構造体のフィールドにマップを持つときは、コンストラクタ関数(New())で初期化するのが定番です。

5. 型が違う値は計算できない

var n int = 3
var f float64 = 1.5
fmt.Println(n + f)
fmt.Println("count: " + n)
./main.go:8:14: invalid operation: n + f (mismatched types int and float64)
./main.go:9:14: invalid operation: "count: " + n (mismatched types untyped string and int)

→ float64(n) + f、"count: " + strconv.Itoa(n)(または fmt.Sprintf("count: %d", n))のように明示的に変換します。

よくある質問

Q. Go は他の言語と比べて何が向いていますか? A. CLI ツール、Web API・マイクロサービス、インフラ系ツール(Docker・Kubernetes・Terraform は Go 製)に特に向いています。単一バイナリで配布でき、起動が速く、goroutine で並行処理を素直に書けるためです。

Q. GOPATH の設定は必要ですか? A. 不要です。Go 1.11 以降のモジュールモードでは、go mod init した任意のフォルダがプロジェクトになります。go env GOPATH の場所は、go install したツールやモジュールキャッシュの置き場として使われるだけです。

Q. 学ぶ順番はどうすればよいですか? A. この記事の順(コマンド → 変数 → 制御構文 → スライス・マップ → 構造体・インターフェース)で、各コードを自分の環境で go run するのが近道です。その後、公式の「A Tour of Go」で演習し、エラー処理 → goroutine・channel と進むと実務のコードが読めるようになります。

Q. go run と go build はどちらを使えばよいですか? A. 開発中の確認は go run .、配布・デプロイは go build -o app . です。go run は一時ディレクトリにビルドして実行するだけで、成果物を残しません。

まとめ

  • プロジェクトは go mod init で始め、go run・go build・go test の3コマンドで開発が回る
  • 変数は var か :=、ゼロ値が必ず入り、型変換は常に明示する
  • ループは for だけ。for i := range n(1.22〜)とイテレータ関数(1.23〜)で range の対象が広がった
  • スライスは配列を共有するビュー、マップは初期化してから書き込む(nil マップへの代入は panic)
  • クラスの代わりに構造体+メソッド+インターフェース(暗黙実装)+埋め込みで設計する
  • ジェネリクスは [T any] と制約で書き、Go 1.27 からメソッドにも型パラメータを付けられる
  • 未使用変数・import はコンパイルエラー、gofmt・go vet で書式とバグの芽を揃える

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Go言語入門|1.27対応の基本文法を実行結果つきで解説」

株式会社RJCでは、Goのようなシンプルで読みやすい言語の設計思想を、実際にコードを動かしながら仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?