- Authors

- Name
- Youngju Kim
- @fjvbn20031
はじめに
Go(Golang)はGoogleが2009年に発表した静的型付け・コンパイル型言語です。シンプルな文法、高速なコンパイル、強力な並行処理サポート、豊富な標準ライブラリが特徴です。クラウドインフラ、マイクロサービス、CLIツール開発に特に優れています。
このガイドでは、Goのコア文法を最初から最後まで体系的に解説します。
1. Go言語の基礎文法
1.1 変数宣言
Goで変数を宣言する方法は複数あります。
package main
import "fmt"
func main() {
// varキーワードで型を明示
var name string = "Go"
var age int = 15
// 型推論 (var)
var pi = 3.14159
// 短縮宣言 (:=) — 関数内でのみ使用可能
greeting := "Hello, Go!"
count := 42
// 複数変数の宣言
var x, y int = 10, 20
a, b := 100, 200
// ゼロ値:初期化しない場合は型のデフォルト値
var zeroInt int // 0
var zeroStr string // ""
var zeroBool bool // false
fmt.Println(name, age, pi, greeting, count)
fmt.Println(x, y, a, b)
fmt.Println(zeroInt, zeroStr, zeroBool)
}
1.2 定数とiota
package main
import "fmt"
// 単純な定数
const Pi = 3.14159
const MaxSize = 1024
// 定数ブロック
const (
StatusOK = 200
StatusNotFound = 404
StatusError = 500
)
// iota: 自動インクリメントの列挙定数
type Direction int
const (
North Direction = iota // 0
East // 1
South // 2
West // 3
)
type ByteSize float64
const (
_ = iota // ブランク識別子で0をスキップ
KB ByteSize = 1 << (10 * iota) // 1 << 10 = 1024
MB // 1 << 20
GB // 1 << 30
)
func main() {
fmt.Println(North, East, South, West) // 0 1 2 3
fmt.Printf("KB=%.0f, MB=%.0f, GB=%.0f\n", float64(KB), float64(MB), float64(GB))
}
1.3 基本型
package main
import "fmt"
func main() {
// 整数型
var i8 int8 = 127
var i16 int16 = 32767
var i32 int32 = 2147483647
var i64 int64 = 9223372036854775807
var ui uint = 42
// プラットフォーム依存 (64ビットシステムではint64)
var n int = 100
// 浮動小数点
var f32 float32 = 3.14
var f64 float64 = 3.141592653589793
// 複素数
var c128 complex128 = 3 + 4i
// string (UTF-8エンコード、不変)
s := "Hello, World"
fmt.Println(len(s)) // バイト数
fmt.Println([]rune(s)) // rune (Unicodeコードポイント) スライス
// byte (uint8のエイリアス)
var b byte = 'A'
// rune (int32のエイリアス、Unicodeコードポイント)
var r rune = 'Z'
// bool
var flag bool = true
fmt.Println(i8, i16, i32, i64, ui, n)
fmt.Println(f32, f64, c128)
fmt.Println(b, r, flag)
}
1.4 配列とスライス
package main
import "fmt"
func main() {
// 配列: 固定サイズ、値型
var arr [5]int
arr[0] = 10
arr2 := [3]string{"Go", "Python", "Rust"}
arr3 := [...]int{1, 2, 3, 4, 5} // コンパイラがサイズを推論
fmt.Println(arr, arr2, arr3)
// スライス: 動的サイズ、参照型
s1 := []int{1, 2, 3}
s2 := make([]int, 3) // len=3, cap=3
s3 := make([]int, 3, 10) // len=3, cap=10
fmt.Println(len(s1), cap(s1)) // 3, 3
fmt.Println(len(s2), cap(s2)) // 3, 3
fmt.Println(len(s3), cap(s3)) // 3, 10
// append: 容量超過時に新配列を割り当て
s1 = append(s1, 4, 5)
fmt.Println(s1) // [1 2 3 4 5]
// スライシング (元の配列とメモリを共有)
s4 := s1[1:3] // [2 3]
s5 := s1[2:] // [3 4 5]
s6 := s1[:2] // [1 2]
fmt.Println(s4, s5, s6)
// copy: 独立したコピーを作成
dst := make([]int, len(s1))
n := copy(dst, s1)
fmt.Printf("コピーされた要素数: %d, dst: %v\n", n, dst)
// 2次元スライス
matrix := make([][]int, 3)
for i := range matrix {
matrix[i] = make([]int, 3)
for j := range matrix[i] {
matrix[i][j] = i*3 + j
}
}
fmt.Println(matrix)
}
1.5 マップ
package main
import "fmt"
func main() {
// マップの宣言と初期化
m1 := map[string]int{
"apple": 5,
"banana": 3,
"cherry": 8,
}
m2 := make(map[string][]string)
// 追加・更新
m1["date"] = 12
m2["fruits"] = []string{"apple", "banana"}
// 値の読み取り
val := m1["apple"]
fmt.Println(val) // 5
// カンマokパターン: キーの存在確認
val2, ok := m1["mango"]
if !ok {
fmt.Println("mango not found, zero value:", val2)
}
// 削除
delete(m1, "cherry")
fmt.Println(m1)
// イテレーション (順序は保証されない)
for key, value := range m1 {
fmt.Printf("%s: %d\n", key, value)
}
// nilマップへの書き込みはパニックを引き起こす
var m3 map[string]int
// m3["key"] = 1 // panic: assignment to entry in nil map
_ = m3
}
1.6 ポインタ
package main
import "fmt"
func increment(n *int) {
*n++ // 間接参照
}
func newInt(val int) *int {
return &val // ローカル変数のアドレスを返せる (ヒープに移動)
}
func main() {
x := 42
p := &x // xのアドレス
fmt.Println(p) // メモリアドレス (例: 0xc0000b4010)
fmt.Println(*p) // 42 (間接参照)
*p = 100
fmt.Println(x) // 100
increment(&x)
fmt.Println(x) // 101
ptr := newInt(200)
fmt.Println(*ptr) // 200
// nilポインタ
var nilPtr *int
fmt.Println(nilPtr) // <nil>
// fmt.Println(*nilPtr) // panic: nil pointer dereference
}
1.7 制御構造
package main
import "fmt"
func main() {
// for: Goで唯一のループ構造
for i := 0; i < 5; i++ {
fmt.Print(i, " ")
}
fmt.Println()
// whileスタイル
n := 1
for n < 100 {
n *= 2
}
fmt.Println(n)
// range
nums := []int{10, 20, 30, 40}
for idx, val := range nums {
fmt.Printf("[%d]=%d ", idx, val)
}
fmt.Println()
// インデックスのみ
for i := range nums {
fmt.Print(nums[i], " ")
}
fmt.Println()
// switch (自動フォールスルーなし)
day := "Monday"
switch day {
case "Saturday", "Sunday":
fmt.Println("週末")
case "Monday":
fmt.Println("月曜日")
default:
fmt.Println("平日")
}
// 条件なしswitch (if-elseの代替)
score := 85
switch {
case score >= 90:
fmt.Println("A")
case score >= 80:
fmt.Println("B")
case score >= 70:
fmt.Println("C")
default:
fmt.Println("F")
}
// defer: 関数終了時に実行 (LIFOの順序)
fmt.Println("開始")
defer fmt.Println("最初のdefer")
defer fmt.Println("2番目のdefer")
fmt.Println("終了")
// 出力: 開始 -> 終了 -> 2番目のdefer -> 最初のdefer
}
2. 関数とメソッド
2.1 複数の戻り値と名前付き戻り値
package main
import (
"errors"
"fmt"
)
// 複数の戻り値
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 名前付き戻り値
func minMax(nums []int) (min, max int) {
if len(nums) == 0 {
return // ゼロ値を返す
}
min, max = nums[0], nums[0]
for _, n := range nums[1:] {
if n < min {
min = n
}
if n > max {
max = n
}
}
return // ネイキッドリターン
}
// 可変長引数
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
result, err := divide(10, 3)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("%.4f\n", result) // 3.3333
}
_, err = divide(5, 0)
if err != nil {
fmt.Println("エラー:", err) // division by zero
}
min, max := minMax([]int{3, 1, 4, 1, 5, 9, 2, 6})
fmt.Println(min, max) // 1 9
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// スライスを可変長引数として展開
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
}
2.2 一級関数とクロージャ
package main
import "fmt"
// 関数を引数として受け取る
func apply(nums []int, fn func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = fn(n)
}
return result
}
// 関数を返す
func multiplier(factor int) func(int) int {
return func(n int) int {
return n * factor
}
}
// クロージャ: 外部スコープの変数をキャプチャ
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
double := func(n int) int { return n * 2 }
nums := []int{1, 2, 3, 4, 5}
fmt.Println(apply(nums, double)) // [2 4 6 8 10]
fmt.Println(apply(nums, func(n int) int { return n * n })) // [1 4 9 16 25]
triple := multiplier(3)
fmt.Println(triple(7)) // 21
c1 := counter()
c2 := counter()
fmt.Println(c1(), c1(), c1()) // 1 2 3
fmt.Println(c2(), c2()) // 1 2 (独立した状態)
}
2.3 メソッド (値レシーバ vs ポインタレシーバ)
package main
import (
"fmt"
"math"
)
type Circle struct {
Radius float64
}
// 値レシーバ: コピーに適用 (元を変更不可)
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// ポインタレシーバ: 元を変更可能
func (c *Circle) Scale(factor float64) {
c.Radius *= factor
}
func init() {
fmt.Println("パッケージ初期化")
}
func main() {
c := Circle{Radius: 5}
fmt.Printf("面積: %.2f\n", c.Area())
fmt.Printf("周長: %.2f\n", c.Perimeter())
c.Scale(2)
fmt.Printf("スケール後の半径: %.1f\n", c.Radius) // 10.0
}
3. 構造体とインターフェース
3.1 構造体と埋め込み
package main
import "fmt"
type Animal struct {
Name string
Age int
}
func (a Animal) Speak() string {
return a.Name + " が鳴く"
}
// 埋め込み: 継承ではなく構成 (Composition over Inheritance)
type Dog struct {
Animal // 埋め込み (匿名フィールド)
Breed string
}
func (d Dog) Speak() string {
return d.Name + " が言う: ワン!"
}
type ServiceDog struct {
Dog
ServiceType string
}
func main() {
d := Dog{
Animal: Animal{Name: "バディ", Age: 3},
Breed: "ラブラドール",
}
// 埋め込みフィールドへの直接アクセス
fmt.Println(d.Name) // d.Animal.Nameと同じ
fmt.Println(d.Age)
fmt.Println(d.Breed)
fmt.Println(d.Speak()) // DogのSpeak()
fmt.Println(d.Animal.Speak()) // AnimalのSpeak()を明示的に呼び出し
sd := ServiceDog{
Dog: d,
ServiceType: "誘導犬",
}
fmt.Println(sd.Name) // sd.Dog.Animal.Name
fmt.Println(sd.ServiceType)
}
3.2 インターフェース
package main
import (
"fmt"
"math"
)
// インターフェース定義
type Shape interface {
Area() float64
Perimeter() float64
}
type Stringer interface {
String() string
}
type Circle struct{ Radius float64 }
type Rectangle struct{ Width, Height float64 }
func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.Radius }
func (c Circle) String() string { return fmt.Sprintf("Circle(r=%.1f)", c.Radius) }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func (r Rectangle) Perimeter() float64 { return 2 * (r.Width + r.Height) }
func (r Rectangle) String() string {
return fmt.Sprintf("Rect(%.1fx%.1f)", r.Width, r.Height)
}
// インターフェースを引数として受け取る関数
func printShapeInfo(s Shape) {
fmt.Printf("面積: %.2f, 周長: %.2f\n", s.Area(), s.Perimeter())
}
// 空インターフェース (すべての型を受け取る)
func printAny(v interface{}) {
fmt.Printf("型: %T, 値: %v\n", v, v)
}
// 型アサーション
func describe(i interface{}) {
if s, ok := i.(string); ok {
fmt.Printf("文字列: %q\n", s)
return
}
if n, ok := i.(int); ok {
fmt.Printf("整数: %d\n", n)
return
}
fmt.Printf("不明な型: %T\n", i)
}
// 型スイッチ
func typeSwitch(v interface{}) string {
switch x := v.(type) {
case int:
return fmt.Sprintf("int: %d", x)
case string:
return fmt.Sprintf("string: %q", x)
case bool:
return fmt.Sprintf("bool: %v", x)
case []int:
return fmt.Sprintf("[]int (%d要素)", len(x))
default:
return fmt.Sprintf("不明な型: %T", x)
}
}
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 6},
}
for _, s := range shapes {
printShapeInfo(s)
if str, ok := s.(Stringer); ok {
fmt.Println(str.String())
}
}
printAny(42)
printAny("hello")
printAny([]int{1, 2, 3})
describe("Go言語")
describe(2024)
fmt.Println(typeSwitch(100))
fmt.Println(typeSwitch("world"))
fmt.Println(typeSwitch([]int{1, 2}))
}
4. エラー処理
4.1 エラー型とカスタムエラー
package main
import (
"errors"
"fmt"
)
// センチネルエラー (パッケージレベルのエラー変数)
var (
ErrNotFound = errors.New("not found")
ErrPermission = errors.New("permission denied")
)
// カスタムエラー型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("バリデーションエラー: %s - %s", e.Field, e.Message)
}
type DatabaseError struct {
Code int
Message string
Err error // ラップされたエラー
}
func (e *DatabaseError) Error() string {
return fmt.Sprintf("データベースエラー [%d]: %s", e.Code, e.Message)
}
func (e *DatabaseError) Unwrap() error {
return e.Err
}
// fmt.Errorfによるエラーラッピング
func findUser(id int) error {
if id <= 0 {
return fmt.Errorf("findUser: 無効なID %d: %w", id, ErrNotFound)
}
return nil
}
func getUser(id int) error {
if err := findUser(id); err != nil {
return fmt.Errorf("getUser: %w", err)
}
return nil
}
func main() {
// errors.Is: エラーチェーン内の特定エラーを確認
err := getUser(-1)
if errors.Is(err, ErrNotFound) {
fmt.Println("not foundエラー:", err)
}
// errors.As: エラーチェーンから特定の型を抽出
dbErr := &DatabaseError{
Code: 500,
Message: "接続失敗",
Err: ErrPermission,
}
wrapped := fmt.Errorf("サービスエラー: %w", dbErr)
var de *DatabaseError
if errors.As(wrapped, &de) {
fmt.Printf("DBエラーコード: %d\n", de.Code)
}
if errors.Is(wrapped, ErrPermission) {
fmt.Println("権限エラーがチェーンに含まれています")
}
valErr := &ValidationError{Field: "email", Message: "形式が無効"}
fmt.Println(valErr)
}
4.2 panicとrecover
package main
import "fmt"
// panicからの回復
func safeDiv(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 mustPositive(n int) int {
if n <= 0 {
panic(fmt.Sprintf("値は正でなければなりません: %d", n))
}
return n
}
func main() {
result, err := safeDiv(10, 2)
fmt.Printf("10/2 = %d, err = %v\n", result, err)
result, err = safeDiv(10, 0)
fmt.Printf("10/0 = %d, err = %v\n", result, err)
defer func() {
if r := recover(); r != nil {
fmt.Println("パニック回復:", r)
}
}()
mustPositive(-5)
}
5. ゴルーチンとチャネル (並行処理)
5.1 ゴルーチンとWaitGroup
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("ワーカー %d 開始\n", id)
time.Sleep(time.Millisecond * 100)
fmt.Printf("ワーカー %d 完了\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("すべてのワーカーが完了しました")
}
5.2 チャネル
package main
import (
"fmt"
"time"
)
func producer(ch chan<- int, count int) {
for i := 0; i < count; i++ {
ch <- i
time.Sleep(time.Millisecond * 10)
}
close(ch)
}
func main() {
// アンバッファードチャネル (同期)
ch1 := make(chan int)
go func() { ch1 <- 42 }()
fmt.Println(<-ch1) // 42
// バッファードチャネル (バッファが満杯になるまで非同期)
ch2 := make(chan string, 3)
ch2 <- "first"
ch2 <- "second"
ch2 <- "third"
fmt.Println(<-ch2) // first
// チャネルの反復処理
numbers := make(chan int, 10)
go producer(numbers, 5)
for n := range numbers {
fmt.Print(n, " ")
}
fmt.Println()
// select: 複数のチャネルを同時処理
ch3 := make(chan string, 1)
ch4 := make(chan string, 1)
go func() {
time.Sleep(time.Millisecond * 50)
ch3 <- "チャネル3"
}()
go func() {
time.Sleep(time.Millisecond * 30)
ch4 <- "チャネル4"
}()
for i := 0; i < 2; i++ {
select {
case msg := <-ch3:
fmt.Println("受信:", msg)
case msg := <-ch4:
fmt.Println("受信:", msg)
}
}
}
5.3 Mutexと同期プリミティブ
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
c.v[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.Lock()
defer c.mu.Unlock()
return c.v[key]
}
// RWMutex: 読み取りは並行、書き込みは排他
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Set(key, value string) {
c.mu.Lock()
defer c.mu.Unlock()
c.data[key] = value
}
func (c *Cache) Get(key string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.data[key]
return val, ok
}
// sync.Once: 一度だけ実行
var (
instance *Cache
once sync.Once
)
func getInstance() *Cache {
once.Do(func() {
instance = &Cache{data: make(map[string]string)}
fmt.Println("キャッシュインスタンスを作成しました")
})
return instance
}
func main() {
counter := SafeCounter{v: make(map[string]int)}
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Inc("key")
}()
}
wg.Wait()
fmt.Println("カウンター:", counter.Value("key")) // 1000
// sync.Map: 並行処理安全なマップ
var sm sync.Map
sm.Store("go", "1.21")
sm.Store("python", "3.12")
sm.Range(func(key, value interface{}) bool {
fmt.Printf("%v: %v\n", key, value)
return true
})
c1 := getInstance()
c2 := getInstance()
fmt.Println(c1 == c2) // true (同じインスタンス)
}
5.4 context.Context
package main
import (
"context"
"fmt"
"time"
)
func doWork(ctx context.Context, id int) error {
select {
case <-time.After(time.Millisecond * 200):
fmt.Printf("作業 %d 完了\n", id)
return nil
case <-ctx.Done():
fmt.Printf("作業 %d キャンセル: %v\n", id, ctx.Err())
return ctx.Err()
}
}
func fetchData(ctx context.Context, url string) (string, error) {
if requestID, ok := ctx.Value("requestID").(string); ok {
fmt.Printf("リクエストID: %s, URL: %s\n", requestID, url)
}
select {
case <-time.After(time.Millisecond * 100):
return "data from " + url, nil
case <-ctx.Done():
return "", ctx.Err()
}
}
func main() {
// WithCancel
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(time.Millisecond * 50)
cancel()
}()
doWork(ctx, 1)
// WithTimeout
ctx2, cancel2 := context.WithTimeout(context.Background(), time.Millisecond*150)
defer cancel2()
doWork(ctx2, 2)
// WithDeadline
deadline := time.Now().Add(time.Millisecond * 300)
ctx3, cancel3 := context.WithDeadline(context.Background(), deadline)
defer cancel3()
doWork(ctx3, 3)
// WithValue: リクエストスコープのデータを渡す
ctx4 := context.WithValue(context.Background(), "requestID", "req-001")
data, err := fetchData(ctx4, "https://api.example.com/users")
if err == nil {
fmt.Println("データ:", data)
}
}
6. パッケージとモジュール
6.1 Goモジュール
# 新しいモジュールの初期化
go mod init github.com/username/myapp
# 依存関係の追加
go get github.com/gin-gonic/gin@v1.9.1
# 依存関係の整理
go mod tidy
# vendorディレクトリの作成
go mod vendor
go.modファイルの例:
module github.com/username/myapp
go 1.21
require (
github.com/gin-gonic/gin v1.9.1
github.com/stretchr/testify v1.8.4
)
6.2 パッケージ構造のベストプラクティス
myapp/
├── cmd/
│ └── server/
│ └── main.go # エントリポイント
├── internal/ # 外部パッケージからインポート不可
│ ├── handler/
│ │ └── user.go
│ ├── service/
│ │ └── user.go
│ └── repository/
│ └── user.go
├── pkg/ # 外部からインポート可能なライブラリ
│ └── validator/
│ └── validator.go
├── api/ # API定義 (OpenAPI, Protobuf)
├── config/
│ └── config.go
├── go.mod
└── go.sum
6.3 主要な標準ライブラリ
package main
import (
"fmt"
"os"
"strings"
"strconv"
"time"
"io"
"bytes"
)
func main() {
// stringsパッケージ
s := " Hello, Go World! "
fmt.Println(strings.TrimSpace(s))
fmt.Println(strings.ToUpper(s))
fmt.Println(strings.Contains(s, "Go"))
fmt.Println(strings.Replace(s, "Go", "Golang", 1))
parts := strings.Split("a,b,c,d", ",")
fmt.Println(parts)
fmt.Println(strings.Join(parts, " | "))
// strconvパッケージ
n, _ := strconv.Atoi("42")
fmt.Println(n + 1)
str := strconv.Itoa(100)
fmt.Println(str + " bottles")
f, _ := strconv.ParseFloat("3.14", 64)
fmt.Printf("%.2f\n", f)
// timeパッケージ
now := time.Now()
fmt.Println(now.Format("2006-01-02 15:04:05"))
fmt.Println(now.Format(time.RFC3339))
tomorrow := now.Add(24 * time.Hour)
fmt.Println(tomorrow.Sub(now))
// osパッケージ
fmt.Println(os.Getenv("HOME"))
// ioとbytes
buf := bytes.NewBufferString("Hello")
buf.WriteString(", World")
data, _ := io.ReadAll(buf)
fmt.Println(string(data))
}
7. ジェネリクス (Go 1.18+)
7.1 ジェネリック関数と型
package main
import "fmt"
// 型パラメータを使ったジェネリック関数
func Min[T interface{ ~int | ~float64 | ~string }](a, b T) T {
if a < b {
return a
}
return b
}
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
func Filter[T any](slice []T, pred func(T) bool) []T {
var result []T
for _, v := range slice {
if pred(v) {
result = append(result, v)
}
}
return result
}
// ジェネリック型
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
top := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return top, true
}
func (s *Stack[T]) Len() int {
return len(s.items)
}
// comparable制約: ==演算子が使用可能
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item {
return true
}
}
return false
}
// カスタム型制約
type Number interface {
~int | ~int32 | ~int64 | ~float32 | ~float64
}
func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(Min(3, 5)) // 3
fmt.Println(Min(3.14, 2.71)) // 2.71
fmt.Println(Min("apple", "banana")) // apple
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2 4 6 8 10]
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4]
var s Stack[string]
s.Push("first")
s.Push("second")
s.Push("third")
for s.Len() > 0 {
item, _ := s.Pop()
fmt.Println(item)
}
fmt.Println(Contains([]string{"go", "python", "rust"}, "go")) // true
fmt.Println(Sum([]float64{1.1, 2.2, 3.3})) // 6.6
}
8. コード例: REST APIサーバー (net/http)
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"sync"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
type UserStore struct {
mu sync.RWMutex
users map[int]User
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]User),
nextID: 1,
}
}
func (s *UserStore) Create(name, email string) User {
s.mu.Lock()
defer s.mu.Unlock()
user := User{ID: s.nextID, Name: name, Email: email}
s.users[s.nextID] = user
s.nextID++
return user
}
func (s *UserStore) GetAll() []User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]User, 0, len(s.users))
for _, u := range s.users {
users = append(users, u)
}
return users
}
func (s *UserStore) GetByID(id int) (User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
u, ok := s.users[id]
return u, ok
}
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}
func makeUsersHandler(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
writeJSON(w, http.StatusOK, store.GetAll())
case http.MethodPost:
var body struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request body"})
return
}
writeJSON(w, http.StatusCreated, store.Create(body.Name, body.Email))
default:
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
}
}
}
func makeUserHandler(store *UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 4 {
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
id, err := strconv.Atoi(parts[3])
if err != nil {
http.Error(w, "Invalid ID", http.StatusBadRequest)
return
}
user, ok := store.GetByID(id)
if !ok {
writeJSON(w, http.StatusNotFound, map[string]string{
"error": fmt.Sprintf("user %d not found", id),
})
return
}
writeJSON(w, http.StatusOK, user)
}
}
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func main() {
store := NewUserStore()
store.Create("Alice", "alice@example.com")
store.Create("Bob", "bob@example.com")
mux := http.NewServeMux()
mux.Handle("/api/users", makeUsersHandler(store))
mux.Handle("/api/users/", makeUserHandler(store))
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
handler := loggingMiddleware(mux)
addr := ":8080"
fmt.Printf("サーバー起動: http://localhost%s\n", addr)
log.Fatal(http.ListenAndServe(addr, handler))
}
9. クイズ
クイズ1: スライスの動作
次のコードの出力結果は何ですか?
a := []int{1, 2, 3, 4, 5}
b := a[1:3]
b[0] = 99
fmt.Println(a)
答え: [1 99 3 4 5]
解説: スライスで作られた b は a と基底配列のメモリを共有します。b[0] は a[1] と同じメモリ位置を指しているため、b[0] = 99 を実行すると a[1] も99に変更されます。独立したコピーが必要な場合は copy を使用してください。
クイズ2: ゴルーチンとクロージャの落とし穴
次のコードはなぜ問題があり、どのように修正すべきですか?
for i := 0; i < 5; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Second)
答え: ほとんどの場合、5を5回出力するか、予測できない値を出力します。
解説: クロージャは変数 i を値ではなく参照でキャプチャします。ゴルーチンが実行される時点では、ループが既に終了して i が5になっています。修正方法は2つあります。
方法1 — 引数として渡す:
for i := 0; i < 5; i++ {
go func(n int) {
fmt.Println(n)
}(i)
}
方法2 — ローカル変数を作成:
for i := 0; i < 5; i++ {
i := i // 新しい変数iを作成
go func() {
fmt.Println(i)
}()
}
クイズ3: インターフェースとnil
次のコードで err == nil は何を出力しますか?
type MyError struct{ msg string }
func (e *MyError) Error() string { return e.msg }
func getError() error {
var err *MyError = nil
return err
}
func main() {
err := getError()
fmt.Println(err == nil)
}
答え: false
解説: Goのインターフェース値は型と値の2つのコンポーネントを持ちます。getError() は型 *MyError、値 nil のインターフェースを返します。型情報が存在するため、このインターフェースは nil インターフェースと比較すると nil ではありません。nil エラーを正しく返すには return nil と直接返してください。
クイズ4: deferの実行順序
次のコードの出力順序は?
func main() {
fmt.Println("A")
defer fmt.Println("defer 1")
fmt.Println("B")
defer fmt.Println("defer 2")
fmt.Println("C")
defer fmt.Println("defer 3")
fmt.Println("D")
}
答え: A B C D defer 3 defer 2 defer 1
解説: defer はLIFO(後入れ先出し)スタック方式で動作します。通常の文は宣言順に実行され、defer された関数は関数終了時に逆順(最後にdeferされたものが最初)で実行されます。
クイズ5: チャネルの方向性
ch chan<- int と ch <-chan int の違いは何ですか?
答え:
chan<- int: 送信専用チャネル (書き込みのみ可能)<-chan int: 受信専用チャネル (読み取りのみ可能)
解説: Goはチャネルの方向を型システムで制限できます。これにより、関数がチャネルをどのように使用するかをコンパイル時に検証できます。例:
func send(ch chan<- int, val int) {
ch <- val // OK
// <-ch // コンパイルエラー
}
func receive(ch <-chan int) int {
return <-ch // OK
// ch <- 1 // コンパイルエラー
}
func pipe(in <-chan int, out chan<- int) {
for v := range in {
out <- v * 2
}
}
チャネルの方向を明示することで、誤った方向でチャネルを使用することを防げます。