こんにちは、TIGの辻です。Go 1.16連載の3記事目です。
Go1.16でアップデートがあった io/ioutil
パッケージが "deprecated"
になる話題のまとめです。
サマリ
- Go1.16から
io/ioutil
パッケージの機能がos
とio
パッケージに移行した - これから新しく実装するコードは
io
やos
パッケージの新しい関数を使うことが推奨される io/ioutil
パッケージが"deprecated"
になるが"deprecated"
といっても将来壊れる、ということではない- 既存のコードは動作し続ける
go fix
コマンドは未対応
内容
Go1.16から io/ioutil
パッケージに含まれる関数が "deprecated"
になります。関連するプロポーザルは #40025 と #42026 です。Package names で良くないパッケージ名として紹介されているように、一般的には util
などというパッケージ名は、純粋にユーティリティを提供するパッケージではない限り避けるべき名前です。io/ioutil
の命名に関しては #19660 にあるように以前から理解しにくいと言われていました。
io/ioutil
パッケージは主にパッケージのインポートサイクルを回避するために存在します。Codebase Refactoring にあるようにGoの io
パッケージは os
パッケージに依存することはできません。io/ioutil
パッケージが直接/間接的に os
パッケージと io
パッケージを参照することで、インポートサイクルを回避したということです。
さて io/ioutil
では以下の8つの型/関数がExportされています。
Discard
NopCloser
ReadAll
ReadDir
ReadFile
TempDir
TempFile
WriteFile
これらのすべての型/関数が "deprecated"
になり、io
パッケージと os
パッケージに機能が移動します。
io
パッケージに移動する型/関数Discard
NopCloser
ReadAll
os
パッケージに移動する関数ReadDir
ReadFile
TempDir
->MkdirTemp
(リネーム)TempFile
->CreateTemp
(リネーム)WriteFile
io
パッケージに移動する型/関数
io
パッケージに移動する型/関数は以下の3つです。
Discard
NopCloser
ReadAll
#40025 によると Discard
と NopCloser
はたまたま io/ioutil
パッケージに含まれてしまった、とのことです。ReadAll
も本来であれば Reader
や Writer
のヘルパー関数が io
パッケージで提供されていることにならって io
パッケージに含まれる機能でしたが、bytes.Buffer
にアクセスする実装となっていており、bytes
パッケージが os
パッケージに依存する関係で io/ioutil
パッケージに追いやられました。
しかしながら ReadAll
は bytes.Buffer
を使う必要がないため、Go1.16の ReadAll
の実装は bytes
パッケージを使わない実装になっています。
- Go1.15の
ioutil.ReadAll
の実装(io/ioutil/ioutil.go)
// readAll reads from r until an error or EOF and returns the data it read |
- Go1.16の
io.ReadAll
の実装(io/io.go)
// ReadAll reads from r until an error or EOF and returns the data it read. |
バイトスライスの容量を拡張するときに一旦 byte
型のゼロ値0をバイトスライスにappendして容量を拡張し、長さは元のスライスの長さに戻すことでスライスの容量だけ予め拡張するオシャレな実装になっています。
io/ioutil
パッケージからは io.ReadAll
に委譲するように実装されています。
- Go1.16の
ioutil.ReadAll
の実装(io/ioutil/ioutil.go)
// ReadAll reads from r until an error or EOF and returns the data it read. |
os
パッケージに移動する関数
os
パッケージに移行する関数は以下の5つです。io/ioutil
パッケージに含まれていた下記の関数はOSファイルシステムのヘルパー関数です。
ReadDir
ReadFile
TempDir
->MkdirTemp
(リネーム)TempFile
->CreateTemp
(リネーム)WriteFile
リネーム
ioutil
の2つの関数がリネームになっています。TempDir
関数は既に os
パッケージに存在します。os.TempDir
関数はOSのデフォルトの一時ディレクトリを返却するAPIです。ioutil
パッケージの TempFile
は今回移行した os
パッケージでは MkdirTemp
という関数になっています。また MkdirTemp
との命名の整合をとるために ioutil
パッケージに存在していた TempFile
は CreateTemp
という命名になっています。
シグネチャ
ReadDir
は返却するシグネチャが更新されています。移行後の ReadDir
は fs.FileInfo
ではなく os.DirEntry
を返却するようになっています。
- Go1.16の
os.ReadDir
の実装(os/dir.go)
// ReadDir reads the named directory, |
- Go1.16の
ioutil.ReadDir
の実装(io/ioutil/ioutil.go)
// ReadDir reads the directory named by dirname and returns |
"deprecated"
とは
"deprecated"
とあるため、将来的に ioutil
パッケージを使っているプログラムが動作しなくなるのでは? と思う方もいるかもしれません。"deprecated"
が何を意味するのか Russ Cox 氏がツイートしています。
Seeing discussion of io/ioutil deprecation in various places.
— Russ Cox (@_rsc) January 19, 2021
To be clear: in Go, “deprecated” means only “there is a better, preferred way.” It does not mean “will break in the future.”
Today’s programs using ioutil.ReadFile and friends will keep working forever. #golang
すわなち「より良い、好ましい方法がある」という意味で「将来的に壊れる」という意味ではない、ということです。Go 1 and the Future of Go Programs にもあるように、少なくともGo1の間はソースレベルの後方互換性が保たれます。
ioutil
パッケージの関数はとても便利ですので、多くの実装で使用されていますが、ioutil
パッケージを使っている既存の実装は引き続きそのまま動作します。新規に実装する場合は os
パッケージや io
パッケージに含まれる関数を利用するのが推奨されています。
"deprecated"
になったパッケージの移行
go fix
を使うと、古いAPIを使用しているGoのプログラムを検出し、新しいAPIに書き換えることができます。馴染みがあるAPIだと context
パッケージがあげられます。もともとは golang.org/x
リポジトリ配下の準標準ライブラリとして実装されていた context
パッケージですが、Go1.7以降は標準ライブラリに移行しています。以下のファイルがあった場合に go tool fix -diff main.go
とすると新しいAPIに書き換えることができます。
- main.go
package main |
Diffを確認できます。-diff
オプションを除けば、ファイルを直接更新できます。
$ go tool fix -diff main.go |
ioutil
パッケージの go fix
に関して #42026 で言及されています。残念ながら #32816 のプロポーザルには含まれない だろう、とコメントしています。将来的に go fix
コマンド一発で既存の ioutil
パッケージを使っているAPIから io
パッケージや os
パッケージのAPIへ移行ができると嬉しいですね。