変数と定数 📌
- 変数宣言:
// 型を指定して宣言 (ゼロ値で初期化) var count int // 初期値を指定して宣言 (型推論) var message = "Hello" // 関数内での短縮宣言 (型推論) value := 100
- 複数変数宣言:
// まとめて宣言 var x, y int var a, b = 1, "text" c, d := true, 3.14 // グループ化して宣言 var ( appVersion string = "1.0.0" debugMode bool = false )
- 定数宣言:
// 型なし定数 (使用時に型が決定) const Pi = 3.14159 // 型付き定数 const MaxSize int = 1024 // 連続する定数 (iota) const ( Sunday = iota // 0 Monday // 1 Tuesday // 2 ) // iotaの応用 const ( _ = 1 << (10 * iota) // 読み飛ばし KiB // 1 << (10*1) = 1024 MiB // 1 << (10*2) = 1048576 GiB // 1 << (10*3) = 1073741824 )
制御構文 🚦
- if / else if / else:
score := 75 if score >= 80 { fmt.Println("Great") } else if score >= 60 { fmt.Println("Good") } else { fmt.Println("Needs Improvement") } // 条件判定前に簡単なステートメントを実行 if err := process(); err != nil { fmt.Println("Error:", err) }
- switch:
// 基本的なswitch day := "Monday" switch day { case "Sunday", "Saturday": fmt.Println("Weekend! 😊") case "Monday": fmt.Println("Start of the week...") default: fmt.Println("Weekday") } // 条件式を持つswitch (if/else ifの代替) num := 15 switch { case num < 10: fmt.Println("Single digit") case num < 100: fmt.Println("Double digit") default: fmt.Println("Large number") } // フォールスルー (fallthrough) value := 1 switch value { case 1: fmt.Println("Value is 1") fallthrough // 次のcaseも実行 case 2: fmt.Println("Value is 1 or 2") default: fmt.Println("Other value") }
- for:
// C言語風forループ for i := 0; i < 5; i++ { fmt.Println(i) } // 条件式のみ (while風) n := 0 for n < 5 { fmt.Println(n) n++ } // 無限ループ // for { // fmt.Println("Looping forever...") // time.Sleep(1 * time.Second) // } // スライスやマップの反復 (range) nums := []int{10, 20, 30} for index, value := range nums { fmt.Printf("Index: %d, Value: %d\n", index, value) } // インデックスのみ for index := range nums { fmt.Println("Index:", index) } // 値のみ for _, value := range nums { fmt.Println("Value:", value) } // マップの反復 colors := map[string]string{"red": "#ff0000", "green": "#00ff00"} for key, value := range colors { fmt.Printf("Key: %s, Value: %s\n", key, value) } // 文字列の反復 (rune単位) for index, runeValue := range "Go言語" { fmt.Printf("%#U starts at byte position %d\n", runeValue, index) }
- defer: 関数終了時に実行される処理を登録。リソース解放などに便利。
func readFile(filename string) error { file, err := os.Open(filename) if err != nil { return err } defer file.Close() // 関数終了時に必ず実行される // ファイル処理... return nil }
関数 ⚙️
- 基本的な関数定義:
func add(x int, y int) int { return x + y } // 引数の型が同じ場合は省略可能 func multiply(x, y int) int { return x * y }
- 複数の戻り値:
func divide(numerator, denominator int) (int, error) { if denominator == 0 { return 0, fmt.Errorf("cannot divide by zero") } return numerator / denominator, nil } result, err := divide(10, 2) if err != nil { fmt.Println("Error:", err) } else { fmt.Println("Result:", result) }
- 名前付き戻り値:
// 戻り値の変数を事前に宣言 func calculate(a, b int) (sum int, difference int) { sum = a + b difference = a - b // return文で変数を指定しなくても良い (naked return) return } s, d := calculate(5, 3) // s = 8, d = 2
- 可変長引数:
func sumNumbers(nums ...int) int { total := 0 for _, num := range nums { total += num } return total } sum := sumNumbers(1, 2, 3, 4, 5) // sum = 15 // スライスを渡すことも可能 numbers := []int{10, 20} sum = sumNumbers(numbers...) // sum = 30
- 無名関数 (クロージャ):
// 変数に関数を代入 greet := func(name string) { fmt.Println("Hello,", name) } greet("Alice") // クロージャ (関数が定義されたスコープの変数を参照) func incrementer() func() int { count := 0 return func() int { count++ return count } } inc := incrementer() fmt.Println(inc()) // 1 fmt.Println(inc()) // 2
データ構造 📚
- 配列 (Array): 固定長
var arr [3]int // [0 0 0] arr[0] = 1 fmt.Println(arr, len(arr)) // [1 0 0] 3 arr2 := [...]string{"apple", "banana"} // 要素数から長さを推論 fmt.Println(arr2, len(arr2)) // [apple banana] 2
- スライス (Slice): 可変長、配列への参照
// スライスリテラルで作成 s1 := []int{1, 2, 3} fmt.Println(s1, len(s1), cap(s1)) // [1 2 3] 3 3 // makeで作成 (長さと容量を指定) s2 := make([]int, 3, 5) // 長さ3, 容量5のスライス [0 0 0] fmt.Println(s2, len(s2), cap(s2)) // [0 0 0] 3 5 // appendで要素追加 (容量が足りなければ新しい配列を確保) s1 = append(s1, 4, 5) fmt.Println(s1, len(s1), cap(s1)) // [1 2 3 4 5] 5 6 (容量は実行環境依存で増加) // スライスからスライスを作成 (元の配列を共有) s3 := s1[1:3] // インデックス1から3の手前まで [2 3] fmt.Println(s3, len(s3), cap(s3)) // [2 3] 2 5 (容量は元のスライスの末尾まで) s3[0] = 99 // 元のスライスs1も変更される fmt.Println(s1) // [1 99 3 4 5] // copyでスライスをコピー s4 := make([]int, len(s1)) copiedCount := copy(s4, s1) fmt.Println(s4, copiedCount) // [1 99 3 4 5] 5
- マップ (Map): キーと値のペア
// マップリテラルで作成 m1 := map[string]int{"apple": 100, "banana": 200} fmt.Println(m1) // map[apple:100 banana:200] // makeで作成 m2 := make(map[string]string) m2["go"] = "Golang" m2["js"] = "JavaScript" fmt.Println(m2) // map[go:Golang js:JavaScript] // 要素へのアクセスと存在確認 price := m1["apple"] // 100 price, ok := m1["orange"] // price = 0 (ゼロ値), ok = false if ok { fmt.Println("Price:", price) } else { fmt.Println("Orange not found") } // 要素の削除 delete(m1, "banana") fmt.Println(m1) // map[apple:100] // マップの反復 (順序は保証されない) for key, value := range m2 { fmt.Printf("Key: %s, Value: %s\n", key, value) }
- 構造体 (Struct): 複数のフィールドを持つカスタム型
type Person struct { Name string Age int } // 構造体の初期化 p1 := Person{Name: "Alice", Age: 30} p2 := Person{"Bob", 25} // フィールド名を省略 (順序が重要) p3 := new(Person) // ポインタを返す (*Person)、フィールドはゼロ値 p3.Name = "Charlie" p3.Age = 40 fmt.Println(p1, p2, *p3) // {Alice 30} {Bob 25} {Charlie 40} // フィールドへのアクセス fmt.Println(p1.Name) // Alice // 構造体の埋め込み (Embedding) type Employee struct { Person // 型名をそのままフィールドとして埋め込む EmployeeID string Salary int } emp := Employee{ Person: Person{Name: "David", Age: 35}, EmployeeID: "E123", Salary: 50000, } // 埋め込まれた構造体のフィールドに直接アクセス可能 fmt.Println(emp.Name) // David fmt.Println(emp.Person.Age) // 35
ポインタ 👉
- ポインタの取得とデリファレンス:
x := 10 p := &x // xのアドレスをポインタpに格納 fmt.Println("Value of x:", x) // 10 fmt.Println("Address of x:", p) // メモリアドレス (例: 0x...) fmt.Println("Value via p:", *p) // ポインタpが指す先の値 (デリファレンス) -> 10 *p = 20 // ポインタ経由で値を変更 fmt.Println("New value of x:", x) // 20
- 関数でのポインタ利用: 値渡しではなく参照渡しのような効果
func increment(val *int) { *val++ // ポインタが指す先の値をインクリメント } num := 5 increment(&num) fmt.Println("Incremented num:", num) // 6
- new関数: 型のゼロ値へのポインタを割り当てて返す
ptr := new(int) // int型のゼロ値(0)へのポインタを返す fmt.Println(*ptr) // 0 *ptr = 100 fmt.Println(*ptr) // 100
メソッドとインターフェース 🧩
- メソッド定義: 特定の型に関連付けられた関数
type Rect struct { Width, Height float64 } // 値レシーバ (元の値を変更しない) func (r Rect) Area() float64 { return r.Width * r.Height } // ポインタレシーバ (元の値を変更できる) func (r *Rect) Scale(factor float64) { r.Width *= factor r.Height *= factor } rect := Rect{Width: 10, Height: 5} fmt.Println("Area:", rect.Area()) // 50.0 rect.Scale(2) // ポインタレシーバでも自動的にアドレスが渡される fmt.Println("Scaled Rect:", rect) // {20 10} // ポインタ型変数でもメソッド呼び出し可能 rectPtr := &Rect{Width: 3, Height: 4} fmt.Println("Area:", rectPtr.Area()) // 12.0 (自動的にデリファレンスされる) rectPtr.Scale(3) fmt.Println("Scaled Rect Ptr:", *rectPtr) // {9 12}
- インターフェース定義: メソッドシグネチャの集まり
type Shape interface { Area() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return math.Pi * c.Radius * c.Radius } // Shapeインターフェースを満たす型はShape型の変数に代入可能 func printArea(s Shape) { fmt.Printf("Area of %T: %f\n", s, s.Area()) } r := Rect{Width: 4, Height: 5} c := Circle{Radius: 3} printArea(r) // Rect型はArea()メソッドを持つのでShapeインターフェースを満たす printArea(c) // Circle型も同様
- 空インターフェース (`interface{}`): あらゆる型を保持できる。型アサーションや型スイッチで元の型を取り出す。
var i interface{} i = "hello" fmt.Println(i) // hello i = 42 fmt.Println(i) // 42 // 型アサーション (Type Assertion) s, ok := i.(string) // string型への変換を試みる if ok { fmt.Println("It's a string:", s) } else { fmt.Println("Not a string") } // 型スイッチ (Type Switch) func checkType(v interface{}) { switch t := v.(type) { case string: fmt.Println("String:", t) case int: fmt.Println("Integer:", t) case bool: fmt.Println("Boolean:", t) default: fmt.Printf("Unknown type: %T\n", t) } } checkType("world") checkType(123) checkType(true) checkType(3.14)
- Stringer インターフェース: `fmt`パッケージなどでオブジェクトを文字列として表現するために使われる。
type IPAddr [4]byte // String()メソッドを実装することでStringerインターフェースを満たす func (ip IPAddr) String() string { return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]) } addr := IPAddr{127, 0, 0, 1} fmt.Println(addr) // 127.0.0.1 (String()が呼ばれる)
エラーハンドリング ⚠️
- `error` インターフェース: Goのエラー処理の基本。`Error()`メソッドを持つ。
type error interface { Error() string }
- 基本的なエラー処理: 関数の戻り値として`error`を返し、呼び出し側で`nil`チェックを行う。
file, err := os.Open("nonexistent.txt") if err != nil { log.Printf("Failed to open file: %v\n", err) // エラーに応じた処理 (早期リターンなど) return } defer file.Close() // 正常系の処理 fmt.Println("File opened successfully.")
- `errors.New`: シンプルなエラーメッセージを持つ`error`を作成。
err := errors.New("something went wrong") fmt.Println(err)
- `fmt.Errorf`: フォーマット指定子を使ってエラーメッセージを組み立て、ラップされたエラー情報を含める。
func readFile(path string) error { _, err := os.ReadFile(path) if err != nil { // %w で元のエラーをラップする return fmt.Errorf("error reading file %s: %w", path, err) } return nil } err := readFile("config.yaml") if err != nil { fmt.Println(err) }
- `errors.Is`: エラーチェーンを辿り、特定のエラーが含まれているか確認。
err1 := os.ErrNotExist // 事前定義されたエラー err2 := fmt.Errorf("operation failed: %w", err1) if errors.Is(err2, os.ErrNotExist) { fmt.Println("The file does not exist.") }
- `errors.As`: エラーチェーンを辿り、特定の型のエラーが含まれているか確認し、その値を抽出。
type MyError struct { Code int Msg string } func (e *MyError) Error() string { return fmt.Sprintf("Error %d: %s", e.Code, e.Msg) } func doSomething() error { // 何らかの処理... return fmt.Errorf("wrapping custom error: %w", &MyError{Code: 500, Msg: "internal server error"}) } err := doSomething() var myErr *MyError if errors.As(err, &myErr) { fmt.Printf("Caught MyError - Code: %d, Message: %s\n", myErr.Code, myErr.Msg) } else { fmt.Println("Caught a different error:", err) }
- `panic` と `recover`: 予期せぬ致命的なエラー(例: 配列の範囲外アクセス)で発生。`defer`と`recover`でパニックから回復可能だが、通常のエラー処理には使わないのがベストプラクティス。
func recoveryDemo() { defer func() { if r := recover(); r != nil { fmt.Println("Recovered from panic:", r) } }() fmt.Println("Calling dangerous function...") causePanic() fmt.Println("This line will not be executed if panic occurs and is recovered.") } func causePanic() { fmt.Println("About to panic!") panic("something bad happened") fmt.Println("This line will not be executed.") } recoveryDemo()
並行処理 (Concurrency) ⚡
- Goroutine (ゴルーチン): `go`キーワードで関数を非同期に実行。軽量なスレッドのようなもの。
func say(s string) { for i := 0; i < 3; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("World") // 新しいGoroutineでsay("World")を実行 say("Hello") // 現在のGoroutineでsay("Hello")を実行 // main関数が終了すると他のGoroutineも終了するため、少し待つ // time.Sleep(500 * time.Millisecond) // より良い方法はsync.WaitGroupを使う }
- Channel (チャネル): Goroutine間でデータを送受信するためのパイプ。型を持つ。
// チャネル作成 (バッファなし) ch := make(chan string) go func() { time.Sleep(1 * time.Second) ch <- "Data from goroutine" // チャネルにデータを送信 (受信されるまでブロック) }() fmt.Println("Waiting for data...") msg := <-ch // チャネルからデータを受信 (データが来るまでブロック) fmt.Println("Received:", msg) // バッファ付きチャネル (指定サイズまでブロックせずに送信可能) bufCh := make(chan int, 2) bufCh <- 1 // ブロックしない bufCh <- 2 // ブロックしない // bufCh <- 3 // バッファが満杯なのでブロックする fmt.Println(<-bufCh) // 1 fmt.Println(<-bufCh) // 2
- `select`: 複数のチャネル操作を待機し、最初に準備ができた操作を実行。
c1 := make(chan string) c2 := make(chan string) go func() { time.Sleep(1 * time.Second) c1 <- "one" }() go func() { time.Sleep(2 * time.Second) c2 <- "two" }() // 2つのGoroutineからの応答を待つ for i := 0; i < 2; i++ { select { case msg1 := <-c1: fmt.Println("Received from c1:", msg1) case msg2 := <-c2: fmt.Println("Received from c2:", msg2) case <-time.After(3 * time.Second): // タイムアウト fmt.Println("Timeout waiting for messages") return // default: // 他に準備ができているチャネルがない場合に実行 (ノンブロッキング) // fmt.Println("No messages ready yet") // time.Sleep(100 * time.Millisecond) } }
- `close`: チャネルを閉じる。送信側が行う。受信側はチャネルが閉じられたことを検知できる。
jobs := make(chan int, 5) done := make(chan bool) go func() { for { j, more := <-jobs // moreはチャネルが開いているかを示す if more { fmt.Println("Received job", j) } else { fmt.Println("Received all jobs") done <- true return } } }() for j := 1; j <= 3; j++ { jobs <- j fmt.Println("Sent job", j) } close(jobs) // これ以上送信しないことを示す fmt.Println("Sent all jobs and closed channel") <-done // worker goroutineの終了を待つ
- `sync.Mutex`: 複数のGoroutineから共有リソースへのアクセスを排他制御(ロック)。
var counter int var mu sync.Mutex var wg sync.WaitGroup func incrementCounter() { defer wg.Done() for i := 0; i < 1000; i++ { mu.Lock() // ロックを取得 counter++ mu.Unlock() // ロックを解放 } } func main() { numGoroutines := 5 wg.Add(numGoroutines) for i := 0; i < numGoroutines; i++ { go incrementCounter() } wg.Wait() // 全てのGoroutineの終了を待つ fmt.Println("Final Counter:", counter) // 期待値: 5000 }
- `sync.WaitGroup`: 複数のGoroutineの終了を待機。
// 上記Mutexの例を参照 // 1. WaitGroup変数を宣言: var wg sync.WaitGroup // 2. Goroutine起動前に wg.Add(1) でカウンタを増やす // 3. Goroutine内の処理終了時に defer wg.Done() でカウンタを減らす // 4. メインGoroutineで wg.Wait() でカウンタが0になるのを待つ
- `context`: リクエストスコープの値、キャンセル信号、デッドラインをGoroutine間で伝播させる。
func worker(ctx context.Context, id int) { for { select { case <-ctx.Done(): // 親コンテキストからのキャンセル通知を待つ fmt.Printf("Worker %d: stopping due to cancellation: %v\n", id, ctx.Err()) return default: fmt.Printf("Worker %d: working...\n", id) time.Sleep(500 * time.Millisecond) // ここで ctx.Value("requestID") のような値を使うこともできる } } } func main() { // タイムアウト付きのコンテキストを作成 (例: 2秒後にキャンセル) // ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // キャンセル関数付きのコンテキストを作成 ctx, cancel := context.WithCancel(context.Background()) defer cancel() // main関数終了時に必ずキャンセルを呼ぶ // リクエストIDなどの値を付与する場合 // requestID := "req-123" // ctx = context.WithValue(ctx, "requestID", requestID) go worker(ctx, 1) go worker(ctx, 2) // 3秒後にキャンセルシグナルを送る time.Sleep(3 * time.Second) fmt.Println("Main: sending cancellation signal...") cancel() // Workerが終了するのを少し待つ time.Sleep(1 * time.Second) fmt.Println("Main: finished.") }
- **並行処理パターン例:**
- **Generator:** チャネルを使って一連の値を生成する関数。
- **Worker Pool:** 固定数のワーカーGoroutineでタスクキューを処理する。
- **Pipeline:** 複数のステージ(Goroutine)をチャネルで繋ぎ、データを段階的に処理する。
- **Fan-In/Fan-Out:** 複数のGoroutineにタスクを分散(Fan-Out)し、結果を一つのチャネルに集約(Fan-In)。
- **Semaphore:** 同時にアクセスできるGoroutineの数を制限する。
パッケージ管理 (Go Modules) 📦
- モジュールの初期化: プロジェクトルートで実行。`go.mod`ファイルが生成される。
go mod init example.com/myproject
- 依存パッケージの追加: コード内で`import`し、`go build`や`go test`を実行すると自動的に`go.mod`と`go.sum`に追加される。明示的に追加・更新する場合は`go get`。
# 最新バージョンを追加 go get github.com/gin-gonic/gin # 特定のバージョンを追加 go get github.com/gin-gonic/gin@v1.7.7 # バージョンを更新 go get -u github.com/gin-gonic/gin # 全ての依存関係を更新 go get -u ./...
- 依存関係の整理: `go.mod`を整理し、不要な依存を削除。`go.sum`も更新される。
go mod tidy
- 依存関係のダウンロード:
go mod download
- 依存関係の確認:
go list -m all
- ベンダーディレクトリの使用: 依存関係をプロジェクト内の`vendor`ディレクトリにコピー。ビルド時にここが参照される。
go mod vendor
テスト (Testing) 🧪
- ファイル名と関数名の規約:
- テストファイル: `*_test.go`
- テスト関数: `func TestXxx(*testing.T)` (Xxxはテスト対象の関数名など、大文字で始まる)
- ベンチマーク関数: `func BenchmarkXxx(*testing.B)`
- Example関数: `func ExampleXxx()`
- 基本的なテスト関数: `testing.T`を使ってエラー報告やテスト終了を行う。
// mymath/mymath.go package mymath func Add(a, b int) int { return a + b } // mymath/mymath_test.go package mymath import "testing" func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5 if result != expected { t.Errorf("Add(2, 3) = %d; want %d", result, expected) // エラーを報告しテスト続行 // t.Fatalf("Add(2, 3) = %d; want %d", result, expected) // エラーを報告しテスト終了 } }
- テストの実行:
# カレントディレクトリのテストを実行 go test # 詳細表示 (-v) go test -v # 特定のテスト関数を実行 (-run 正規表現) go test -v -run TestAdd # カバレッジ計測 go test -cover go test -coverprofile=coverage.out go tool cover -html=coverage.out # HTMLで結果表示
- テーブル駆動テスト: 複数のテストケースをまとめて記述する一般的なパターン。
func TestAddTableDriven(t *testing.T) { testCases := []struct { name string // テストケース名 (オプション) a, b int expected int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -1, -5, -6}, {"zero", 0, 0, 0}, {"positive and negative", 10, -3, 7}, } for _, tc := range testCases { // t.Run でサブテストを作成 (go test -run TestAddTableDriven/negative_numbers のように実行可能) t.Run(tc.name, func(t *testing.T) { result := Add(tc.a, tc.b) if result != tc.expected { t.Errorf("Add(%d, %d) = %d; want %d", tc.a, tc.b, result, tc.expected) } }) } }
- ベンチマークテスト: コードのパフォーマンスを測定。
func BenchmarkAdd(b *testing.B) { // b.N はベンチマークフレームワークによって自動的に調整される実行回数 for i := 0; i < b.N; i++ { Add(100, 200) } }
# ベンチマーク実行 (-bench 正規表現) go test -bench . go test -bench BenchmarkAdd
- Example関数: 関数の使用例を示し、godocに表示される。出力がコメントと一致するかテストされる。
func ExampleAdd() { sum := Add(1, 5) fmt.Println(sum) // Output: 6 }
- ヘルパー関数とセットアップ/ティアダウン:
- `t.Helper()`: テストヘルパー関数内で呼び出すと、エラー発生時のファイル/行番号がヘルパー呼び出し元になる。
- `TestMain`: パッケージ内のテスト実行前後に一度だけ実行される特殊な関数。DB接続や一時ファイル作成などに使う。
func TestMain(m *testing.M) { fmt.Println("Setting up tests...") // セットアップ処理 exitCode := m.Run() // パッケージ内のテストを実行 fmt.Println("Tearing down tests...") // ティアダウン処理 os.Exit(exitCode) }
- HTTPテスト: `net/http/httptest`パッケージでHTTPハンドラやクライアントをテスト。
func MyHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello, test!") } func TestMyHandler(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/test", nil) rr := httptest.NewRecorder() // http.ResponseWriterのモック handler := http.HandlerFunc(MyHandler) handler.ServeHTTP(rr, req) // ステータスコードをチェック if status := rr.Code; status != http.StatusOK { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK) } // レスポンスボディをチェック expected := "Hello, test!\n" if rr.Body.String() != expected { t.Errorf("handler returned unexpected body: got %v want %v", rr.Body.String(), expected) } }
- モックとスタブ: 外部依存(DB, APIなど)をテストダブルに置き換える。インターフェースを活用すると容易になる。`gomock`などのライブラリも利用される。
ファイル操作 (File I/O) 📄
目的 | パッケージ | 関数/メソッド | コード例 | 備考 |
---|---|---|---|---|
ファイル全体を読み込む (推奨) | `os` | `ReadFile` |
|
Go 1.16+。シンプルでメモリ効率が良い。 |
ファイル全体を書き込む (推奨) | `os` | `WriteFile` |
|
Go 1.16+。ファイルが存在しない場合は作成、存在する場合は上書き。 |
ファイルを開く (読み取り) | `os` | `Open` |
|
読み取り専用 (`O_RDONLY`)。`defer file.Close()`を忘れずに。 |
ファイルを作成/開く (書き込み) | `os` | `Create` |
|
書き込み用に開く (`O_WRONLY|O_CREATE|O_TRUNC`)。存在する場合は内容を切り捨てる。 |
ファイルを開く (詳細指定) | `os` | `OpenFile` |
|
`flag`で開くモード (追記、読み書きなど)、`perm`でパーミッションを指定。 |
ファイルから読み込む (`*os.File`) | `os` | `(*File).Read` |
|
指定したバッファに読み込む。EOF (End Of File) はエラーではない場合がある。 |
ファイルへ書き込む (`*os.File`) | `os` | `(*File).Write` |
|
バイトスライスを書き込む。 |
ファイルへ文字列を書き込む (`*os.File`) | `os` | `(*File).WriteString` |
|
`Write([]byte(s))`の効率的な代替。 |
バッファリング読み込み (行単位など) | `bufio` | `NewScanner` |
|
大きなファイルを効率的に処理。区切り文字の変更も可能。 |
バッファリング書き込み | `bufio` | `NewWriter` |
|
書き込み回数を減らし効率化。最後に`Flush()`が必要。 |
ファイル/ディレクトリの情報を取得 | `os` | `Stat`, `Lstat` |
|
`Lstat`はシンボリックリンク自体の情報を返す。 |
ファイル/ディレクトリの存在確認 | `os` | `Stat`, `IsNotExist` |
|
`Stat`のエラーを`errors.Is(err, os.ErrNotExist)`でチェックするのも一般的。 |
ディレクトリ作成 | `os` | `Mkdir`, `MkdirAll` |
|
`MkdirAll`は存在していてもエラーにならない。 |
ファイル/ディレクトリの削除 | `os` | `Remove`, `RemoveAll` |
|
`RemoveAll`は慎重に使用。 |
ファイル/ディレクトリのリネーム/移動 | `os` | `Rename` |
|
同じファイルシステム内でのみ動作。 |
JSON処理 🧩
- Goの構造体 → JSON文字列 (エンコード/マーシャリング): `encoding/json.Marshal`
type Config struct { Server string `json:"serverAddress"` // タグでJSONキー名を指定 Port int `json:"port"` Enabled bool `json:"enabled"` Users []string `json:"users,omitempty"` // omitempty: スライスがnilか空なら出力しない Password string `json:"-"` // -: このフィールドはJSONに含めない } conf := Config{ Server: "localhost", Port: 8080, Enabled: true, // Users: []string{"admin", "guest"}, Password: "secret", } jsonData, err := json.Marshal(conf) // jsonData, err := json.MarshalIndent(conf, "", " ") // 整形して出力する場合 if err != nil { log.Fatal("JSON marshal error:", err) } fmt.Println(string(jsonData)) // 出力例 (Marshal): {"serverAddress":"localhost","port":8080,"enabled":true} // 出力例 (MarshalIndent): // { // "serverAddress": "localhost", // "port": 8080, // "enabled": true // }
- JSON文字列 → Goの構造体 (デコード/アンマーシャリング): `encoding/json.Unmarshal`
jsonString := `{"serverAddress":"192.168.1.100","port":9000,"enabled":false,"users":["root"],"unknownField":"ignore"}` var loadedConf Config err := json.Unmarshal([]byte(jsonString), &loadedConf) // ポインタを渡す if err != nil { log.Fatal("JSON unmarshal error:", err) } fmt.Printf("Loaded Config: %+v\n", loadedConf) // 出力例: Loaded Config: {Server:192.168.1.100 Port:9000 Enabled:false Users:[root] Password:}
- JSON文字列 → 不明な構造 (`map[string]interface{}`):
jsonString := `{"name":"Alice","age":30,"city":"New York","extra":{"active":true}}` var data map[string]interface{} // mapまたはinterface{}にデコード err := json.Unmarshal([]byte(jsonString), &data) if err != nil { /*...*/ } name := data["name"].(string) // 型アサーションで値を取り出す age := data["age"].(float64) // JSONの数値はfloat64としてデコードされることが多い extraMap := data["extra"].(map[string]interface{}) isActive := extraMap["active"].(bool) fmt.Println(name, age, isActive) // Alice 30 true
- ストリーミングデコード (大きなJSONやリクエストボディ): `json.Decoder`
// http.Request.Body (io.Reader) などから読み込む場合 // dec := json.NewDecoder(r.Body) // err := dec.Decode(&myStruct) // ファイルから読み込む例 file, _ := os.Open("large.json") defer file.Close() dec := json.NewDecoder(file) // io.Readerを渡す var v map[string]interface{} // または具体的な構造体 err := dec.Decode(&v) // JSONオブジェクトを1つデコード if err != nil { /*...*/ } fmt.Println(v)
- ストリーミングエンコード: `json.Encoder`
// http.ResponseWriter (io.Writer) などに書き込む場合 // enc := json.NewEncoder(w) // err := enc.Encode(myStruct) // ファイルに書き込む例 file, _ := os.Create("output_stream.json") defer file.Close() enc := json.NewEncoder(file) // io.Writerを渡す enc.SetIndent("", " ") // 整形出力する場合 data := map[string]string{"hello": "world"} err := enc.Encode(data) // JSONオブジェクトを書き込む if err != nil { /*...*/ }
ネットワーク (Networking) 🌐
- シンプルなHTTP GETリクエスト: `net/http.Get`
resp, err := http.Get("https://example.com") if err != nil { log.Fatal("HTTP GET error:", err) } defer resp.Body.Close() // レスポンスボディは必ず閉じる fmt.Println("Status Code:", resp.StatusCode) if resp.StatusCode == http.StatusOK { bodyBytes, err := io.ReadAll(resp.Body) if err != nil { log.Fatal("Error reading response body:", err) } fmt.Println("Response Body:\n", string(bodyBytes)) }
- HTTPリクエストのカスタマイズ (`http.Client`, `http.NewRequest`):
// カスタムクライアント (タイムアウト設定など) client := &http.Client{ Timeout: 10 * time.Second, } // リクエストを作成 (メソッド, URL, ボディを指定) reqBody := strings.NewReader(`{"key":"value"}`) // io.Reader req, err := http.NewRequest(http.MethodPost, "https://httpbin.org/post", reqBody) if err != nil { log.Fatal("Error creating request:", err) } // ヘッダーを設定 req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer your_token") req.Header.Set("User-Agent", "MyGoApp/1.0") // リクエストを実行 resp, err := client.Do(req) if err != nil { log.Fatal("HTTP Do error:", err) } defer resp.Body.Close() // レスポンス処理 (上記Getの例と同様) fmt.Println("Status Code:", resp.StatusCode) bodyBytes, _ := io.ReadAll(resp.Body) fmt.Println(string(bodyBytes))
- シンプルなHTTPサーバー: `net/http.ListenAndServe`, `http.HandleFunc`
func helloHandler(w http.ResponseWriter, r *http.Request) { // w: レスポンスを書き込むためのライター // r: リクエスト情報を持つポインタ fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:]) // URLパスから名前を取得 (例: /Alice -> Hello, Alice!) } func headersHandler(w http.ResponseWriter, r *http.Request) { for name, headers := range r.Header { for _, h := range headers { fmt.Fprintf(w, "%v: %v\n", name, h) } } } func main() { // URLパスとハンドラ関数を登録 http.HandleFunc("/hello/", helloHandler) http.HandleFunc("/headers", headersHandler) fmt.Println("Starting server on :8080") // サーバーを起動 (アドレスとデフォルトのServeMuxを指定) err := http.ListenAndServe(":8080", nil) if err != nil { log.Fatal("ListenAndServe error: ", err) } }
- HTTPサーバーのカスタマイズ (`http.Server`): タイムアウト設定など詳細な制御。
func main() { mux := http.NewServeMux() // 新しいServeMuxを作成 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Custom Server Root") }) server := &http.Server{ Addr: ":8081", Handler: mux, // カスタムServeMuxを指定 ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } fmt.Println("Starting custom server on :8081") err := server.ListenAndServe() if err != nil && err != http.ErrServerClosed { log.Fatal("Server error: ", err) } }
- TCPサーバー (低レベル): `net.Listen`, `listener.Accept`
func handleConnection(conn net.Conn) { defer conn.Close() fmt.Printf("Accepted connection from %s\n", conn.RemoteAddr()) buf := make([]byte, 1024) for { n, err := conn.Read(buf) if err != nil { if err != io.EOF { fmt.Println("Read error:", err) } break // EOF またはエラーでループ終了 } fmt.Printf("Received: %s", string(buf[:n])) // エコーバック _, err = conn.Write([]byte("Echo: " + string(buf[:n]))) if err != nil { fmt.Println("Write error:", err) break } } fmt.Printf("Connection closed from %s\n", conn.RemoteAddr()) } func main() { listener, err := net.Listen("tcp", ":8888") if err != nil { log.Fatal("Listen error:", err) } defer listener.Close() fmt.Println("TCP Server listening on :8888") for { conn, err := listener.Accept() // 新しい接続を待機 if err != nil { fmt.Println("Accept error:", err) continue // エラーが発生しても次の接続を待つ } go handleConnection(conn) // 各接続を個別のGoroutineで処理 } }
- TCPクライアント (低レベル): `net.Dial`
func main() { conn, err := net.Dial("tcp", "localhost:8888") if err != nil { log.Fatal("Dial error:", err) } defer conn.Close() fmt.Println("Connected to server.") // サーバーにメッセージを送信 _, err = conn.Write([]byte("Hello from client!\n")) if err != nil { log.Fatal("Write error:", err) } // サーバーからの応答を受信 buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { log.Fatal("Read error:", err) } fmt.Printf("Server response: %s", string(buf[:n])) }