Go's ioutil package to be deprecated in 1.16

New features may get all the coverage, but work on core packages has not stopped.

It was easy to see the motivation for the deprecation in Go tech lead Russ Cox’s proposal:

io/ioutil, like most things with util in the name, has turned out to be a poorly defined and hard to understand collection of things.

In a series of a few changes, the entire ioutil package is due to become deprecated starting from Go 1.16.

Existing code using ioutil will continue to work; ioutil will consist of simple wrappers to new functions which reside in the io and os packages.

Initially, a proposal by Cox back in July was approved which saw the move of general I/O helpers, like ioutil.ReadAll, out of package ioutil and into io.

Remaining code in ioutil consisted of OS file system helpers, like ReadFile. A few months later, a second proposal by Cox suggested moving those into package os. Acceptance of the proposal was the nail in ioutil’s coffin.

The deprecation of ioutil comes as part of what will be a significant Go release. Module-aware mode is enabled by default. The darwin/arm64 port will be released which means Go will be natively supported on Apple’s new Macs using their M1 SoC. A new io/fs package, demoed last year, will make its debut.

Whilst new features tend to get more journalistic coverage, long time Go programmers may be encouraged by this recent deprecation. Relatively thankless work such as this suggests a dedication to keeping the core of the language clean and easy to understand; values that brought so many programmers to the language in the first place.

Example Migration

Migration of code using ioutil should be straightforward. Here is an example migration adapted from package wal in the popular Prometheus project:

package wal

import (
    "fmt"
    "io/ioutil"
    "os"
    ...
)

func TestLastCheckpoint(t *testing.T) {
    dir, err := ioutil.TempDir("", "test_checkpoint")
    require.NoError(t, err)
    defer func() {
        require.NoError(t, os.RemoveAll(dir))
    }()
...

We rename ioutil.TempDir to os.MkDirTemp. Now that ioutil is no longer needed, and os was already imported, we have one less dependency:

package wal

import (
    "fmt"
    "os"
    ...
)

func TestLastCheckpoint(t *testing.T) {
    dir, err := os.MkDirTemp("", "test_checkpoint")
    require.NoError(t, err)
    defer func() {
        require.NoError(t, os.RemoveAll(dir))
    }()
...

Member of the Go team Bryan Mills has an open proposal for the go fix command to automatically migrate deprecated code. This means existing code using ioutil may not have to be changed by hand. Discussion of the proposal stalled over a year ago. Additional feedback from the proposal review committee may be requested later this year.