Go-MySQL-DriverでカスタムのDialをサポートしていたり圧縮プロトコルサポートのコードをレビューしていたりして、利用している Write() の実装が多様化してきたので、「Write(p)って Read(p)みたいに n が len(p) より小さい場合にループで続きを書き込まなくてい良いのは決まりがあったっけ?」と気になって確認してみました。
まず、 net.Conn
の定義ではReadとWriteは次のようになっています。
// https://pkg.go.dev/net#Conn type Conn interface { // Read reads data from the connection. // Read can be made to time out and return an error after a fixed // time limit; see SetDeadline and SetReadDeadline. Read(b []byte) (n int, err error) // Write writes data to the connection. // Write can be made to time out and return an error after a fixed // time limit; see SetDeadline and SetWriteDeadline. Write(b []byte) (n int, err error)
ここには n と err の関係どころか n の意味自体書かれていません。実際には net.Conn
のReadとWriteは io.Reader
と io.Writer
を満たしているので、そちらのドキュメントを見る必要があります。
Reader is the interface that wraps the basic Read method.
Read reads up to len(p) bytes into p. It returns the number of bytes read (0 <= n <= len(p)) and any error encountered. Even if Read returns n < len(p), it may use all of p as scratch space during the call. If some data is available but not len(p) bytes, Read conventionally returns what is available instead of waiting for more.
(略)
Writer is the interface that wraps the basic Write method.
Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p). Write must not modify the slice data, even temporarily.
まとめると次のようになります。
Readの場合
- 現時点で得られたデータが
len(p)
よりも少ない場合は、後続のデータを待たずにn < len(p)
を返すのが一般的(待っても良い)。len(p)
バイトのデータを待ちたい場合は、io.ReadFull()
やio.ReadAtLeast()
を使える。
n > 0
かつerr != nil
の場合があるので、err
をチェックする前にp[:n]
までのデータを処理する必要がある。- 特に
err == io.EOF
の場合は正常系として頻出する。
- 特に
- Readの実装は、
n
にかかわらず、p
全体を一時メモリとして利用可能。
Writeの場合
n < len(p)
の場合はerr
がnil
でないことが保証されている。- つまり、呼び出し側は
err == nil
の時に改めてp[n:]
を書き込む必要はない。 - そのため、
io.WriteFull()
などは存在しない。
- つまり、呼び出し側は
n > 0
かつerr != nil
の場合があるのはReadと同じ。- ただしReadと違って正常系の
err == io.EOF
が無いので、途中までデータが書き込めたことに対して何か処理が必要で無い限りは無視していい。
- ただしReadと違って正常系の
- Writeは
p
に対して一切書き込みを行わない。
ということで、Writeの方が使う側にとってはかなりシンプルですね。