Go言語入門:25の例で学ぶ

Go言語入門:25の例で学ぶ

Go(Golang)は Google が開発した言語です。シンプルで、コンパイルが速く、並行処理(concurrency)を標準で備えているため、サーバー、ツール、クラウドシステムで人気があります。この記事には 25 個のコード例があり、すべて Go 1.27 で実行した実際の出力を載せています。基本から、並行処理、Web ハンドラー、ジェネリクスまで順に説明します。

必要なもの

go.dev/dl から Go をインストールし、`go version` で確認します。`.go` ファイルを作り、`go run ファイル名.go` で実行します。

図解

以下の図は処理の流れと、ゴルーチンとチャネルの考え方を示します(概略図で、実際のスクリーンショットではありません)。
以下の図は処理の流れと、ゴルーチンとチャネルの考え方を示します(概略図で、実際のスクリーンショットではありません)。

例 1: こんにちは世界

最も簡単な Go プログラムです。すべてのプログラムは main パッケージと main 関数から始まります。

package main

import "fmt"

func main() {
	fmt.Println("สวัสดี Go")
}

**実際の出力**

สวัสดี Go

例 2: 変数と定数

`:=` は宣言と代入を一度に行い、`var` は型を明示して宣言し、`const` は変わらない値に使います。

package main

import "fmt"

const schoolName = "โรงเรียนตัวอย่าง"

func main() {
	name := "สมชาย"
	var age int = 20
	fmt.Println(schoolName, name, age)
}

**実際の出力**

โรงเรียนตัวอย่าง สมชาย 20

例 3: ゼロ値

値を指定せずに宣言した変数には、その型のゼロ値が入ります。たとえば 0、空文字列、false です。

package main

import "fmt"

func main() {
	var n int
	var s string
	var b bool
	var f float64
	fmt.Printf("int=%d string=%q bool=%t float=%.1f\n", n, s, b, f)
}

**実際の出力**

int=0 string="" bool=false float=0.0

例 4: if と else

if と else if で判断します。条件を括弧で囲む必要はありません。

package main

import "fmt"

func main() {
	score := 72
	if score >= 80 {
		fmt.Println("A")
	} else if score >= 70 {
		fmt.Println("B")
	} else {
		fmt.Println("C")
	}
}

**実際の出力**

B

例 5: for ループ

Go のループは for の一種類だけです。カウント用にも while のようにも使えます。

package main

import "fmt"

func main() {
	for i := 1; i <= 3; i++ {
		fmt.Println("นับ", i)
	}
	n := 0
	for n < 2 {
		n++
	}
	fmt.Println("n =", n)
}

**実際の出力**

นับ 1
นับ 2
นับ 3
n = 2

例 6: switch

switch は複数の場合を比較します。明示しない限り次の case に落ちないため break は不要です。

package main

import "fmt"

func main() {
	day := "เสาร์"
	switch day {
	case "เสาร์", "อาทิตย์":
		fmt.Println("วันหยุด")
	default:
		fmt.Println("วันทำงาน")
	}
}

**実際の出力**

วันหยุด

例 7: 配列とスライス

配列はサイズが固定です。スライスは append で伸び、s[a:b] で一部を切り出せます。

package main

import "fmt"

func main() {
	arr := [3]int{1, 2, 3}
	s := []int{10, 20}
	s = append(s, 30, 40)
	fmt.Println("array:", arr, "len:", len(arr))
	fmt.Println("slice:", s, "len:", len(s), "cap:", cap(s) >= 4)
	fmt.Println("ตัดช่วง:", s[1:3])
}

**実際の出力**

array: [1 2 3] len: 3
slice: [10 20 30 40] len: 4 cap: true
ตัดช่วง: [20 30]

例 8: マップ

マップはキーと値の組を保存します。`v, ok :=` の形式でキーの有無を確認できます。

package main

import "fmt"

func main() {
	scores := map[string]int{"สมชาย": 85, "สมหญิง": 72}
	scores["สมศักดิ์"] = 45
	if v, ok := scores["สมชาย"]; ok {
		fmt.Println("คะแนน:", v)
	}
	delete(scores, "สมหญิง")
	fmt.Println("จำนวน:", len(scores))
}

**実際の出力**

คะแนน: 85
จำนวน: 2

例 9: 複数の戻り値を持つ関数

関数は複数の値を返せます。通常はエラーを最後の戻り値として返します。

package main

import (
	"errors"
	"fmt"
)

func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("หารด้วยศูนย์ไม่ได้")
	}
	return a / b, nil
}

func main() {
	q, err := divide(10, 2)
	fmt.Println("ผล:", q, "error:", err)
	_, err = divide(1, 0)
	fmt.Println("error:", err)
}

**実際の出力**

ผล: 5 error: <nil>
error: หารด้วยศูนย์ไม่ได้

例 10: クロージャ

外側のスコープの変数を覚えている関数です。カウンターや非公開の状態に使えます。

package main

import "fmt"

func counter() func() int {
	count := 0
	return func() int {
		count++
		return count
	}
}

func main() {
	next := counter()
	fmt.Println(next(), next(), next())
}

**実際の出力**

1 2 3

例 11: 構造体(struct)とメソッド

struct は複数のデータをまとめ、メソッドはその型に動作を結びつけます。

package main

import "fmt"

type Student struct {
	Name  string
	Score int
}

func (s Student) Grade() string {
	if s.Score >= 80 {
		return "A"
	}
	return "B"
}

func main() {
	s := Student{Name: "สมชาย", Score: 85}
	fmt.Println(s.Name, s.Grade())
}

**実際の出力**

สมชาย A

例 12: インターフェース

インターフェースは型が持つべきメソッドの集合を定めます。そのメソッドを持つ型は自動的にそのインターフェースを満たします。

package main

import "fmt"

type Shape interface {
	Area() float64
}

type Rect struct{ W, H float64 }

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

func main() {
	var s Shape = Rect{W: 3, H: 4}
	fmt.Printf("พื้นที่ = %.1f\n", s.Area())
}

**実際の出力**

พื้นที่ = 12.0

例 13: ポインター

&x は変数のアドレスを返し、*p はそのアドレスの値を読み書きします。関数の呼び出し元の値を変えられます。

package main

import "fmt"

func increment(n *int) {
	*n++
}

func main() {
	x := 5
	increment(&x)
	fmt.Println("x =", x)
}

**実際の出力**

x = 6

例 14: エラーの処理

fmt.Errorf と %w でエラーを作り、errors.Is で既知のエラーと一致するか確認します。

package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("ไม่พบข้อมูล")

func find(id int) error {
	if id != 1 {
		return fmt.Errorf("รหัส %d: %w", id, ErrNotFound)
	}
	return nil
}

func main() {
	err := find(2)
	fmt.Println(err)
	fmt.Println("ตรงกับ ErrNotFound:", errors.Is(err, ErrNotFound))
}

**実際の出力**

รหัส 2: ไม่พบข้อมูล
ตรงกับ ErrNotFound: true

例 15: defer

defer は関数が終わるまで呼び出しを遅らせます。ファイルを閉じるなど、後始末に向いています。

package main

import "fmt"

func main() {
	defer fmt.Println("ทำงานสุดท้าย")
	fmt.Println("เริ่มต้น")
	fmt.Println("ประมวลผล")
}

**実際の出力**

เริ่มต้น
ประมวลผล
ทำงานสุดท้าย

例 16: panic と recover

panic は通常の処理を止めます。recover は defer された関数の中でのみ捕まえられるので、プログラム全体が落ちずに済みます。

package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("กู้คืนจาก panic: %v", r)
		}
	}()
	return a / b, nil
}

func main() {
	fmt.Println(safeDivide(10, 2))
	fmt.Println(safeDivide(1, 0))
}

**実際の出力**

5 <nil>
0 กู้คืนจาก panic: runtime error: integer divide by zero

例 17: ゴルーチン(goroutine)

go キーワードで並行に動く処理を始めます。sync.WaitGroup はすべての処理が終わるまで待ちます。

package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	for i := 1; i <= 3; i++ {
		wg.Add(1)
		go func(n int) {
			defer wg.Done()
			_ = n * n
		}(i)
	}
	wg.Wait()
	fmt.Println("ทุกงานเสร็จแล้ว")
}

**実際の出力**

ทุกงานเสร็จแล้ว

例 18: チャネル(channel)

チャネルはゴルーチン同士で値を受け渡します。この例では、ゴルーチンから main へ値を送り、最後にチャネルを閉じます。

package main

import "fmt"

func main() {
	ch := make(chan int)
	go func() {
		for i := 1; i <= 3; i++ {
			ch <- i * 10
		}
		close(ch)
	}()
	for v := range ch {
		fmt.Println("ได้รับ", v)
	}
}

**実際の出力**

ได้รับ 10
ได้รับ 20
ได้รับ 30

例 19: select とタイムアウト

select は複数のチャネルを同時に待ちます。time.After で待つ最大時間を決められます。

package main

import (
	"fmt"
	"time"
)

func main() {
	result := make(chan string)
	go func() {
		time.Sleep(200 * time.Millisecond)
		result <- "เสร็จ"
	}()
	select {
	case msg := <-result:
		fmt.Println(msg)
	case <-time.After(50 * time.Millisecond):
		fmt.Println("หมดเวลา")
	}
}

**実際の出力**

หมดเวลา

例 20: 共有データを守る Mutex

sync.Mutex を使うと、複数のゴルーチンが同じ変数を安全に変更できます。合計は常に正しくなります。

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup
	total := 0
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			mu.Lock()
			total++
			mu.Unlock()
		}()
	}
	wg.Wait()
	fmt.Println("รวม =", total)
}

**実際の出力**

รวม = 100
以下の図は処理の流れと、ゴルーチンとチャネルの考え方を示します(概略図で、実際のスクリーンショットではありません)。
以下の図は処理の流れと、ゴルーチンとチャネルの考え方を示します(概略図で、実際のスクリーンショットではありません)。

例 21: strings パッケージと rune

strings は文字列の関数を提供します。len はバイト数を数えるため、タイ語の文字数は []rune に変換して数えます。

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "Go เขียนง่าย"
	fmt.Println(strings.ToUpper("go"), strings.Contains(s, "เขียน"))
	fmt.Println(strings.Split("a,b,c", ","), len("สวัสดี"))
	fmt.Println([]rune("สวัสดี")[0] == 'ส')
}

**実際の出力**

GO true
[a b c] 18
true

例 22: ファイルの読み書き

os.WriteFile と os.ReadFile は、ファイル全体を一度の呼び出しで読み書きします。

package main

import (
	"fmt"
	"os"
)

func main() {
	if err := os.WriteFile("note.txt", []byte("บันทึกจาก Go\n"), 0644); err != nil {
		fmt.Println("เขียนไม่ได้:", err)
		return
	}
	data, err := os.ReadFile("note.txt")
	if err != nil {
		fmt.Println("อ่านไม่ได้:", err)
		return
	}
	fmt.Print("อ่านได้: ", string(data))
}

**実際の出力**

อ่านได้: บันทึกจาก Go

例 23: JSON

encoding/json は struct と JSON を相互に変換します。`json:"..."` のタグでフィールド名を指定します。

package main

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

func main() {
	b, _ := json.Marshal(Student{Name: "สมชาย", Score: 85})
	fmt.Println(string(b))

	var s Student
	_ = json.Unmarshal([]byte(`{"name":"สมหญิง","score":72}`), &s)
	fmt.Println(s.Name, s.Score)
}

**実際の出力**

{"name":"สมชาย","score":85}
สมหญิง 72

例 24: HTTP ハンドラーのテスト

httptest を使うと、本物のサーバーを起動せずにハンドラー関数をテストできます。

package main

import (
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
)

func hello(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "สวัสดีจาก Go HTTP")
}

func main() {
	req := httptest.NewRequest(http.MethodGet, "/", nil)
	rec := httptest.NewRecorder()
	hello(rec, req)
	body, _ := io.ReadAll(rec.Result().Body)
	fmt.Println(rec.Code, string(body))
}

**実際の出力**

200 สวัสดีจาก Go HTTP

例 25: ジェネリクス

型パラメーター T を使い、複数の型に対応する関数を書きます。インターフェースで型の条件を指定します。

package main

import "fmt"

type Number interface {
	~int | ~float64
}

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

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))
	fmt.Println(Sum([]float64{1.5, 2.5}))
}

**実際の出力**

6
4

次のステップ

JSON を受け取ってファイルに保存する Web サーバーを作ってみてください。その後、タイムアウトやキャンセルのために net/http と context パッケージを学びましょう。

まとめ

Go は文法がシンプルで、型はコンパイル時に検査され、エラーは値として扱います。ゴルーチンとチャネルで並行処理を行えるため、サーバーや多くのタスクを扱う処理に適しています。