[インデックス 12890] ファイルの概要
このコミットは、Go言語の標準ライブラリtime
パッケージにおけるUnixNano
関数の振る舞いに関するものです。当初はint64
の範囲を超えるナノ秒値に対してパニックを発生させる変更が提案されましたが、議論の結果、最終的にはドキュメントの更新に留まりました。これは、UnixNano
が返す値がint64
で表現できない場合の挙動が未定義であることを明記し、特にゼロ値のtime.Time
がこの問題を引き起こす可能性があることを示唆するものです。
コミット
commit e4389c1d3058b90fac40bb5bc7578f7e7d580899
Author: Gustavo Niemeyer <gustavo@niemeyer.net>
Date: Thu Apr 12 22:16:31 2012 -0300
time: panic if UnixNano is out of range
R=golang-dev, remyoudompheng, dsymonds, gustavo, dchest, r, rsc
CC=golang-dev
https://golang.org/cl/5985059
---
src/pkg/time/time.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e4389c1d3058b90fac40bb5bc7578f7e7d580899
元コミット内容
元のコミットメッセージは「time: panic if UnixNano is out of range」とあり、UnixNano
関数が返すナノ秒値がint64
の範囲を超えた場合にパニックを発生させることを意図していました。これは、不正な値がサイレントに返されることを防ぎ、開発者に問題の発生を明確に通知するための変更として提案されました。
変更の背景
この変更の背景には、time.Time
型のゼロ値(time.Time{}
)をUnixNano()
で変換しようとした際に、その結果がint64
の範囲外となり、未定義の挙動や誤った値が返されるという問題がありました。Gustavo Niemeyer氏(コミットの著者)は、このような「範囲外」のタイムスタンプに対してサイレントにゴミ値が返されるのは「愚かなユーザーエラー」であり、「罠」であると主張しました。彼は、開発者がこのような問題を早期に発見できるように、パニックを発生させるべきだと考えました。
しかし、この提案はGoコミュニティ内で活発な議論を巻き起こしました。主な反対意見は以下の通りです。
- APIの互換性:
UnixNano
のような低レベルな関数がパニックを起こすように変更することは、既存のコードを破壊する可能性のあるAPI変更となる。 - 一貫性:
time.Unix()
関数は範囲外のナノ秒を処理する際に秒数を調整するアプローチを取っており、UnixNano()
も同様のアプローチを取るか、あるいは単に未定義の挙動をドキュメント化する方が良いのではないか。 - 代替案: パニックではなく、
math.MaxInt64
やmath.MinInt64
に値をクランプするか、あるいは単にドキュメントで未定義の挙動を明記する方が望ましい。 - ゼロ値の扱い: 多くの開発者が
time.Time
のゼロ値を「未設定」のフラグとして使用しており、これがUnixNano
の範囲外となることが問題の根本にある。
これらの議論の結果、最終的にはパニックを導入するのではなく、UnixNano
関数のドキュメントを更新し、int64
で表現できないナノ秒値の場合には結果が未定義であることを明記する方針に落ち着きました。特に、ゼロ値のtime.Time
がこの未定義の挙動を引き起こす可能性があることも追記されました。
前提知識の解説
time.Time
型: Go言語の標準ライブラリtime
パッケージで提供される、特定の時点を表す型です。UnixNano()
関数:time.Time
型のメソッドで、その時刻をUnixエポック(1970年1月1日UTC)からの経過ナノ秒数としてint64
型で返します。int64
型: Go言語における64ビット符号付き整数型です。表現できる値の範囲は、約-9.22 × 10^18から約9.22 × 10^18までです。- Unixエポック: 1970年1月1日00:00:00 UTCを指します。Unix時間はこの時点からの経過秒数(またはナノ秒数)で表現されます。
- パニック (panic): Go言語におけるランタイムエラーの一種で、プログラムの実行を停止させます。通常、回復不可能なエラーやプログラマの論理的な誤りを示すために使用されます。
- ゼロ値の
time.Time
:time.Time{}
で初期化されたtime.Time
型の値は、内部的にはすべてのフィールドがゼロに設定されています。これは、Unixエポックよりもはるか昔の時刻(西暦1年1月1日)を指し、UnixNano()
でナノ秒に変換しようとすると、int64
の最小値よりもさらに小さな値となり、オーバーフローが発生します。
技術的詳細
time.Time
型は、内部的に秒数とナノ秒数を保持しています。UnixNano()
関数は、これらの内部表現をUnixエポックからのナノ秒数に変換してint64
として返します。
int64
で表現できるナノ秒の範囲は非常に広いですが、それでも限界があります。特に、time.Time
のゼロ値は、Unixエポックから非常に遠い過去の時刻(西暦1年1月1日)を表します。この時刻をナノ秒に変換しようとすると、int64
の負の最大値(math.MinInt64
)よりも小さな値となり、オーバーフローが発生します。
当初の提案では、このようなオーバーフローが発生した場合にパニックを発生させることで、開発者がこの問題を認識し、適切な対処(例えば、time.Time
のゼロ値をUnixNano
に渡さない、あるいはtime.IsZero()
でチェックする)を行うことを促そうとしました。
しかし、Go言語の設計哲学では、低レベルなライブラリ関数は可能な限りパニックを避けるべきであるという考え方があります。パニックは通常、回復不可能なエラーやプログラマの論理的な誤りを示すために予約されており、APIの利用方法の誤りに対しては、エラーを返すか、あるいはドキュメントで明確に未定義の挙動を記述することが推奨されます。
この議論の結果、UnixNano
が返す値がint64
の範囲外となる場合の挙動を「未定義」とすることで合意されました。これは、Go言語の他の部分でも見られるアプローチであり、特定の入力に対して関数が有効な結果を保証しないことを意味します。開発者は、このような未定義の挙動に依存すべきではなく、入力値が関数の期待する範囲内にあることを確認する責任があります。
特に、ゼロ値のtime.Time
がUnixNano
の範囲外となることが明記されたことで、開発者はtime.Time
のゼロ値を「未設定」のフラグとして使用する際に、UnixNano
を呼び出す前にそのゼロ値性を確認する必要があることを明確に理解できるようになりました。
コアとなるコードの変更箇所
--- a/src/pkg/time/time.go
+++ b/src/pkg/time/time.go
@@ -763,7 +763,9 @@ func (t Time) Unix() int64 {
}
// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
-// since January 1, 1970 UTC.
+// since January 1, 1970 UTC. The result is undefined if the Unix time
+// in nanoseconds cannot be represented by an int64. Note that this
+// means the result of calling UnixNano on the zero Time is undefined.
func (t Time) UnixNano() int64 {
return (t.sec+internalToUnix)*1e9 + int64(t.nsec)
}
コアとなるコードの解説
変更はsrc/pkg/time/time.go
ファイルのUnixNano
関数のコメント部分にあります。
元のコメントは以下の通りでした。
// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC.
これが以下のように変更されました。
// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
// since January 1, 1970 UTC. The result is undefined if the Unix time
// in nanoseconds cannot be represented by an int64. Note that this
// means the result of calling UnixNano on the zero Time is undefined.
追加された行は以下の2行です。
The result is undefined if the Unix time in nanoseconds cannot be represented by an int64.
- これは、
UnixNano
が返すナノ秒数がint64
型で表現できない場合、その結果が「未定義 (undefined)」であることを明確に述べています。これは、関数が特定の入力に対して有効な結果を保証しないことを意味します。
- これは、
Note that this means the result of calling UnixNano on the zero Time is undefined.
- この行は、特に
time.Time
のゼロ値に対してUnixNano
を呼び出した場合、結果が未定義になることを明示的に注意喚起しています。これは、ゼロ値がint64
の範囲外のナノ秒数を表すためです。
- この行は、特に
この変更は、コードのロジック自体には手を加えずに、関数の振る舞いに関する重要な制約と注意点をドキュメントとして追加することで、開発者がUnixNano
をより安全かつ正確に使用できるようにすることを目的としています。
関連リンク
- Go CL 5985059: https://golang.org/cl/5985059
参考にした情報源リンク
- Go CL 5985059 (Code Review Discussion): https://golang.org/cl/5985059