コンテンツにスキップ

Go/Standard library

出典: フリー教科書『ウィキブックス(Wikibooks)』
< Go

1. はじめに

[編集]

Go言語は、シンプルで効率的なプログラミングを実現するために設計された言語です。その強力な標準ライブラリは、Goの魅力の一つであり、多くのタスクをサードパーティライブラリに依存せずに実現できます。この章では、Goの標準ライブラリを活用して、実践的なプログラミングスキルを身につけることを目指します。

1.1 標準ライブラリの重要性

[編集]

Goの標準ライブラリは、以下のような特徴を持っています。

  • 豊富な機能: ファイル操作、ネットワーク通信、データのエンコード/デコード、並行処理など、多岐にわたる機能を提供します。
  • 一貫性のある設計: すべてのパッケージはGoの設計思想に基づいており、シンプルで使いやすいAPIを提供します。
  • 高品質なドキュメント: 公式ドキュメントは詳細でわかりやすく、開発者が迅速に学ぶための強力なリソースです。

標準ライブラリを学ぶことで、Go言語の真の力を引き出すことができます。

2. 標準ライブラリの基本

[編集]

標準ライブラリを使い始める前に、基本的な概念と使い方を学びましょう。

2.1 パッケージのインポートと使い方

[編集]

Goでは、標準ライブラリのパッケージをimport文でインポートして使用します。

package main

import (
    "fmt"  // フォーマットされた入出力を提供
    "os"   // オペレーティングシステム機能を提供
)

func main() {
    fmt.Println("Hello, World!")
    fmt.Println("現在のユーザー:", os.Getenv("USER"))
}
  • import文で必要なパッケージを指定します。
  • 複数のパッケージをインポートする場合は、括弧()で囲みます。

2.2 ドキュメントの読み方

[編集]

Goの標準ライブラリは、高品質なドキュメントを提供しています。以下の方法でドキュメントを参照できます。

  • go docコマンド: ターミナルでgo doc <パッケージ名>を実行します。
  go doc fmt.Println
  • pkg.go.dev: ブラウザで標準ライブラリのドキュメントを参照できます。

2.3 標準ライブラリの設計思想

[編集]

Goの標準ライブラリは、以下の設計思想に基づいています。

  • シンプルさ: 必要最小限の機能を提供し、複雑さを排除します。
  • 一貫性: すべてのパッケージは同じ設計原則に従っています。
  • 実用性: 実際の開発で役立つ機能を優先します。

これらの思想を理解することで、標準ライブラリを効果的に活用できます。

3. 入出力とファイル操作

[編集]

入出力(I/O)操作は、プログラミングの基本的なタスクの一つです。Goの標準ライブラリは、fmtioioutilosパッケージを通じて、強力なI/O機能を提供します。

3.1 fmtパッケージ: フォーマットされた入出力

[編集]

fmtパッケージは、フォーマットされた入出力を提供します。特に、PrintfScanfなどの関数は、C言語のprintfscanfに似た使い勝手を持っています。

3.1.1 基本的な出力
[編集]
package main

import "fmt"

func main() {
    fmt.Println("Hello, World!") // 改行付き出力
    fmt.Print("Hello, ")         // 改行なし出力
    fmt.Print("Go!\n")           // 改行を明示
    fmt.Printf("数値: %d, 文字列: %s\n", 42, "Go") // フォーマット指定
}
  • Println: 改行付きで出力します。
  • Print: 改行なしで出力します。
  • Printf: 動詞( verbs )(%d, %sなど)を使って出力します。
3.1.2 動詞( verbs )
[編集]
動詞( verbs )
指定子 説明
%v デフォルトフォーマット
%d 整数
%f 浮動小数点数
%s 文字列
%t 真偽値
%p ポインタ

3.2 ioioutilパッケージ: 基本的なI/O操作

[編集]

ioパッケージは、基本的なI/O操作を提供します。ReaderWriterインターフェースは、GoのI/O操作の中心です。

3.2.1 ReaderWriter
[編集]
package main

import (
    "io"
    "os"
)

func main() {
    // 標準入力から読み取り、標準出力に書き込む
    io.Copy(os.Stdout, os.Stdin)
}
  • io.Copy: ReaderからWriterにデータをコピーします。
3.2.2 ファイルの読み書き
[編集]
package main

import (
    "io/ioutil"
    "log"
)

func main() {
    // ファイルにデータを書き込む
    err := ioutil.WriteFile("example.txt", []byte("Hello, Go!"), 0644)
    if err != nil {
        log.Fatal(err)
    }

    // ファイルからデータを読み取る
    data, err := ioutil.ReadFile("example.txt")
    if err != nil {
        log.Fatal(err)
    }
    log.Println(string(data))
}
  • ioutil.WriteFile: ファイルにデータを書き込みます。
  • ioutil.ReadFile: ファイルからデータを読み取ります。

3.3 osパッケージ: ファイル操作と環境変数

[編集]

osパッケージは、ファイル操作や環境変数の取得など、オペレーティングシステムとのやり取りを提供します。

3.3.1 ファイル操作
[編集]
package main

import (
    "log"
    "os"
)

func main() {
    // ファイルを開く
    file, err := os.Open("example.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()

    // ファイル情報を取得
    info, err := file.Stat()
    if err != nil {
        log.Fatal(err)
    }
    log.Println("ファイル名:", info.Name())
    log.Println("サイズ:", info.Size())
}
  • os.Open: ファイルを開きます。
  • file.Stat: ファイルの情報を取得します。
3.3.2 環境変数
[編集]
package main

import (
    "fmt"
    "os"
)

func main() {
    // 環境変数を取得
    user := os.Getenv("USER")
    fmt.Println("現在のユーザー:", user)
}
  • os.Getenv: 環境変数の値を取得します。

4. 文字列操作

[編集]

文字列操作は、プログラミングにおいて非常に重要なタスクです。Goの標準ライブラリは、stringsstrconvunicodeパッケージを通じて、文字列の検索、置換、分割、結合、変換などの機能を提供します。

4.1 stringsパッケージ: 文字列の操作

[編集]

stringsパッケージは、文字列の検索、置換、分割、結合などの操作を提供します。

4.1.1 文字列の検索
[編集]
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, Go!"

    // 文字列が含まれているか確認
    fmt.Println(strings.Contains(s, "Go"))  // true

    // 文字列の位置を検索
    fmt.Println(strings.Index(s, "Go"))    // 7

    // 文字列が特定の接頭辞/接尾辞で始まるか確認
    fmt.Println(strings.HasPrefix(s, "Hello")) // true
    fmt.Println(strings.HasSuffix(s, "Go!"))   // true
}
  • strings.Contains: 文字列が含まれているか確認します。
  • strings.Index: 文字列の位置を検索します。
  • strings.HasPrefix/strings.HasSuffix: 文字列が特定の接頭辞/接尾辞で始まるか確認します。
4.1.2 文字列の置換
[編集]
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "Hello, World!"

    // 文字列の置換
    fmt.Println(strings.Replace(s, "World", "Go", 1)) // Hello, Go!
}
  • strings.Replace: 文字列を置換します。
4.1.3 文字列の分割と結合
[編集]
package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "a,b,c"

    // 文字列を分割
    parts := strings.Split(s, ",")
    fmt.Println(parts) // [a b c]

    // 文字列を結合
    joined := strings.Join(parts, "-")
    fmt.Println(joined) // a-b-c
}
  • strings.Split: 文字列を分割します。
  • strings.Join: 文字列を結合します。

4.2 strconvパッケージ: 文字列と数値の変換

[編集]

strconvパッケージは、文字列と数値の相互変換を提供します。

4.2.1 文字列から数値への変換
[編集]
package main

import (
    "fmt"
    "strconv"
)

func main() {
    s := "42"

    // 文字列を整数に変換
    i, err := strconv.Atoi(s)
    if err != nil {
        fmt.Println("変換エラー:", err)
    } else {
        fmt.Println(i) // 42
    }

    // 文字列を浮動小数点数に変換
    f, err := strconv.ParseFloat("3.14", 64)
    if err != nil {
        fmt.Println("変換エラー:", err)
    } else {
        fmt.Println(f) // 3.14
    }
}
  • strconv.Atoi: 文字列を整数に変換します。
  • strconv.ParseFloat: 文字列を浮動小数点数に変換します。
4.2.2 数値から文字列への変換
[編集]
package main

import (
    "fmt"
    "strconv"
)

func main() {
    i := 42
    f := 3.14

    // 整数を文字列に変換
    s1 := strconv.Itoa(i)
    fmt.Println(s1) // "42"

    // 浮動小数点数を文字列に変換
    s2 := strconv.FormatFloat(f, 'f', 2, 64)
    fmt.Println(s2) // "3.14"
}
  • strconv.Itoa: 整数を文字列に変換します。
  • strconv.FormatFloat: 浮動小数点数を文字列に変換します。

4.3 unicodeパッケージ: ユニコード文字の操作

[編集]

unicodeパッケージは、ユニコード文字の操作と検証を提供します。

4.3.1 文字の検証
[編集]
package main

import (
    "fmt"
    "unicode"
)

func main() {
    r := 'A'

    // 文字が大文字か確認
    fmt.Println(unicode.IsUpper(r)) // true

    // 文字が数字か確認
    fmt.Println(unicode.IsDigit(r)) // false
}
  • unicode.IsUpper: 文字が大文字か確認します。
  • unicode.IsDigit: 文字が数字か確認します。
4.3.2 文字の変換
[編集]
package main

import (
    "fmt"
    "unicode"
)

func main() {
    r := 'a'

    // 文字を大文字に変換
    fmt.Println(string(unicode.ToUpper(r))) // "A"
}
  • unicode.ToUpper: 文字を大文字に変換します。

5. データのエンコードとシリアライゼーション

[編集]

データのエンコードとシリアライゼーションは、異なるシステム間でデータを交換する際に重要な役割を果たします。Goの標準ライブラリは、JSON、XML、CSVなどの形式を扱うためのパッケージを提供しています。

5.1 encoding/jsonパッケージ: JSONのエンコードとデコード

[編集]

JSON(JavaScript Object Notation)は、軽量で人間が読みやすいデータ交換フォーマットです。Goのencoding/jsonパッケージは、JSONデータのエンコード(Goのデータ構造からJSONへの変換)とデコード(JSONからGoのデータ構造への変換)を簡単に行えます。

5.1.1 構造体からJSONへのエンコード
[編集]
package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name  string <code>json:"name"</code>
    Age   int    <code>json:"age"</code>
    Email string <code>json:"email,omitempty"</code>
}

func main() {
    p := Person{Name: "Alice", Age: 30}

    // 構造体をJSONにエンコード
    jsonData, err := json.Marshal(p)
    if err != nil {
        fmt.Println("エンコードエラー:", err)
    } else {
        fmt.Println(string(jsonData)) // {"name":"Alice","age":30}
    }
}
  • json.Marshal: Goのデータ構造をJSONにエンコードします。
  • json:"..."要素: フィールド名をカスタマイズしたり、空のフィールドを省略したりできます。
5.1.2 JSONから構造体へのデコード
[編集]
package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name  string <code>json:"name"</code>
    Age   int    <code>json:"age"</code>
    Email string <code>json:"email,omitempty"</code>
}

func main() {
    jsonData := <code>{"name":"Alice","age":30}</code>

    // JSONを構造体にデコード
    var p Person
    err := json.Unmarshal([]byte(jsonData), &p)
    if err != nil {
        fmt.Println("デコードエラー:", err)
    } else {
        fmt.Printf("%+v\n", p) // {Name:Alice Age:30 Email:}
    }
}
  • json.Unmarshal: JSONをGoのデータ構造にデコードします。

5.2 encoding/xmlパッケージ: XMLのエンコードとデコード

[編集]

XML(Extensible Markup Language)は、構造化されたデータを表現するためのフォーマットです。Goのencoding/xmlパッケージは、XMLデータのエンコードとデコードをサポートします。

5.2.1 構造体からXMLへのエンコード
[編集]
package main

import (
    "encoding/xml"
    "fmt"
)

type Person struct {
    XMLName xml.Name <code>xml:"person"</code>
    Name    string   <code>xml:"name"</code>
    Age     int      <code>xml:"age"</code>
    Email   string   <code>xml:"email,omitempty"</code>
}

func main() {
    p := Person{Name: "Alice", Age: 30}

    // 構造体をXMLにエンコード
    xmlData, err := xml.MarshalIndent(p, "", "  ")
    if err != nil {
        fmt.Println("エンコードエラー:", err)
    } else {
        fmt.Println(string(xmlData))
        // <person>
        //   <name>Alice</name>
        //   <age>30</age>
        // </person>
    }
}
  • xml.MarshalIndent: Goのデータ構造をXMLにエンコードし、インデントを追加します。
5.2.2 XMLから構造体へのデコード
[編集]
package main

import (
    "encoding/xml"
    "fmt"
)

type Person struct {
    XMLName xml.Name <code>xml:"person"</code>
    Name    string   <code>xml:"name"</code>
    Age     int      <code>xml:"age"</code>
    Email   string   <code>xml:"email,omitempty"</code>
}

func main() {
    xmlData := `
    <person>
        <name>Alice</name>
        <age>30</age>
    </person>`

    // XMLを構造体にデコード
    var p Person
    err := xml.Unmarshal([]byte(xmlData), &p)
    if err != nil {
        fmt.Println("デコードエラー:", err)
    } else {
        fmt.Printf("%+v\n", p) // {XMLName:{Space: Local:person} Name:Alice Age:30 Email:}
    }
}
  • xml.Unmarshal: XMLをGoのデータ構造にデコードします。

5.3 encoding/csvパッケージ: CSVファイルの読み書き

[編集]

CSV(Comma-Separated Values)は、表形式のデータをテキストファイルとして保存するためのフォーマットです。Goのencoding/csvパッケージは、CSVファイルの読み書きをサポートします。

5.3.1 CSVファイルの読み取り
[編集]
package main

import (
    "encoding/csv"
    "fmt"
    "os"
)

func main() {
    // CSVファイルを開く
    file, err := os.Open("data.csv")
    if err != nil {
        fmt.Println("ファイルオープンエラー:", err)
        return
    }
    defer file.Close()

    // CSVリーダーを作成
    reader := csv.NewReader(file)

    // CSVデータを読み取る
    records, err := reader.ReadAll()
    if err != nil {
        fmt.Println("CSV読み取りエラー:", err)
        return
    }

    // 読み取ったデータを表示
    for _, record := range records {
        fmt.Println(record)
    }
}
  • csv.NewReader: CSVリーダーを作成します。
  • reader.ReadAll: CSVデータをすべて読み取ります。
5.3.2 CSVファイルの書き込み
[編集]
package main

import (
    "encoding/csv"
    "os"
)

func main() {
    // CSVファイルを作成
    file, err := os.Create("output.csv")
    if err != nil {
        fmt.Println("ファイル作成エラー:", err)
        return
    }
    defer file.Close()

    // CSVライターを作成
    writer := csv.NewWriter(file)
    defer writer.Flush()

    // CSVデータを書き込む
    records := [][]string{
        {"name", "age", "email"},
        {"Alice", "30", "alice@example.com"},
        {"Bob", "25", "bob@example.com"},
    }
    for _, record := range records {
        if err := writer.Write(record); err != nil {
            fmt.Println("CSV書き込みエラー:", err)
            return
        }
    }
}
  • csv.NewWriter: CSVライターを作成します。
  • writer.Write: CSVデータを書き込みます。

6. ネットワークプログラミング

[編集]

Go言語は、ネットワークプログラミングを強力にサポートしています。標準ライブラリのnetおよびnet/httpパッケージを使うことで、TCP/UDP通信やHTTPリクエストの送受信を簡単に実装できます。

6.1 netパッケージ: TCP/UDP通信

[編集]

netパッケージは、TCPやUDPを使った低レベルのネットワーク通信を提供します。

6.1.1 TCPサーバーの実装
[編集]

以下は、TCPサーバーの簡単な例です。クライアントからの接続を受け付け、メッセージを返します。

package main

import (
    "bufio"
    "fmt"
    "net"
    "strings"
)

func handleConnection(conn net.Conn) {
    defer conn.Close() // 関数終了時に接続を閉じる

    // クライアントからのメッセージを読み取る
    reader := bufio.NewReader(conn)
    message, err := reader.ReadString('\n')
    if err != nil {
        fmt.Println("読み取りエラー:", err)
        return
    }

    // メッセージを処理して返信
    fmt.Printf("受信: %s", message)
    conn.Write([]byte(strings.ToUpper(message))) // 大文字に変換して返信
}

func main() {
    // TCPサーバーを起動
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        fmt.Println("サーバー起動エラー:", err)
        return
    }
    defer listener.Close()
    fmt.Println("サーバーを起動しました。ポート: 8080")

    // クライアントからの接続を待機
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("接続エラー:", err)
            continue
        }
        go handleConnection(conn) // ゴルーチンで接続を処理
    }
}
  • net.Listen: TCPサーバーを起動します。
  • listener.Accept: クライアントからの接続を待機します。
  • go handleConnection(conn): ゴルーチンを使って並行処理を行います。
6.1.2 TCPクライアントの実装
[編集]

以下は、TCPサーバーに接続してメッセージを送信するクライアントの例です。

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
)

func main() {
    // サーバーに接続
    conn, err := net.Dial("tcp", "localhost:8080")
    if err != nil {
        fmt.Println("接続エラー:", err)
        return
    }
    defer conn.Close()

    // メッセージを送信
    fmt.Print("メッセージを入力: ")
    scanner := bufio.NewScanner(os.Stdin)
    if scanner.Scan() {
        message := scanner.Text()
        conn.Write([]byte(message + "\n"))
    }

    // サーバーからの返信を読み取る
    reader := bufio.NewReader(conn)
    response, err := reader.ReadString('\n')
    if err != nil {
        fmt.Println("読み取りエラー:", err)
        return
    }
    fmt.Printf("サーバーからの返信: %s", response)
}
  • net.Dial: サーバーに接続します。
  • conn.Write: メッセージを送信します。
  • reader.ReadString: サーバーからの返信を読み取ります。

6.2 net/httpパッケージ: HTTP通信

[編集]

net/httpパッケージは、HTTPクライアントとサーバーの機能を提供します。

6.2.1 HTTPサーバーの実装
[編集]

以下は、簡単なHTTPサーバーの例です。

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    // ハンドラを登録
    http.HandleFunc("/hello", helloHandler)

    // サーバーを起動
    fmt.Println("サーバーを起動しました。ポート: 8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        fmt.Println("サーバー起動エラー:", err)
    }
}
  • http.HandleFunc: パスとハンドラ関数を登録します。
  • http.ListenAndServe: HTTPサーバーを起動します。
6.2.2 HTTPクライアントの実装
[編集]

以下は、HTTPリクエストを送信してレスポンスを取得するクライアントの例です。

package main

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

func main() {
    // HTTP GETリクエストを送信
    resp, err := http.Get("http://localhost:8080/hello")
    if err != nil {
        fmt.Println("リクエストエラー:", err)
        return
    }
    defer resp.Body.Close()

    // レスポンスを読み取る
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("読み取りエラー:", err)
        return
    }
    fmt.Println("レスポンス:", string(body))
}
  • http.Get: HTTP GETリクエストを送信します。
  • ioutil.ReadAll: レスポンスボディを読み取ります。

6.3 net/urlパッケージ: URLの解析と操作

[編集]

net/urlパッケージは、URLの解析と操作を提供します。

6.3.1 URLの解析
[編集]
package main

import (
    "fmt"
    "net/url"
)

func main() {
    u, err := url.Parse("https://example.com/path?query=value#fragment")
    if err != nil {
        fmt.Println("URL解析エラー:", err)
        return
    }

    fmt.Println("スキーム:", u.Scheme)
    fmt.Println("ホスト:", u.Host)
    fmt.Println("パス:", u.Path)
    fmt.Println("クエリ:", u.RawQuery)
    fmt.Println("フラグメント:", u.Fragment)
}
  • url.Parse: URLを解析します。
6.3.2 URLの操作
[編集]
package main

import (
    "fmt"
    "net/url"
)

func main() {
    u := &url.URL{
        Scheme:   "https",
        Host:     "example.com",
        Path:     "/path",
        RawQuery: "query=value",
        Fragment: "fragment",
    }
    fmt.Println("URL:", u.String())
}
  • url.URL: URLを構築します。

7. 並行処理

[編集]

Go言語は、並行処理を簡単かつ効率的に実現するための強力な機能を提供しています。標準ライブラリのsynccontextatomicパッケージを使うことで、ゴルーチン間の同期やキャンセル処理を効果的に管理できます。

7.1 ゴルーチン: 軽量なスレッド

[編集]

Go言語の並行処理の基本は、ゴルーチンです。ゴルーチンは、軽量なスレッドとして並行処理を実現します。

7.1.1 ゴルーチンの起動
[編集]

以下は、ゴルーチンを起動する簡単な例です。

package main

import (
    "fmt"
    "time"
)

func printNumbers() {
    for i := 1; i <= 5; i++ {
        fmt.Println(i)
        time.Sleep(500 * time.Millisecond)
    }
}

func main() {
    // ゴルーチンを起動
    go printNumbers()

    // メインゴルーチンも並行して実行
    for i := 0; i < 3; i++ {
        fmt.Println("Main:", i)
        time.Sleep(1 * time.Second)
    }

    // ゴルーチンの終了を待つ
    time.Sleep(3 * time.Second)
}
  • go printNumbers(): ゴルーチンを起動します。
  • ゴルーチンは非同期で実行されるため、メインゴルーチンと並行して動作します。

7.2 syncパッケージ: 同期プリミティブ

[編集]

ゴルーチン間でデータを共有する場合、同期が必要です。syncパッケージは、同期プリミティブを提供します。

7.2.1 sync.WaitGroup
[編集]

sync.WaitGroupは、複数のゴルーチンの終了を待機するために使用します。

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, wg *sync.WaitGroup) {
    defer wg.Done() // ゴルーチン終了時に呼び出す

    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    var wg sync.WaitGroup

    for i := 1; i <= 3; i++ {
        wg.Add(1) // ゴルーチンを追加
        go worker(i, &wg)
    }

    wg.Wait() // すべてのゴルーチンの終了を待機
    fmt.Println("All workers done")
}
  • wg.Add(1): ゴルーチンを追加します。
  • wg.Done(): ゴルーチンが終了したことを通知します。
  • wg.Wait(): すべてのゴルーチンの終了を待機します。
7.2.2 sync.Mutex
[編集]

sync.Mutexは、ゴルーチン間で共有リソースへのアクセスを同期するために使用します。

package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    counter int
    mutex   sync.Mutex
)

func increment(wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 1000; i++ {
        mutex.Lock() // ロック
        counter++
        mutex.Unlock() // アンロック
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }

    wg.Wait()
    fmt.Println("Counter:", counter) // 10000
}
  • mutex.Lock(): 共有リソースへのアクセスをロックします。
  • mutex.Unlock(): ロックを解除します。

7.3 contextパッケージ: キャンセルとタイムアウト

[編集]

contextパッケージは、ゴルーチンのキャンセルやタイムアウトを管理するための機能を提供します。

7.3.1 キャンセルの伝播
[編集]

以下は、contextを使ってキャンセルを伝播する例です。

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, wg *sync.WaitGroup) {
    defer wg.Done()

    for {
        select {
        case <-ctx.Done(): // キャンセルを検知
            fmt.Println("Worker canceled")
            return
        default:
            fmt.Println("Working...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithCancel(context.Background())

    wg.Add(1)
    go worker(ctx, &wg)

    time.Sleep(2 * time.Second)
    cancel() // キャンセルを通知

    wg.Wait()
    fmt.Println("Main done")
}
  • context.WithCancel: キャンセル可能なコンテキストを作成します。
  • ctx.Done(): キャンセルを検知します。
7.3.2 タイムアウトの設定
[編集]

以下は、contextを使ってタイムアウトを設定する例です。

package main

import (
    "context"
    "fmt"
    "time"
)

func worker(ctx context.Context, wg *sync.WaitGroup) {
    defer wg.Done()

    for {
        select {
        case <-ctx.Done(): // タイムアウトを検知
            fmt.Println("Worker timed out")
            return
        default:
            fmt.Println("Working...")
            time.Sleep(500 * time.Millisecond)
        }
    }
}

func main() {
    var wg sync.WaitGroup
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    wg.Add(1)
    go worker(ctx, &wg)

    wg.Wait()
    fmt.Println("Main done")
}
  • context.WithTimeout: タイムアウト付きのコンテキストを作成します。

7.4 atomicパッケージ: アトミック操作

[編集]

atomicパッケージは、アトミック操作を提供します。アトミック操作は、ロックを使わずに共有リソースを安全に操作するための機能です。

7.4.1 アトミックなカウンタ
[編集]

以下は、atomicを使ってカウンタを操作する例です。

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)

var counter int64

func increment(wg *sync.WaitGroup) {
    defer wg.Done()

    for i := 0; i < 1000; i++ {
        atomic.AddInt64(&counter, 1) // アトミックに加算
    }
}

func main() {
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go increment(&wg)
    }

    wg.Wait()
    fmt.Println("Counter:", counter) // 10000
}
  • atomic.AddInt64: アトミックに値を加算します。

8. 時間と日付

[編集]

Go言語の標準ライブラリは、時間と日付を扱うための強力な機能を提供しています。timeパッケージを使うことで、時間の取得、フォーマット、パース、タイマー、ティッカーなどの操作を簡単に行えます。

8.1 時間の取得

[編集]

現在の時刻を取得するには、time.Now関数を使用します。

8.1.1 現在の時刻を取得
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println("現在の時刻:", now)
}
  • time.Now: 現在の時刻を取得します。
8.1.2 時刻の構成要素
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println("年:", now.Year())
    fmt.Println("月:", now.Month())
    fmt.Println("日:", now.Day())
    fmt.Println("時:", now.Hour())
    fmt.Println("分:", now.Minute())
    fmt.Println("秒:", now.Second())
}
  • now.Year(): 年を取得します。
  • now.Month(): 月を取得します。
  • now.Day(): 日を取得します。
  • now.Hour(): 時を取得します。
  • now.Minute(): 分を取得します。
  • now.Second(): 秒を取得します。

8.2 時刻のフォーマットとパース

[編集]

Go言語では、時刻のフォーマットとパースに独自のレイアウトを使用します。

8.2.1 時刻のフォーマット
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println("フォーマットされた時刻:", now.Format("2006-01-02 15:04:05"))
}
  • now.Format: 時刻を指定したフォーマットで文字列に変換します。
  • 2006-01-02 15:04:05は、Goの時刻フォーマットのレイアウトです。
8.2.2 時刻のパース
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    layout := "2006-01-02 15:04:05"
    str := "2023-10-01 12:34:56"
    t, err := time.Parse(layout, str)
    if err != nil {
        fmt.Println("パースエラー:", err)
    } else {
        fmt.Println("パースされた時刻:", t)
    }
}
  • time.Parse: 文字列を時刻に変換します。

8.3 タイマーとティッカー

[編集]

timeパッケージは、タイマーとティッカーを提供します。タイマーは指定した時間後に一度だけ通知し、ティッカーは指定した間隔で繰り返し通知します。

8.3.1 タイマー
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    timer := time.NewTimer(2 * time.Second)
    <-timer.C // タイマーが通知するまで待機
    fmt.Println("2秒経過しました")
}
  • time.NewTimer: 指定した時間後に通知するタイマーを作成します。
  • <-timer.C: タイマーが通知するまで待機します。
8.3.2 ティッカー
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()

    for i := 0; i < 5; i++ {
        <-ticker.C // ティッカーが通知するまで待機
        fmt.Println("ティック:", i+1)
    }
}
  • time.NewTicker: 指定した間隔で通知するティッカーを作成します。
  • <-ticker.C: ティッカーが通知するまで待機します。

8.4 時間の計算

[編集]

timeパッケージは、時間の加算や減算を簡単に行えます。

8.4.1 時間の加算
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    future := now.Add(24 * time.Hour) // 24時間後
    fmt.Println("24時間後の時刻:", future)
}
  • now.Add: 指定した時間を加算します。
8.4.2 時間の減算
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    past := now.Add(-24 * time.Hour) // 24時間前
    fmt.Println("24時間前の時刻:", past)
}
  • now.Add: 負の値を指定して時間を減算します。
8.4.3 時間の差分
[編集]
package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    time.Sleep(2 * time.Second)
    end := time.Now()
    duration := end.Sub(start) // 時間の差分
    fmt.Println("経過時間:", duration)
}
  • end.Sub: 2つの時刻の差分を計算します。

9. データ構造とアルゴリズム

[編集]

Go言語の標準ライブラリは、基本的なデータ構造とアルゴリズムを提供しています。containersortmathパッケージを使うことで、リスト、ヒープ、リング、ソート、数学関数などを簡単に利用できます。

9.1 containerパッケージ: データ構造

[編集]

containerパッケージは、リスト、ヒープ、リングなどのデータ構造を提供します。

9.1.1 リスト
[編集]

container/listパッケージは、双方向リンクリストを提供します。

package main

import (
    "container/list"
    "fmt"
)

func main() {
    // リストを作成
    l := list.New()

    // 要素を追加
    l.PushBack(1)
    l.PushBack(2)
    l.PushFront(0)

    // リストを走査
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }
}
  • list.New: 新しいリストを作成します。
  • l.PushBack: リストの末尾に要素を追加します。
  • l.PushFront: リストの先頭に要素を追加します。
  • l.Front: リストの先頭要素を取得します。
  • e.Next: 次の要素に移動します。
9.1.2 ヒープ
[編集]

container/heapパッケージは、ヒープを提供します。ヒープは優先度付きキューとして利用できます。

package main

import (
    "container/heap"
    "fmt"
)

// IntHeapは整数のヒープです
type IntHeap []int

func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

func main() {
    h := &IntHeap{2, 1, 5}
    heap.Init(h)

    heap.Push(h, 3)
    fmt.Println("最小値:", (*h)[0])

    for h.Len() > 0 {
        fmt.Println(heap.Pop(h))
    }
}
  • heap.Init: ヒープを初期化します。
  • heap.Push: ヒープに要素を追加します。
  • heap.Pop: ヒープから最小値を取り出します。
9.1.3 リング
[編集]

container/ringパッケージは、リングバッファを提供します。

package main

import (
    "container/ring"
    "fmt"
)

func main() {
    // リングを作成
    r := ring.New(3)

    // リングに値を設定
    for i := 0; i < r.Len(); i++ {
        r.Value = i
        r = r.Next()
    }

    // リングを走査
    r.Do(func(p interface{}) {
        fmt.Println(p)
    })
}
  • ring.New: 新しいリングを作成します。
  • r.Value: リングの現在の値を設定または取得します。
  • r.Next: 次の要素に移動します。
  • r.Do: リングのすべての要素に対して関数を適用します。

9.2 sortパッケージ: ソート

[編集]

sortパッケージは、スライスのソートを提供します。

9.2.1 スライスのソート
[編集]
package main

import (
    "fmt"
    "sort"
)

func main() {
    nums := []int{4, 2, 7, 1, 3}
    sort.Ints(nums)
    fmt.Println("ソートされたスライス:", nums)
}
  • sort.Ints: 整数のスライスをソートします。
9.2.2 カスタムソート
[編集]
package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name string
    Age  int
}

type ByAge []Person

func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

func main() {
    people := []Person{
        {"Alice", 25},
        {"Bob", 30},
        {"Charlie", 20},
    }

    sort.Sort(ByAge(people))
    fmt.Println("年齢でソート:", people)
}
  • sort.Sort: カスタムソートを実行します。

9.3 mathパッケージ: 数学関数

[編集]

mathパッケージは、数学関数と定数を提供します。

9.3.1 基本的な数学関数
[編集]
package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("円周率:", math.Pi)
    fmt.Println("平方根:", math.Sqrt(16))
    fmt.Println("べき乗:", math.Pow(2, 3))
    fmt.Println("絶対値:", math.Abs(-10))
}
  • math.Pi: 円周率を取得します。
  • math.Sqrt: 平方根を計算します。
  • math.Pow: べき乗を計算します。
  • math.Abs: 絶対値を計算します。
9.3.2 三角関数
[編集]
package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Println("サイン:", math.Sin(math.Pi/2))
    fmt.Println("コサイン:", math.Cos(0))
    fmt.Println("タンジェント:", math.Tan(math.Pi/4))
}
  • math.Sin: サインを計算します。
  • math.Cos: コサインを計算します。
  • math.Tan: タンジェントを計算します。

10. テストとベンチマーク

[編集]

Go言語は、テストとベンチマークを簡単に実行するための強力なツールを標準で提供しています。testingパッケージを使うことで、ユニットテストやベンチマークを効率的に記述・実行できます。

10.1 testingパッケージ: ユニットテスト

[編集]

Goのテストは、_test.goというサフィックスが付いたファイルに記述します。テスト関数はTestで始まり、*testing.Tを引数に取ります。

10.1.1 基本的なテスト
[編集]

以下は、簡単な関数のテスト例です。

// math.go
package main

func Add(a, b int) int {
    return a + b
}
// math_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2, 3) = %d; want 5", result)
    }
}
  • TestAdd: テスト関数です。
  • t.Errorf: テストが失敗した場合にエラーメッセージを出力します。
10.1.2 テーブル駆動テスト
[編集]

テーブル駆動テストは、複数のテストケースを効率的に記述する方法です。

package main

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {2, 3, 5},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        result := Add(tt.a, tt.b)
        if result != tt.want {
            t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.want)
        }
    }
}
  • tests: テストケースを構造体のスライスとして定義します。
  • for _, tt := range tests: 各テストケースを順番に実行します。

10.2 ベンチマーク

[編集]

ベンチマークは、コードのパフォーマンスを測定するために使用します。ベンチマーク関数はBenchmarkで始まり、*testing.Bを引数に取ります。

10.2.1 基本的なベンチマーク
[編集]

以下は、簡単な関数のベンチマーク例です。

// math_test.go
package main

import "testing"

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(2, 3)
    }
}
  • BenchmarkAdd: ベンチマーク関数です。
  • b.N: ベンチマークの実行回数です。
10.2.2 ベンチマークの実行
[編集]

ベンチマークを実行するには、以下のコマンドを使用します。

go test -bench .

10.3 テストのカバレッジ

[編集]

テストのカバレッジは、テストがどの程度コードをカバーしているかを示します。カバレッジを測定するには、以下のコマンドを使用します。

go test -cover

カバレッジレポートをHTML形式で出力するには、以下のコマンドを使用します。

go test -coverprofile=coverage.out
go tool cover -html=coverage.out

10.4 サブテスト

[編集]

サブテストは、テストをさらに細かい単位に分割するために使用します。

package main

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b, want int
    }{
        {"正の数", 2, 3, 5},
        {"ゼロ", 0, 0, 0},
        {"負の数", -1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.want {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.want)
            }
        })
    }
}
  • t.Run: サブテストを実行します。

10.5 テストのヘルパー関数

[編集]

テストコードの重複を避けるために、ヘルパー関数を使用します。

package main

import "testing"

func testAdd(t *testing.T, a, b, want int) {
    result := Add(a, b)
    if result != want {
        t.Errorf("Add(%d, %d) = %d; want %d", a, b, result, want)
    }
}

func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, want int
    }{
        {2, 3, 5},
        {0, 0, 0},
        {-1, 1, 0},
    }

    for _, tt := range tests {
        testAdd(t, tt.a, tt.b, tt.want)
    }
}
  • testAdd: テストのヘルパー関数です。

11. その他の便利なパッケージ

[編集]

Goの標準ライブラリには、テストやベンチマーク以外にも多くの便利なパッケージが含まれています。ここでは、flaglogregexppathfilepathパッケージの使い方を紹介します。

11.1 flagパッケージ: コマンドライン引数の解析

[編集]

flagパッケージは、コマンドライン引数を簡単に解析するための機能を提供します。

11.1.1 基本的な使い方
[編集]

以下は、コマンドライン引数を解析する簡単な例です。

package main

import (
    "flag"
    "fmt"
)

func main() {
    // コマンドライン引数を定義
    name := flag.String("name", "Guest", "名前")
    age := flag.Int("age", 30, "年齢")

    // 引数を解析
    flag.Parse()

    // 引数の値を表示
    fmt.Printf("名前: %s, 年齢: %d\n", *name, *age)
}
  • flag.String: 文字列の引数を定義します。
  • flag.Int: 整数の引数を定義します。
  • flag.Parse: 引数を解析します。
11.1.2 実行例
[編集]
go run main.go -name Alice -age 25

出力:

名前: Alice, 年齢: 25

11.2 logパッケージ: ロギング

[編集]

logパッケージは、ログを出力するための機能を提供します。

11.2.1 基本的な使い方
[編集]

以下は、ログを出力する簡単な例です。

package main

import (
    "log"
    "os"
)

func main() {
    // ログをファイルに出力
    file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("ファイルを開けません:", err)
    }
    defer file.Close()

    log.SetOutput(file)
    log.Println("ログをファイルに出力します")
}
  • log.SetOutput: ログの出力先を設定します。
  • log.Println: ログを出力します。
11.2.2 ログのフォーマット
[編集]
package main

import "log"

func main() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
    log.Println("フォーマットされたログ")
}
  • log.SetFlags: ログのフォーマットを設定します。

11.3 regexpパッケージ: 正規表現

[編集]

regexpパッケージは、正規表現を使った文字列操作を提供します。

11.3.1 正規表現のマッチ
[編集]

以下は、正規表現を使って文字列を検索する例です。

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(<code>\d+</code>)
    match := re.FindString("123 abc 456")
    fmt.Println("マッチした文字列:", match)
}
  • regexp.MustCompile: 正規表現をコンパイルします。
  • re.FindString: 正規表現にマッチする文字列を検索します。
11.3.2 正規表現の置換
[編集]

以下は、正規表現を使って文字列を置換する例です。

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(<code>\d+</code>)
    result := re.ReplaceAllString("123 abc 456", "XXX")
    fmt.Println("置換後の文字列:", result)
}
  • re.ReplaceAllString: 正規表現にマッチする文字列を置換します。

11.4 pathfilepathパッケージ: パスの操作

[編集]

pathfilepathパッケージは、パスの操作を提供します。pathは主にURLパスを扱い、filepathはファイルシステムのパスを扱います。

11.4.1 パスの結合
[編集]

以下は、パスを結合する例です。

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    dir := "/usr/local"
    file := "bin/go"
    fullPath := filepath.Join(dir, file)
    fmt.Println("結合されたパス:", fullPath)
}
  • filepath.Join: パスを結合します。
11.4.2 パスの分解
[編集]

以下は、パスを分解する例です。

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    dir, file := filepath.Split("/usr/local/bin/go")
    fmt.Println("ディレクトリ:", dir)
    fmt.Println("ファイル名:", file)
}
  • filepath.Split: パスをディレクトリとファイル名に分解します。

12. 実践的なプロジェクト例

[編集]

ここまで学んだ標準ライブラリを活用して、簡単なプロジェクトを実装してみましょう。

12.1 シンプルなWebサーバー

[編集]

以下は、net/httpパッケージを使ったシンプルなWebサーバーの例です。

package main

import (
    "fmt"
    "net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/hello", helloHandler)
    fmt.Println("サーバーを起動しました。ポート: 8080")
    http.ListenAndServe(":8080", nil)
}
  • http.HandleFunc: パスとハンドラ関数を登録します。
  • http.ListenAndServe: HTTPサーバーを起動します。

12.2 ファイルの読み書き

[編集]

以下は、osioutilパッケージを使ったファイルの読み書きの例です。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
)

func main() {
    // ファイルにデータを書き込む
    data := []byte("Hello, Go!")
    err := ioutil.WriteFile("example.txt", data, 0644)
    if err != nil {
        log.Fatal("ファイル書き込みエラー:", err)
    }

    // ファイルからデータを読み取る
    content, err := ioutil.ReadFile("example.txt")
    if err != nil {
        log.Fatal("ファイル読み込みエラー:", err)
    }
    fmt.Println("ファイルの内容:", string(content))
}
まとめ

次のステップとして、以下のことをお勧めします。

  • 公式ドキュメントを読む: pkg.go.dev で標準ライブラリの詳細を確認できます。
  • 実践的なプロジェクトに挑戦する: 学んだ知識を活用して、実際のプロジェクトを構築してみましょう。
  • コミュニティに参加する: Goのコミュニティに参加して、他の開発者と知識を共有しましょう。