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

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

このコミットは、Go言語の標準ライブラリにおける time パッケージの AfterFunc 関数が、時間間隔の指定に int64 型のナノ秒ではなく、より型安全で表現力の高い time.Duration 型を使用するように変更されたものです。これにより、コードの可読性と堅牢性が向上し、時間単位の誤用を防ぐことができます。

コミット

commit 2949f3b659f2efb161efca19ff92398fbf37e081
Author: David Symonds <dsymonds@golang.org>
Date:   Thu Dec 8 15:42:44 2011 +1100

    time: use Duration for AfterFunc.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5465043

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

https://github.com/golang/go/commit/2949f3b659f2efb161efca19ff92398fbf37e081

元コミット内容

time: use Duration for AfterFunc.

このコミットは、time パッケージの AfterFunc 関数が、時間指定に time.Duration 型を使用するように変更することを目的としています。

変更の背景

Go言語の初期のバージョンでは、時間間隔をナノ秒単位の int64 型で表現することが一般的でした。しかし、int64 は単なる整数型であるため、それが時間間隔を表すのか、あるいは他の数値(例えば、バイト数やカウントなど)を表すのかがコード上で不明瞭になるという問題がありました。これにより、開発者が誤った単位で数値を渡してしまう可能性や、コードの意図が伝わりにくくなるという課題がありました。

time.Duration 型は、Go言語の time パッケージで定義されているカスタム型であり、時間間隔を明示的に表現するために導入されました。この型を使用することで、コンパイラが型チェックを行い、誤った型の値が渡されることを防ぐことができます。また、コードを読む人にとっても、その値が時間間隔であることを一目で理解できるようになります。

このコミットは、このような背景から、AfterFunc 関数がより安全で表現力の高い time.Duration 型を使用するように変更することで、Go言語のAPI設計の一貫性と堅牢性を向上させることを目的としています。

前提知識の解説

Go言語の time パッケージ

Go言語の time パッケージは、時間に関する機能を提供する標準ライブラリです。時刻の表現 (time.Time)、時間間隔の表現 (time.Duration)、タイマー、ティック、日付のフォーマットなど、様々な機能が含まれています。

time.Duration

time.Duration は、Go言語の time パッケージで定義されている型であり、時間間隔を表します。これは int64 のエイリアスとして定義されており、内部的にはナノ秒単位で時間を保持します。しかし、単なる int64 とは異なり、time.Duration 型は時間間隔であることを明示的に示し、コンパイラによる型チェックの恩恵を受けます。

time.Duration 型は、以下のような定数とメソッドを提供します。

  • 定数: time.Nanosecond, time.Microsecond, time.Millisecond, time.Second, time.Minute, time.Hour など、様々な時間単位を表す定数が定義されています。これにより、5 * time.Second のように、人間が理解しやすい形で時間間隔を記述できます。
  • メソッド: Seconds(), Milliseconds(), String() など、時間間隔を異なる単位で取得したり、文字列として表現したりするためのメソッドが提供されています。

time.AfterFunc 関数

time.AfterFunc は、指定された時間間隔が経過した後に、別のゴルーチンで関数を実行するための関数です。この関数は *time.Timer を返します。このタイマーは、Stop() メソッドを呼び出すことで、関数が実行される前にキャンセルすることができます。

変更前は、AfterFunc の第一引数は ns int64 であり、ナノ秒単位の整数値を受け取っていました。変更後は d Duration となり、time.Duration 型の値を受け取るようになります。

技術的詳細

このコミットの主要な変更点は、time.AfterFunc 関数のシグネチャと、それに伴う内部実装および呼び出し箇所の修正です。

time.AfterFunc のシグネチャ変更

変更前:

func AfterFunc(ns int64, f func()) *Timer

変更後:

func AfterFunc(d Duration, f func()) *Timer

この変更により、AfterFunc を呼び出す際には、int64 のナノ秒ではなく、time.Duration 型の値を渡すことが必須となります。これにより、例えば time.AfterFunc(5, func(){}) のような記述はコンパイルエラーとなり、time.AfterFunc(5 * time.Second, func(){}) のように明示的に時間単位を指定する必要が生じます。これは、開発者が意図しない時間単位で関数を呼び出すことを防ぐ上で非常に重要です。

内部実装の変更

time.AfterFunc の内部では、タイマーの when フィールド(タイマーが発火する時刻)を計算する際に、引数で受け取った時間間隔をナノ秒に変換して使用します。

変更前:

when: nano() + ns,

変更後:

when: nano() + int64(d),

time.Duration 型は内部的に int64 でナノ秒を保持しているため、int64(d) と型変換することで、以前と同様にナノ秒単位の値を when フィールドに加算しています。この変更は、外部APIの型安全性を高めつつ、内部の処理ロジックは大きく変えることなく実現されています。

呼び出し箇所の変更

time.AfterFunc のシグネチャ変更に伴い、この関数を呼び出している既存のコードも修正されています。主にテストコードにおいて、1e9 (10億ナノ秒) のような int64 リテラルが 1 * time.Secondtime.Second のように time.Duration 型の定数と演算子を組み合わせた表現に置き換えられています。これにより、コードの意図がより明確になり、時間単位の誤解がなくなります。

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

このコミットで変更された主要なファイルは以下の通りです。

  • src/pkg/net/http/serve_test.go: http パッケージのテストファイルで、goTimeout 関数や time.After の呼び出し箇所が time.Duration を使用するように変更されています。
  • src/pkg/testing/testing.go: testing パッケージの内部で、テストのタイムアウト処理に使用される time.AfterFunc の呼び出しが変更されています。
  • src/pkg/time/sleep.go: time パッケージの AfterFunc 関数のシグネチャと内部実装が変更されています。これがこのコミットの核心部分です。
  • src/pkg/time/sleep_test.go: time パッケージのテストファイルで、AfterFuncNewTimer の呼び出し箇所が time.Duration を使用するように変更されています。

コアとなるコードの解説

src/pkg/time/sleep.go の変更

--- a/src/pkg/time/sleep.go
+++ b/src/pkg/time/sleep.go
@@ -72,13 +72,13 @@ func After(d Duration) <-chan Time {
 	return NewTimer(d).C
 }
 
-// AfterFunc waits at least ns nanoseconds before calling f
+// AfterFunc waits for the duration to elapse and then calls f
 // in its own goroutine. It returns a Timer that can
 // be used to cancel the call using its Stop method.
-func AfterFunc(ns int64, f func()) *Timer {
+func AfterFunc(d Duration, f func()) *Timer {
 	t := &Timer{
 		r: runtimeTimer{
-			when: nano() + ns,
+			when: nano() + int64(d),
 			f:    goFunc,
 			arg:  f,
 		},

この差分は、time.AfterFunc 関数の定義そのものです。

  1. 関数のコメントが「AfterFunc waits at least ns nanoseconds before calling f」から「AfterFunc waits for the duration to elapse and then calls f」に変更され、引数がナノ秒ではなく期間(Duration)であることを明確にしています。
  2. 関数のシグネチャが func AfterFunc(ns int64, f func()) *Timer から func AfterFunc(d Duration, f func()) *Timer に変更されました。これにより、第一引数の型が int64 から time.Duration に変わりました。
  3. 内部の実装で、when: nano() + ns, の部分が when: nano() + int64(d), に変更されました。time.Duration 型の変数 dint64 に明示的に型変換することで、内部的には引き続きナノ秒単位の整数値として扱っています。これは time.Durationint64 のエイリアスであるため可能です。

src/pkg/net/http/serve_test.go の変更例

--- a/src/pkg/net/http/serve_test.go
+++ b/src/pkg/net/http/serve_test.go
@@ -361,7 +361,7 @@ func TestIdentityResponse(t *testing.T) {
 
 	// The ReadAll will hang for a failing test, so use a Timer to
 	// fail explicitly.
-	goTimeout(t, 2e9, func() {
+	goTimeout(t, 2*time.Second, func() {
 		got, _ := ioutil.ReadAll(conn)
 		expectedSuffix := "\r\n\r\ntoo short"
 		if !strings.HasSuffix(string(got), expectedSuffix) {
@@ -395,7 +395,7 @@ func testTcpConnectionCloses(t *testing.T, req string, h Handler) {
 	success := make(chan bool)
 	go func() {
 		select {
-		case <-time.After(5e9):
+		case <-time.After(5 * time.Second):
 			t.Fatal("body not closed after 5s")
 		case <-success:
 		}

この差分は、http パッケージのテストコードにおける変更例です。

  1. goTimeout 関数の呼び出しで、第二引数が 2e9 (20億ナノ秒) から 2*time.Second に変更されています。goTimeout 関数も同様に引数の型が int64 から time.Duration に変更されています。
  2. time.After 関数の呼び出しで、引数が 5e9 から 5 * time.Second に変更されています。time.Aftertime.Duration を引数に取る関数です。

これらの変更は、time.Duration 型の導入によって、時間間隔の表現がより明確になり、コードの可読性と保守性が向上したことを示しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (time パッケージ)
  • Go言語のソースコード (特に src/pkg/time/sleep.go)
  • Go言語のコミット履歴 (GitHub)
  • Go言語のコードレビューシステム (Gerrit)