Description
io/ioutil exists mainly to avoid import cycles: it can make use of packages that themselves depend on io.
The practical effect of this is that io/ioutil is mainly about convenient OS file access: ReadDir, ReadFile, TempDir, TempFile, and WriteFile. These are here because os was too low level, and io cannot import os. (For more about why io
should not depend on os
, see “Codebase Refactoring (with help from Go)”, especially section 3.)
The three functions that don't quite fit this bill are Discard, NopCloser, and ReadAll. These are general, useful I/O helpers without dependencies, at least conceptually. They don't need to be in io/ioutil.
It is confusing that nearly all reader and writer adapters—like LimitReader, MultiReader, MultiWriter, TeeReader, SectionReader—are in io, while NopCloser and Discard hide in io/ioutil. They should join the others. I think it was mostly accidental that they ended up in io/ioutil.
It is similarly confusing that the reader and writer helpers—Copy, CopyBuffer, CopyN, ReadAtLeast, ReadFull, WriteString—are all in io, while ReadAll alone hides in io/ioutil. It too should join the others.
In the case of ReadAll, there is a clearer reason why it was relegated to io/ioutil: it imports bytes for access to bytes.Buffer, and bytes imports io. But ReadAll need not use bytes.Buffer, especially now that we have append
built in.
Obviously we cannot delete these three from io/ioutil. But we can move the implementations to io and leave wrappers behind. That will be easier for new users, and it lets more packages do general I/O without a dependency on os.
Edit: To be clear, no existing code will break. The old symbols will remain behind, as wrappers of the ones in io.