- Go言語
Go言語入門|1.27対応の基本文法を実行結果つきで解説
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期連続の黒字決算。