Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11001] ファイルの概要

このコミットは、Go言語の標準ライブラリであるflagパッケージに、time.Duration型のコマンドラインフラグを追加するものです。これにより、ユーザーはコマンドライン引数として時間間隔(例: 10s, 5m, 2h30m)を直接指定できるようになり、プログラム内でtime.Duration型として簡単に扱えるようになります。

コミット

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/cf506f6eacbe50661380f8d1e483b6375e423f06

元コミット内容

flag: add Duration flag type.

This works in the expected way: flag.Duration returns a *time.Duration,
and uses time.ParseDuration for parsing the input.

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5489113

変更の背景

Go言語のflagパッケージは、コマンドライン引数をパースするための標準的な方法を提供します。しかし、このコミット以前は、time.Duration型を直接サポートするフラグタイプが存在しませんでした。プログラムで時間間隔を扱う場合、通常はtime.Duration型を使用しますが、コマンドラインから文字列として受け取った時間値を手動でtime.ParseDurationなどを使って変換する必要がありました。

このコミットは、このような一般的なユースケースに対応し、開発者がより簡潔かつ安全に時間間隔をコマンドライン引数として扱えるようにするために、Durationフラグタイプを追加しました。これにより、他のプリミティブ型(int, string, boolなど)と同様に、time.Durationflagパッケージの機能としてシームレスに利用できるようになります。

前提知識の解説

Go言語のflagパッケージ

flagパッケージは、Goプログラムがコマンドライン引数をパースするための機能を提供します。主な機能は以下の通りです。

  • フラグの定義: flag.Int(), flag.String(), flag.Bool()などの関数を使って、フラグの名前、デフォルト値、説明(usage string)を定義します。これらの関数は、フラグの値が格納される変数のポインタを返します。
  • フラグのパース: flag.Parse()を呼び出すことで、コマンドライン引数がパースされ、定義されたフラグ変数に値が設定されます。
  • flag.Valueインターフェース: flagパッケージは、カスタムのフラグタイプを定義するためのValueインターフェースを提供します。このインターフェースは以下の2つのメソッドを持ちます。
    • Set(string) bool: コマンドラインから読み取った文字列値を、フラグの型に変換して設定します。成功した場合はtrueを返します。
    • String() string: フラグの現在の値を文字列として返します。これは、フラグのデフォルト値や現在の値を表示する際に使用されます。

time.Duration

time.Durationは、Go言語のtimeパッケージで定義されている型で、ナノ秒単位の時間間隔を表します。これはint64のエイリアスであり、時間計算やタイマー、タイムアウトなどの処理で広く使用されます。

time.ParseDuration関数

time.ParseDurationは、timeパッケージの関数で、"1h30m", "10s", "500ms"のような文字列をtime.Duration型にパースします。この関数は、時間単位("ns", "us" (or "µs"), "ms", "s", "m", "h")を認識し、それに基づいてtime.Duration値を返します。

fmt.Stringerインターフェース

Go言語のfmtパッケージで定義されているStringerインターフェースは、String() stringメソッドを持つ型が満たすべきインターフェースです。このインターフェースを実装することで、fmt.Printなどの関数でその型の値を人間が読める形式の文字列として出力できるようになります。flag.ValueインターフェースもString()メソッドを含むため、flag.Valueを実装する型は必然的にfmt.Stringerも満たします。

技術的詳細

このコミットでは、flagパッケージにtime.Duration型のフラグを追加するために、以下の主要な変更が行われました。

  1. timeパッケージのインポート: src/pkg/flag/flag.go"time"パッケージがインポートされました。
  2. durationValue型の定義: type durationValue time.Duration この型は、time.Durationを基底とする新しい型として定義され、flag.Valueインターフェースを実装します。
  3. newDurationValue関数の追加: func newDurationValue(val time.Duration, p *time.Duration) *durationValue このヘルパー関数は、durationValueの新しいインスタンスを作成し、指定されたtime.Duration値を初期値として設定します。
  4. durationValueSetメソッドの実装: func (d *durationValue) Set(s string) bool このメソッドは、コマンドラインから渡された文字列stime.Duration型に変換するためにtime.ParseDuration(s)を使用します。パースが成功した場合、その値をdurationValueの基底となるtime.Durationに設定し、trueを返します。エラーが発生した場合はfalseを返します。
  5. durationValueStringメソッドの実装: func (d *durationValue) String() string このメソッドは、durationValueの現在の値を文字列として返します。これは、time.Duration型のString()メソッド(例: 1h30m0s)を呼び出すことで実現されます。
  6. DurationVar関数の追加: func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) func DurationVar(p *time.Duration, name string, value time.Duration, usage string) これらの関数は、既存のIntVar, StringVarなどと同様に、time.Duration型のフラグを定義します。pはフラグの値が格納されるtime.Duration変数のポインタ、nameはフラグ名、valueはデフォルト値、usageは説明文字列です。内部的にはnewDurationValueを使ってdurationValueを作成し、FlagSet.VarまたはcommandLine.Varに渡します。
  7. Duration関数の追加: func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration func Duration(name string, value time.Duration, usage string) *time.Duration これらの関数は、DurationVarと同様にtime.Duration型のフラグを定義しますが、フラグの値が格納されるtime.Duration変数のポインタを返します。これは、flag.Int()flag.String()などの既存の関数と同様のパターンです。
  8. テストの追加: src/pkg/flag/flag_test.goに、新しく追加されたDurationフラグの動作を検証するためのテストケースが追加されました。これには、フラグの定義、値の設定、パース後の値の検証などが含まれます。

これらの変更により、flagパッケージはtime.Duration型をネイティブにサポートし、開発者はコマンドライン引数として時間間隔をより直感的に扱えるようになりました。

コアとなるコードの変更箇所

src/pkg/flag/flag.go

import (
	"os"
	"sort"
	"strconv"
	"time" // + timeパッケージのインポート
)

// ... 既存のコード ...

// -- time.Duration Value // + 新しいDurationフラグの型定義
type durationValue time.Duration

func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
	*p = val
	return (*durationValue)(p)
}

func (d *durationValue) Set(s string) bool {
	v, err := time.ParseDuration(s) // + time.ParseDurationを使用して文字列をパース
	*d = durationValue(v)
	return err == nil
}

func (d *durationValue) String() string { return (*time.Duration)(d).String() } // + time.DurationのString()メソッドを使用

// ... 既存のコード ...

// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag.
func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
	f.Var(newDurationValue(value, p), name, usage)
}

// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
	commandLine.Var(newDurationValue(value, p), name, usage)
}

// Duration defines a time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a time.Duration variable that stores the value of the flag.
func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration {
	p := new(time.Duration)
	f.DurationVar(p, name, value, usage)
	return p
}

// Duration defines a time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a time.Duration variable that stores the value of the flag.
func Duration(name string, value time.Duration, usage string) *time.Duration {
	return commandLine.Duration(name, value, usage)
}

src/pkg/flag/flag_test.go

import (
	"os"
	"sort"
	"testing"
	"time" // + timeパッケージのインポート
)

var (
	// ... 既存のテストフラグ定義 ...
	test_duration = Duration("test_duration", 0, "time.Duration value") // + Durationフラグの追加
)

// ... 既存のテストコード ...

func TestEverything(t *testing.T) {
	// ... 既存のテストケース ...
	case f.Name == "test_duration" && f.Value.String() == desired+"s": // + Durationフラグのテストケース追加
		ok = true
	// ...
	if len(m) != 8 { // + フラグの総数を7から8に変更
		t.Error("VisitAll misses some flags")
		for k, v := range m {
			t.Log(k, *v)
		}
	}
	// ...
	Set("test_duration", "1s") // + Durationフラグに値を設定
	desired = "1"
	Visit(visitor)
	if len(m) != 8 { // + フラグの総数を7から8に変更
		t.Error("Visit fails after set")
		for k, v := range m {
			t.Log(k, *v)
		}
	}
	// ...
}

func testParse(f *FlagSet, t *testing.T) {
	// ... 既存のテストフラグ定義 ...
	durationFlag := f.Duration("duration", 5*time.Second, "time.Duration value") // + Durationフラグの定義
	extra := "one-extra-argument"
	args := []string{
		// ... 既存の引数 ...
		"-duration", "2m", // + Durationフラグの引数追加
		extra,
	}
	// ...
	if *durationFlag != 2*time.Minute { // + Durationフラグの値の検証
		t.Error("duration flag should be 2m, is ", *durationFlag)
	}
	// ...
}

コアとなるコードの解説

durationValue型とflag.Valueインターフェースの実装

  • type durationValue time.Duration: time.Durationを基底とする新しい型を定義しています。これにより、time.Durationの持つメソッド(例: String())を継承しつつ、flag.Valueインターフェースのメソッドを独自に実装できます。
  • func (d *durationValue) Set(s string) bool: このメソッドがflagパッケージの核心部分です。コマンドラインから渡された文字列stime.ParseDuration関数を使ってtime.Duration型に変換します。例えば、-duration=1h30mという引数が与えられた場合、s"1h30m"となり、これがtime.ParseDurationによって適切なtime.Duration値に変換され、d*time.Durationへのポインタ)が指す変数に設定されます。パースに失敗した場合はfalseを返し、flagパッケージがエラーを処理できるようにします。
  • func (d *durationValue) String() string: このメソッドは、フラグの現在の値を文字列として表現するために使用されます。time.Duration型自体がString()メソッドを実装しているため、そのメソッドを呼び出すことで、例えば1h30m0sのような標準的な時間間隔の文字列表現が得られます。これは、--helpオプションなどでフラグのデフォルト値や現在の値を表示する際に利用されます。

DurationVarDurationヘルパー関数

これらの関数は、他のプリミティブ型(Int, Stringなど)と同様に、time.Duration型のフラグを簡単に定義するためのものです。

  • DurationVar(p *time.Duration, name string, value time.Duration, usage string): この関数は、ユーザーが提供するtime.Duration型の変数pに、パースされたフラグの値を格納します。これは、プログラム内で特定の変数にフラグの値を直接バインドしたい場合に便利です。内部的には、newDurationValueを使ってdurationValueのインスタンスを作成し、それをFlagSet.Var(またはグローバルなcommandLine.Var)に渡して、カスタムのflag.Value実装として登録します。
  • Duration(name string, value time.Duration, usage string) *time.Duration: この関数は、新しいtime.Duration変数を内部的に作成し、そのポインタを返します。ユーザーは返されたポインタを介してフラグの値にアクセスします。これは、フラグの値を格納するための新しい変数を宣言する必要がある場合に、より簡潔な構文を提供します。

これらの変更により、flagパッケージはtime.Durationをファーストクラスの型として扱い、Go開発者はコマンドライン引数として時間間隔を扱う際のコードの記述量を減らし、エラーの可能性を低減できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • コミットメッセージと差分情報
  • Go言語のflagパッケージのソースコード
  • Go言語のtimeパッケージのソースコード