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

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

このコミットは、Go言語の標準ライブラリ crypto/rand パッケージ内の ExampleRead 関数のサンプルコードを簡素化するものです。具体的には、io.ReadFull の特性をより適切に活用することで、不要な条件チェックを削除しています。

コミット

  • コミットハッシュ: ce00562607e0967ea8329aa4728a5bf4e1a8e666
  • Author: Rob Pike r@golang.org
  • Date: Mon Aug 12 12:52:23 2013 +1000

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

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

元コミット内容

    crypto/rand: simplify example to exploit properties of ReadFull
    No need for the complex condition.
    Fixes #6089
    
    R=golang-dev, mischief, adg
    CC=golang-dev
    https://golang.org/cl/12731043

変更の背景

この変更の背景には、io.ReadFull 関数の挙動に対するより深い理解と、それに基づくコードの簡素化があります。元のコードでは、io.ReadFull の呼び出し後に、読み込んだバイト数 n が期待されるバイト数 len(b) と等しいかどうか、そしてエラー err が発生していないかどうかを両方チェックしていました。

しかし、io.ReadFull の設計上の特性として、この関数は「指定されたバイト数を正確に読み込む」ことを保証します。もし指定されたバイト数を読み込めなかった場合、io.ReadFull は必ず非nilのエラーを返します。したがって、n != len(b) という条件は、err != nil という条件が真である場合にのみ発生するため、冗長なチェックとなります。

この冗長性を排除し、よりGoらしい(idiomatic Go)エラーハンドリングにすることで、コードの可読性と簡潔性を向上させるのがこのコミットの目的です。また、この変更はIssue #6089を修正すると明記されています。

前提知識の解説

io.Reader インターフェース

Go言語における io.Reader は、データを読み込むための基本的なインターフェースです。

type Reader interface {
    Read(p []byte) (n int, err error)
}

Read メソッドは、データを p に最大 len(p) バイトまで読み込み、読み込んだバイト数 n とエラー err を返します。エラーが io.EOF の場合、それ以上読み込むデータがないことを示します。

io.ReadFull 関数

io.ReadFullio パッケージで提供されるヘルパー関数で、指定された io.Reader から、与えられたバイトスライス buf を完全に埋めるまでデータを読み込もうとします。

func ReadFull(r Reader, buf []byte) (n int, err error)

この関数の重要な特性は以下の通りです。

  1. 正確なバイト数の読み込み: io.ReadFulllen(buf) バイトを正確に読み込むことを試みます。
  2. エラーハンドリング:
    • 成功: len(buf) バイトを完全に読み込めた場合、nlen(buf) となり、errnil となります。
    • io.EOF: io.ReadFull が呼び出された時点で、まだ1バイトも読み込んでいない状態でストリームの終端に達した場合(つまり、n0 の場合)、io.EOF エラーを返します。
    • io.ErrUnexpectedEOF: io.ReadFulllen(buf) バイトを読み込む前にストリームの終端に達した場合(つまり、n > 0 だが n < len(buf) の場合)、io.ErrUnexpectedEOF エラーを返します。これは、期待したよりも少ないバイト数しか読み込めなかったことを示します。
    • その他のエラー: 基になる io.Reader が他のエラーを返した場合、io.ReadFull はそのエラーをそのまま返します。

この特性から、io.ReadFullnil ではないエラーを返した場合、それは常に len(buf) バイトを読み込めなかったことを意味します。逆に、エラーが nil であれば、len(buf) バイトが完全に読み込まれたことを意味します。

技術的詳細

元のコードでは、io.ReadFull の戻り値 nerr を両方チェックしていました。

n, err := io.ReadFull(rand.Reader, b)
if n != len(b) || err != nil {
    fmt.Println("error:", err)
    return
}

ここで、io.ReadFull の特性を考慮すると、以下のことが言えます。

  • もし io.ReadFulllen(b) バイトを完全に読み込めた場合、nlen(b) となり、errnil となります。この場合、n != len(b)falseerr != nilfalse となり、if 文の条件は false となります。
  • もし io.ReadFulllen(b) バイトを完全に読み込めなかった場合、io.ReadFull は必ず非nilのエラー (io.EOF, io.ErrUnexpectedEOF, または基になるリーダーからの他のエラー) を返します。この場合、err != niltrue となり、if 文の条件は true となります。

したがって、n != len(b) という条件は err != niltrue である場合にのみ意味を持つため、err != nil のチェックだけで十分であり、n のチェックは冗長になります。

新しいコードでは、この冗長なチェックを削除し、err のみを確認するように変更されています。

_, err := io.ReadFull(rand.Reader, b)
if err != nil {
    fmt.Println("error:", err)
    return
}

これにより、コードはより簡潔になり、io.ReadFull の意図する挙動をより明確に反映しています。また、n の戻り値が使用されないため、_ で破棄することで、未使用変数の警告も回避できます。

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

--- a/src/pkg/crypto/rand/example_test.go
+++ b/src/pkg/crypto/rand/example_test.go
@@ -16,8 +16,8 @@ import (
  func ExampleRead() {
  	c := 10
  	b := make([]byte, c)
- 	n, err := io.ReadFull(rand.Reader, b)
- 	if n != len(b) || err != nil {
+ 	_, err := io.ReadFull(rand.Reader, b)
+ 	if err != nil {
  		fmt.Println("error:", err)
  		return
  	}

コアとなるコードの解説

変更は src/pkg/crypto/rand/example_test.go ファイルの ExampleRead 関数内で行われています。

  1. 変更前:

    n, err := io.ReadFull(rand.Reader, b)
    if n != len(b) || err != nil {
        fmt.Println("error:", err)
        return
    }
    

    ここでは io.ReadFull の戻り値 n (読み込んだバイト数) と err (エラー) の両方を受け取っています。そして、if 文で n が期待されるバイト数 len(b) と異なるか、または errnil でない場合にエラー処理を行っています。

  2. 変更後:

    _, err := io.ReadFull(rand.Reader, b)
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    

    変更後では、io.ReadFull の戻り値のうち n_ (ブランク識別子) で破棄され、err のみを受け取っています。そして、if 文では errnil でないかどうかのチェックのみを行っています。

この変更により、io.ReadFull の「指定されたバイト数を読み込めなかった場合は必ずエラーを返す」という保証を最大限に活用し、コードをより簡潔でGoのイディオムに沿ったものにしています。

関連リンク

参考にした情報源リンク