👁

Go標準ライブラリ

この章では、Goの標準ライブラリでよく使う代表的なパッケージについて学び、ハンズオン形式で学習します。これにより、fmtosstringsbufiotimeencoding/json などの実務頻出パッケージの使い方が身につきます。

1. 本章の概要

1.1 本章の目的

Goには標準ライブラリとして、入出力・文字列操作・日時・JSONなど、実務で頻繁に使う機能があらかじめ用意されています。よく使うパッケージを一通り知っておくと、以降のハンズオンで実装が楽になります。本章では、入出力の fmt、環境変数・コマンドライン引数・ファイルの読み書きを扱う os、文字列操作の strings、標準入力の bufio、入出力の抽象化を担う io、日時の time、JSONの相互変換を行う encoding/json を扱います。

1.2 ハンズオンの流れ

各パッケージを1つずつ動かしながら、実務で頻出するパターンを一通り扱います。

1.3 事前準備

必要なツール

この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。

ツール名 関連箇所 理由
Visual Studio Code Visual Studio Codeのインストール Goのコードを記述するエディタとして使用する
Go Goのインストール 本章のサンプルコードを実行する言語処理系として使用する

2. fmt パッケージ

プログラムが外部と情報をやり取りするための仕組みが入出力です。Goでは、標準ライブラリの fmt パッケージで画面表示や文字列組み立てを行います。「Go基本文法」の「まずは動かしてみよう」でも fmt.Println を使っており、実は本講座で最初から使い続けている定番パッケージです。

2.1 標準出力(fmt.Println)

標準出力(standard output)は、プログラムが処理結果を書き出す既定の出力先です。プログラム側からは常に「標準出力に書き込む」という同じ操作ですが、実際の出力先は実行環境によって決まります。ターミナルから実行した場合はターミナル画面に表示され、サーバ上でサービスとして動かす場合はログファイルやログ収集システムに転送されます。プログラム側が出力先を意識せずに済むのが利点です。

fmt.Println は、渡した値をこの標準出力に書き出し、末尾に改行を自動で付ける関数です。

1つの値の出力

ここではまず、fmt.Printlnに値を1つだけ渡す形式を扱います。書き方は以下のとおりです。

fmt.Println(値)   // 値を表示し、末尾で改行する

次のコードは、文字列・整数・真偽値をそれぞれ1つずつ渡して表示する例です。main.go を以下の内容に書き換えて保存し、go run main.go で実行します。

package main

import "fmt"

func main() {
    fmt.Println("Hello, Go!")
    fmt.Println(42)
    fmt.Println(true)
}

以下のような実行結果が表示されます。

Hello, Go!
42
true

fmt.Println は文字列だけでなく、整数や真偽値もそのまま渡せることが確認できます。

複数の値の連結出力

fmt.Println は複数の値を受け取ることもでき、その場合はカンマで区切って渡します。書き方は以下のとおりです。

fmt.Println(値1, 値2, ...)   // 半角スペースで区切って表示し、末尾で改行する

次のコードは、文字列と整数、真偽値を組み合わせて1行で表示する例です。

fmt.Println("count =", 10, "enabled =", true)

以下のような実行結果が表示されます。

count = 10 enabled = true

4つの値がスペースで区切られて1行にまとまって表示されました。

コードを解説します。

fmt.Println("count =", 10, "enabled =", true)

fmt.Println は複数の値を受け取ると、それぞれを半角スペースで区切って連結し、1行にまとめて出力します。

2.2 書式付き出力(fmt.Printf)

fmt.Printf は、書式指定子(%s %d %f など)を使って値を埋め込みながら出力できる関数です。書き方は以下のとおりです。

fmt.Printf("書式文字列", 引数1, 引数2, ...)   // 書式文字列の %s/%d などの位置に引数を埋め込んで出力する

次のコードは、名前と年齢を文字列に埋め込んで表示する例です。

package main

import "fmt"

func main() {
    name := "Taro"
    age := 30

    fmt.Printf("名前は %s、年齢は %d です\n", name, age)
}

以下のような実行結果が表示されます。

名前は Taro、年齢は 30 です

%s の位置に name%d の位置に age の値が埋め込まれて表示されました。

コードを解説します。

fmt.Printf("名前は %s、年齢は %d です\n", name, age)

第1引数の書式文字列にある %s は文字列、%d は整数の埋め込み位置を示します。第2引数以降に渡した nameage が順に埋め込まれます。\n は改行を表します。fmt.Println と違い、fmt.Printf は自動で改行を付けないため、必要に応じて明示します。

書式指定子 動作
%s 文字列を埋め込む
%d 整数を10進数で埋め込む
%f 浮動小数点数を埋め込む
%t 真偽値を埋め込む
%v 値の既定表現で埋め込む(型を問わず使える)
%T 値の型そのものを埋め込む

2.3 文字列の組み立て(fmt.Sprintf)

fmt.Sprintf は、フォーマット付き文字列を「出力せずに文字列として返す」関数です。ログメッセージやエラーメッセージを組み立てるときによく使います。書き方は以下のとおりです。

文字列 := fmt.Sprintf("書式文字列", 引数1, 引数2, ...)

次のコードは、書式文字列に値を埋め込んで得た文字列を、あとから fmt.Println で表示する例です。

package main

import "fmt"

func main() {
    user := "Taro"
    resource := "reservations"
    msg := fmt.Sprintf("user=%s resource=%s created", user, resource)
    fmt.Println(msg)
}

以下のような実行結果が表示されます。

user=Taro resource=reservations created

Sprintf で組み立てた文字列 user=Taro resource=reservations createdmsg に入り、それを fmt.Println で表示した結果です。

コードを解説します。

msg := fmt.Sprintf("user=%s resource=%s created", user, resource)

fmt.Sprintf の書式指定子は fmt.Printf と同じで、出力先だけが「画面」ではなく「戻り値の文字列」になります。実際にはこの msg をログライブラリに渡す、あるいはエラーメッセージとして errors.New に渡す、といった使い方をします。

2.4 独自の表示形式の定義(fmt.Stringer)

自分で定義した型を fmt.Println で表示したとき、既定では {field1 field2 ...} のような素っ気ない形式になります。表示形式を自作したい場合は、その型に String() string メソッドを実装します。fmt パッケージは表示時にこのメソッド(Goインタフェース で扱った interface の一種で fmt.Stringer と呼ばれる)を自動で呼び出します。書き方は以下のとおりです。

type 型名 struct { ... }

func (レシーバ 型名) String() string {
    return 表示したい文字列
}

次のコードは、User 型に String() を持たせて、fmt.Println の出力を自作する例です。

package main

import "fmt"

type User struct {
    Name string
    Age  int
}

func (u User) String() string {
    return fmt.Sprintf("<User %s(%d歳)>", u.Name, u.Age)
}

func main() {
    u := User{Name: "Taro", Age: 30}
    fmt.Println(u)
}

以下のような実行結果が表示されます。

<User Taro(30歳)>

String() を持たせるだけで、fmt.Println の出力形式を <User Taro(30歳)> に切り替えられました。既定なら {Taro 30} と表示されるところが、任意の書式で表示できます。

コードを解説します。

func (u User) String() string {
    return fmt.Sprintf("<User %s(%d歳)>", u.Name, u.Age)
}

User 型に String() string メソッドを実装しています。この形(引数なし、戻り値 string)で String メソッドを持つ型は、fmt パッケージが表示時に自動でこのメソッドを呼び出してくれます。ログやデバッグ表示を分かりやすくするときの定番パターンです。

3. os パッケージ

os パッケージは、コマンドライン引数の参照、環境変数の取得、ファイル操作など、OS が提供する機能にアクセスするための関数を提供します。

3.1 コマンドライン引数の取得(os.Args)

os.Args は、実行時にプログラムへ渡されたコマンドライン引数を保持するスライスです。CLI ツールで、実行時のオプションやサブコマンドを受け取る用途で使います。書き方は以下のとおりです。

os.Args   // []string ─ コマンドライン引数(先頭はプログラムのパス)

次のコードは、実行時に渡されたコマンドライン引数を表示する例です。

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println("引数:", os.Args)
}

main.go として保存し、引数を2つ渡して実行します。

go run main.go hello world

以下のような実行結果が表示されます(先頭のパスは環境により異なります)。

引数: [/tmp/go-build.../exe/main hello world]

先頭にビルドされたバイナリのパスが入り、続いて渡した helloworld が並んで出力されました。

コードを解説します。

fmt.Println("引数:", os.Args)

os.Args は、プログラムを起動したときに渡されたコマンドライン引数のスライスです。先頭(os.Args[0])にはプログラム自身のパスが入り、2つ目以降にユーザが渡した引数が並びます。

3.2 環境変数の取得(os.Getenv)

環境変数(environment variable)は、OS が管理する「名前と値」の設定情報で、実行中のプロセスから参照できます。シェルの export コマンドや、コンテナ実行時のオプション(docker run -e など)、サービス起動時の設定などで指定します。ソースコードに直接書きたくない値(データベース接続先・APIキー・動作モードの切り替えなど)を、実行時にプロセスへ渡す用途で広く使われます。

os.Getenv は、指定した名前の環境変数の値を取得する関数です。書き方は以下のとおりです。

os.Getenv("キー名")   // string ─ 環境変数の値。未設定なら空文字列

次のコードは、環境変数 MY_NAME の値を表示する例です。

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println("MY_NAME:", os.Getenv("MY_NAME"))
}

環境変数はプログラム側では設定できないため、実行前にシェル側で設定しておく必要があります。MY_NAMETaro を設定した状態でプログラムを実行します。

macOS / Linux(bash / zsh):

export MY_NAME=Taro
go run main.go

Windows(PowerShell):

$env:MY_NAME = "Taro"
go run main.go

以下のような実行結果が表示されます。

MY_NAME: Taro

シェルで設定した MY_NAME の値が、Goプログラムから os.Getenv("MY_NAME") で取り出されて表示されました。

コードを解説します。

fmt.Println("MY_NAME:", os.Getenv("MY_NAME"))

os.Getenv("キー名") で、指定した名前の環境変数の値を取得できます。設定されていない場合は空文字列が返ります。

3.3 .env ファイルからの環境変数の読み込み(godotenv)

os.Getenv で環境変数を取り出せるようになりましたが、環境変数はシェル側で1つずつ export して用意する必要があります。キーの数が増えるとこの手順は煩雑で、ターミナルを開き直すたびに設定し直す手間もかかります。そこで実際の開発では、.env ファイル(環境変数を KEY=VALUE 形式で1行1変数書いたテキストファイル)にまとめて、godotenv パッケージで一括読み込みする形がよく使われます。

godotenv は外部パッケージなので、「Goモジュールとパッケージ」で扱った go get で導入します。ここでは、.env に書いた値を godotenv.Load で環境変数として登録し、os.Getenv で取り出す流れを体験します。

godotenv の導入

main.go があるフォルダで、godotenvgo get で取り込みます。

go get github.com/joho/godotenv

以下のような実行結果が表示されます。

go: added github.com/joho/godotenv v1.5.x

go.modgodotenv がバージョン付きで追加され、import して使えるようになります。

.env ファイルの作成

main.go と同じフォルダに .env を作成します。書き方は以下のとおりです。1行に1変数、KEY=VALUE 形式で書きます。

キー1=値1
キー2=値2

次の内容の .env を作成します。

APP_NAME=DevOpsCamp
APP_ENV=development
💡 ポイント
.env にはパスワードやAPIキーなど、外部に出したくない値が入ることが多いため、通常は .gitignore.env を追加して Git 管理から除外します。共有したい設定値(サンプル)は、値を空にした .env.example を別途 Git 管理下に置く運用が一般的です。

読み込みと取り出し

書き方は以下のとおりです。godotenv.Load() を呼ぶと、カレントディレクトリの .env が読み込まれ、書かれた KEY=VALUE がプロセスの環境変数として登録されます。登録後は、通常の環境変数と同じように os.Getenv で取り出せます。

godotenv.Load()           // error ─ .env を読み込み、環境変数として登録する
os.Getenv("キー名")        // string ─ 登録された環境変数の値

次のコードは、.env から APP_NAMEAPP_ENV を読み込んで表示する例です。

package main

import (
    "fmt"
    "os"

    "github.com/joho/godotenv"
)

func main() {
    if err := godotenv.Load(); err != nil {
        fmt.Println(".envファイル読み込み失敗:", err)
        return
    }

    fmt.Println("APP_NAME:", os.Getenv("APP_NAME"))
    fmt.Println("APP_ENV:", os.Getenv("APP_ENV"))
}

main.go として保存し、以下のコマンドで実行します。

go run main.go

以下のような実行結果が表示されます。

APP_NAME: DevOpsCamp
APP_ENV: development

.env に書いた値がプロセスの環境変数として登録され、os.Getenv で取り出せていることが確認できます。シェル側で export する必要はありません。

コードを解説します。

if err := godotenv.Load(); err != nil {
    fmt.Println(".envファイル読み込み失敗:", err)
    return
}

godotenv.Load() は、実行時のカレントディレクトリにある .env を読み込み、書かれた KEY=VALUE を環境変数として登録します。ファイルが見つからない・書式が不正などの場合はエラーが返るため、if err != nil で判定して早期リターンします。

fmt.Println("APP_NAME:", os.Getenv("APP_NAME"))

godotenv.Load() を通した後は、os.Getenv の呼び出し方は export で設定したときとまったく同じです。値の取り出し側のコードを変えずに、設定の受け渡し方だけを差し替えられるのが godotenv を挟むメリットです。

3.4 ファイルの読み込み(os.ReadFile)

os.ReadFile は、ファイル全体を一度に読み込んで []byte として返す関数です。書き方は以下のとおりです。

os.ReadFile(ファイルパス)   // ([]byte, error) ─ ファイル全体を読み込む

サンプルを動かす前に、読み込み対象の hello.txt を作業ディレクトリ(main.go と同じ場所)に用意します。シェルから作成する場合は以下のようにします。

macOS / Linux(bash / zsh):

echo "Hello, Go!" > hello.txt

Windows(PowerShell):

Set-Content -Path hello.txt -Value "Hello, Go!"

Visual Studio Code で hello.txt を新規作成し、Hello, Go! と記入して保存しても構いません。

次のコードは、用意した hello.txt を読み込んで表示する例です。

package main

import (
    "fmt"
    "os"
)

func main() {
    data, err := os.ReadFile("hello.txt")
    if err != nil {
        fmt.Println("読み込みエラー:", err)
        return
    }
    fmt.Println(string(data))
}

main.go として保存し、以下のコマンドで実行します。

go run main.go

以下のような実行結果が表示されます。

Hello, Go!

os.ReadFilehello.txt の中身が []byte として読み込まれ、string(data) で文字列に変換して表示されました。

コードを解説します。

data, err := os.ReadFile("hello.txt")

os.ReadFile(パス) は、指定したファイルの内容をすべて []byte として読み込みます。ファイルが存在しない場合や権限がない場合は err にエラー情報が入るため、判定して分岐します。[]byte を人間が読める形にするため、string(data) で文字列へ変換しています。

💡 ポイント
os.ReadFile はファイル全体を一度にメモリに読み込むため、巨大なファイル(数GB規模など)には向きません。行単位で処理したい場合や巨大ファイルを扱う場合は、os.Open*os.File を取得し、bufio.NewScanner などのバッファ経由で処理します。

3.5 ファイルの書き込み(os.WriteFile)

os.WriteFile は、ファイルにデータを一度に書き込む関数です。ファイルが存在しなければ新規作成、存在すれば上書きされます。書き方は以下のとおりです。

os.WriteFile(ファイルパス, データ, パーミッション)   // error ─ ファイルにデータを書き込む

次のコードは、hello.txt に文字列を書き出す例です。

package main

import (
    "fmt"
    "os"
)

func main() {
    content := []byte("Hello, Go!\n")
    err := os.WriteFile("hello.txt", content, 0644)
    if err != nil {
        fmt.Println("書き込みエラー:", err)
        return
    }
    fmt.Println("hello.txt に書き込みました")
}

main.go として保存し、以下のコマンドで実行します。

go run main.go

以下のような実行結果が表示されます。

hello.txt に書き込みました

カレントディレクトリの hello.txtHello, Go! が書き込まれた状態になります。前節で用意したファイルがある場合は、上書きされます。

コードを解説します。

err := os.WriteFile("hello.txt", content, 0644)

os.WriteFile(パス, データ, パーミッション) は、指定パスに []byte の内容をまとめて書き込みます。第3引数の 0644 は Unix系のファイルパーミッション(所有者は読み書き可、他は読み取りのみ)を表します。Windows では効果を持ちませんが、指定は必要です。

4. strings パッケージ

ログ処理、ファイル名の組み立て、URLの生成など、実際には文字列の分割・置換・大文字化といった操作が頻繁に登場します。Goでは標準ライブラリの strings パッケージにこれらの関数が揃っています。ここでは代表的な関数を1つずつ確認します。

4.1 区切り文字による分割(strings.Split)

strings.Split は、指定した区切り文字で文字列を分割し、[]string のスライスとして返す関数です。書き方は以下のとおりです。

strings.Split(文字列, 区切り文字)   // []string ─ 区切って分割する

次のコードは、カンマ区切りの文字列を3つに分割する例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "hello,go,world"
    fmt.Println(strings.Split(s, ","))
}

以下のような実行結果が表示されます。

[hello go world]

区切り文字 , で分割された3要素のスライスが表示されました。

4.2 区切り文字による連結(strings.Join)

strings.Join は、Split の逆で、スライスを区切り文字で1本の文字列に連結する関数です。書き方は以下のとおりです。

strings.Join(スライス, 区切り文字)   // string ─ スライスを区切り文字で連結する

次のコードは、3要素のスライスを - で連結する例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    parts := []string{"a", "b", "c"}
    fmt.Println(strings.Join(parts, "-"))
}

以下のような実行結果が表示されます。

a-b-c

スライスの各要素が - でつながれた1つの文字列として表示されました。

4.3 大文字への変換(strings.ToUpper)

strings.ToUpper は、文字列全体を大文字に変換する関数です。書き方は以下のとおりです。

strings.ToUpper(文字列)   // string ─ 大文字に変換する

次のコードは、小文字混じりの文字列を大文字化する例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.ToUpper("hello,go,world"))
}

以下のような実行結果が表示されます。

HELLO,GO,WORLD

アルファベット部分だけが大文字化され、カンマなどの記号はそのまま表示されました。

4.4 部分文字列の置換(strings.Replace)

strings.Replace は、指定した部分文字列を別の文字列に置換する関数です。書き方は以下のとおりです。

strings.Replace(文字列, 置換前, 置換後, 回数)   // string ─ 部分文字列を置換する

第4引数の 回数 は「先頭から何回置換するか」を指定します。-1 を指定するとすべての一致箇所を置換します。

次のコードは、GoWorld に1回だけ置換する例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Replace("Hello, Go", "Go", "World", 1))
}

以下のような実行結果が表示されます。

Hello, World

Go の部分が World に置き換わって表示されました。

4.5 前後の空白の削除(strings.TrimSpace)

strings.TrimSpace は、文字列の前後の空白(半角スペース・タブ・改行など)を削除する関数です。書き方は以下のとおりです。

strings.TrimSpace(文字列)   // string ─ 前後の空白を削除する

次のコードは、前後にスペースが入った文字列から空白を取り除く例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "  Hello, Go  "
    fmt.Println(strings.TrimSpace(s))
}

以下のような実行結果が表示されます。

Hello, Go

前後のスペースが取り除かれた Hello, Go が表示されました。ユーザ入力やファイルから読み込んだ文字列の前処理でよく使います。

4.6 部分文字列の有無判定(strings.Contains)

strings.Contains は、文字列の中に指定した部分文字列が含まれるかを判定し、true / false を返す関数です。書き方は以下のとおりです。

strings.Contains(文字列, 部分文字列)   // bool ─ 含まれるかを判定する

次のコードは、hello,go,worldgo が含まれるかを判定する例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    fmt.Println(strings.Contains("hello,go,world", "go"))
}

以下のような実行結果が表示されます。

true

go が含まれているため true が返されました。

4.7 先頭・末尾の一致判定(strings.HasPrefix / HasSuffix)

strings.HasPrefix は指定した文字列で始まるか、strings.HasSuffix は指定した文字列で終わるかを判定する関数です。書き方は以下のとおりです。

strings.HasPrefix(文字列, 先頭文字列)   // bool ─ 先頭が一致するかを判定する
strings.HasSuffix(文字列, 末尾文字列)   // bool ─ 末尾が一致するかを判定する

次のコードは、URLパスやファイル名の先頭・末尾を判定する例です。

package main

import (
    "fmt"
    "strings"
)

func main() {
    path := "/api/users"
    file := "report.pdf"

    fmt.Println(strings.HasPrefix(path, "/api"))
    fmt.Println(strings.HasSuffix(file, ".pdf"))
}

以下のような実行結果が表示されます。

true
true

path/api で始まっているため1つ目が truefile.pdf で終わっているため2つ目も true になりました。

5. bufio パッケージ

CLI ツールや簡単な対話プログラムを書くとき、ユーザから入力を受け取れる必要があります。Goでは bufioos を組み合わせて標準入力を受け取ります。書き方は以下のとおりです。

scanner := bufio.NewScanner(os.Stdin)   // 標準入力を1行単位で読める Scanner を作る
scanner.Scan()                          // 1行分の入力を待ち受ける
文字列 := scanner.Text()                 // 読み取った文字列を取り出す

次のコードは、名前の入力を促し、入力された文字列を挨拶と組み合わせて表示する例です。

package main

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

func main() {
    scanner := bufio.NewScanner(os.Stdin)
    fmt.Print("名前を入力してください: ")
    scanner.Scan()
    name := scanner.Text()

    fmt.Printf("こんにちは、%sさん\n", name)
}

実行すると、以下のように名前の入力を待ち、入力すると挨拶が返ります。

名前を入力してください: Taro
こんにちは、Taroさん

ユーザが Taro と入力して Enter を押したところ、こんにちは、Taroさん と表示されました。

コードを解説します。

scanner := bufio.NewScanner(os.Stdin)

bufio.NewScanner(os.Stdin) は「標準入力を1行単位で読み取れる Scanner」を作ります。os.Stdin はキーボードなどから入る標準入力を表すオブジェクトです。

fmt.Print("名前を入力してください: ")

fmt.Printfmt.Println と違って改行を付けません。プロンプトのように「入力を促す文字を表示し、その直後に入力を受ける」場面で使います。

scanner.Scan()
name := scanner.Text()

scanner.Scan() で1行分の入力を待ち受け、Enter が押されるとその行を取得します。scanner.Text() で読み取った文字列を取り出し、name に代入しています。

6. io パッケージ

io パッケージは、「読み込み」「書き込み」といった入出力の抽象化を提供する interface が中心のパッケージです。実際の読み書き機能は os(ファイル)や bytes(メモリバッファ)などのパッケージが提供しますが、それらは全て io.Reader / io.Writer という共通の interface(Goインタフェース 参照)を満たしています。

代表的な io.Writer の定義は以下のとおりです。

type Writer interface {
    Write(p []byte) (n int, err error)
}

Write([]byte) を持ち、書き込んだバイト数と error を返す型」を表します。os.File(ファイル)、bytes.Buffer(メモリバッファ)、http.ResponseWriter(HTTPレスポンス)などが全て io.Writer を満たしています。書き込む側は「相手が io.Writer である」ことだけを知っていれば、実際の書き込み先がファイルでもメモリでもHTTPレスポンスでも同じコードで動きます。

次のコードは、os.Stdout(標準出力)と bytes.Buffer(メモリ)に同じ関数で書き込みを行う例です。

package main

import (
    "bytes"
    "fmt"
    "io"
    "os"
)

func writeGreeting(w io.Writer, name string) {
    fmt.Fprintf(w, "Hello, %s\n", name)
}

func main() {
    // 標準出力に書き込む
    writeGreeting(os.Stdout, "Taro")

    // メモリバッファに書き込む
    var buf bytes.Buffer
    writeGreeting(&buf, "Jiro")
    fmt.Println("buffer content:", buf.String())
}

以下のような実行結果が表示されます。

Hello, Taro
buffer content: Hello, Jiro

writeGreetingio.Writer を受け取るだけで、書き込み先を意識しません。os.Stdout&bytes.Buffer{} のどちらを渡しても、同じ関数で書き込みができました。

コードを解説します。

func writeGreeting(w io.Writer, name string) {
    fmt.Fprintf(w, "Hello, %s\n", name)
}

引数の型を io.Writer にすることで、実際の書き込み先を関数側で意識せずに済みます。fmt.Fprintf は「渡された io.Writer に対して書式付き出力する」関数で、fmt.Printf(標準出力に固定)と違って書き込み先を選べます。

writeGreeting(os.Stdout, "Taro")

os.Stdout は「標準出力」を表す *os.File で、io.Writer を満たしています。そのため writeGreeting の引数にそのまま渡せます。

var buf bytes.Buffer
writeGreeting(&buf, "Jiro")

bytes.Buffer はメモリ上のバッファで、こちらも io.Writer を満たしています。&buf として渡した後、buf.String() で溜まった内容を取り出せます。

「書き込み先を切り替える」ときに interface による抽象化が実感できる場面で、Goが公式で用意している代表的な interface として押さえておく価値があります。

7. time パッケージ

time パッケージは、現在時刻の取得、日時のフォーマット、経過時間の計測などに使います。

7.1 現在時刻の取得(time.Now)

time.Now は、現在時刻を time.Time 型の値として返す関数です。書き方は以下のとおりです。

now := time.Now()   // time.Time ─ 現在時刻を取得する

次のコードは、現在時刻を取得してそのまま表示する例です。

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println(now)
}

以下のような実行結果が表示されます(実行した時刻により異なります)。

2026-07-15 12:34:56.789 +0900 JST

time.Time 型の値を fmt.Println に渡すと、ナノ秒とタイムゾーンを含む既定の形式で表示されます。

7.2 日時のフォーマット(Format)

time.Time 型の Format メソッドは、日時を任意の書式に整形した文字列を返します。書き方は以下のとおりです。

文字列 := 時刻.Format("2006-01-02 15:04:05")   // string ─ 指定した書式で整形する

次のコードは、現在時刻を YYYY-MM-DD HH:MM:SS の形式で整形して表示する例です。

package main

import (
    "fmt"
    "time"
)

func main() {
    now := time.Now()
    fmt.Println(now.Format("2006-01-02 15:04:05"))
}

以下のような実行結果が表示されます。

2026-07-15 12:34:56

指定した書式に沿って、年月日と時分秒が整形されて表示されました。

💡 ポイント
Goの日時フォーマット文字列は独特で、2006-01-02 15:04:05 という固定の参照時刻をどう表示するかで指定します。例えば 2006-01-02YYYY-MM-DD を意味します。「2006 が年」「01 が月」「02 が日」といった対応を覚えれば、他言語よりも直感的に扱えます。

7.3 経過時間の計測(time.Since)

time.Since は、指定した時刻から現在までの経過時間を返す関数です。書き方は以下のとおりです。

elapsed := time.Since(開始時刻)   // time.Duration ─ 開始時刻から現在までの経過時間

次のコードは、time.Sleep で 100ms だけ待った処理の経過時間を計測する例です。

package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    time.Sleep(100 * time.Millisecond)
    elapsed := time.Since(start)
    fmt.Println("経過時間:", elapsed)
}

以下のような実行結果が表示されます。

経過時間: 100.234ms

time.Sleep で 100ms 待った後、time.Since(start)start からの経過時間が取得され、約100msが表示されました。処理時間のログ出力やパフォーマンス計測でよく使うパターンです。

8. encoding/json パッケージ

Goと外部システム(REST API など)とのやり取りでは、JSON形式が広く使われます。encoding/json を使うと struct と JSON を相互変換できます。

以降のサンプルで共通して使う User 型は以下の定義です。フィールドに付けた `json:"..."` タグにより、JSON 上のキー名を指定できます(Name"name"Age"age")。

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

8.1 struct から JSON への変換(json.Marshal)

json.Marshal は、struct を JSON 形式のバイト列 []byte に変換する関数です。書き方は以下のとおりです。

data, err := json.Marshal(構造体)   // ([]byte, error) ─ struct を JSON バイト列に変換する

次のコードは、User 型の値を JSON に変換して表示する例です。

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    u := User{Name: "Taro", Age: 30}
    data, _ := json.Marshal(u)
    fmt.Println(string(data))
}

以下のような実行結果が表示されます。

{"name":"Taro","age":30}

User の中身が JSON 形式のバイト列に変換され、string(data) で文字列にして表示すると {"name":"Taro","age":30} になりました。タグで指定したキー名(name / age)が使われている点に注目してください。

8.2 JSON から struct への変換(json.Unmarshal)

json.Unmarshal は、JSON バイト列を struct に読み込む関数です。書き方は以下のとおりです。

err = json.Unmarshal(バイト列, &構造体変数)   // error ─ JSON バイト列を struct に読み込む

第2引数は「書き込む先の struct のポインタ」なので、Goポインタ で扱った &変数名 の形でアドレスを渡します。

次のコードは、JSON 文字列を User に読み込んで表示する例です。

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    input := []byte(`{"name":"Jiro","age":25}`)
    var u User
    json.Unmarshal(input, &u)
    fmt.Println(u)
}

以下のような実行結果が表示されます。

{Jiro 25}

JSON 文字列が User に読み込まれ、フィールド値が並んだ既定形式で表示されました。u.Name"Jiro"u.Age25 が入っている状態です。

9. まとめ

この章では、Goの代表的な標準ライブラリを学びつつ、実際に入出力・文字列操作・日時扱い・JSONの相互変換を体験しました。

  • fmt.Println / fmt.Printf / fmt.Sprintf を場面に応じて使い分けて、入出力を扱える
  • 独自型に String() string を実装すると fmt.Stringer を満たし、fmt.Println の出力形式を自作できる
  • os パッケージで、環境変数・コマンドライン引数・ファイルの読み書きを扱える
  • godotenv.Load.env の内容を環境変数として登録すると、シェルで export せずに os.Getenv から値を取り出せる
  • strings パッケージには、分割・置換・大文字化などの便利関数が揃っている
  • bufio.NewScanner で標準入力を1行単位で受け取れる
  • io.Writer を引数の型に取ると、書き込み先(ファイル・メモリ・HTTPレスポンス等)を柔軟に切り替えられる
  • time パッケージのフォーマット文字列は、2006-01-02 15:04:05 の固定参照時刻ベースである
  • encoding/json により、structとJSONを相互変換でき、`json:"..."` タグでキー名を指定できる

次の章では、Goのエラーハンドリングを学びつつ、実際にエラーの伝播とラップ、errors.Is / errors.As による識別、deferpanic / recover を体験します。

この教材は役に立ちましたか?

いいねをたくさんいただけると、制作者の励みになり、より多くのセクションが作れるようになります。

感想を一言(任意)

いただいたコメントは次の制作のヒントになります。ぜひお気軽にご投稿ください。

このコメントは他の受講生には公開されません。DevOps Camp運営が、教材改善のために確認します。

0 / 2000