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

[インデックス 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.MaxInt64math.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.TimeUnixNanoの範囲外となることが明記されたことで、開発者は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行です。

  1. The result is undefined if the Unix time in nanoseconds cannot be represented by an int64.
    • これは、UnixNanoが返すナノ秒数がint64型で表現できない場合、その結果が「未定義 (undefined)」であることを明確に述べています。これは、関数が特定の入力に対して有効な結果を保証しないことを意味します。
  2. Note that this means the result of calling UnixNano on the zero Time is undefined.
    • この行は、特にtime.Timeのゼロ値に対してUnixNanoを呼び出した場合、結果が未定義になることを明示的に注意喚起しています。これは、ゼロ値がint64の範囲外のナノ秒数を表すためです。

この変更は、コードのロジック自体には手を加えずに、関数の振る舞いに関する重要な制約と注意点をドキュメントとして追加することで、開発者がUnixNanoをより安全かつ正確に使用できるようにすることを目的としています。

関連リンク

参考にした情報源リンク