Closed

#golang io/ioutil の非推奨化(deprecation)について

11

対象となる変数・関数は以下の通り:

< 1.15 1.16
ioutil.Discard io.Discard
ioutil.NopCloser() io.NopCloser()
ioutil.ReadAll() io.ReadAll()
ioutil.ReadDir() os.ReadDir()
ioutil.ReadFile() os.ReadFile()
ioutil.TempDir() os.MkdirTemp()
ioutil.TempFile() os.CreateTemp()
ioutil.WriteFile() os.WriteFile()

なお deprecation といっても物理的に廃止・削除されるわけでなく(和訳にある通り)非推奨化といったニュアンスが近いと思う。

とはいえ,該当の関数を使っている場合は早めに換装(refactoring)していくのがいいだろう。

Go 1.16 における ioutil.Discard の実装は以下の通り

io/ioutil/ioutil.go
// Discard is an io.Writer on which all Write calls succeed
// without doing anything.
//
// As of Go 1.16, this value is simply io.Discard.
var Discard io.Writer = io.Discard

Go 1.16 における ioutil.NopCloser() 関数の実装は以下の通り

io/ioutil/ioutil.go
// NopCloser returns a ReadCloser with a no-op Close method wrapping
// the provided Reader r.
//
// As of Go 1.16, this function simply calls io.NopCloser.
func NopCloser(r io.Reader) io.ReadCloser {
    return io.NopCloser(r)
}

Go 1.16 における ioutil.ReadAll() 関数の実装は以下の通り

io/ioutil/ioutil.go
// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
//
// As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error) {
    return io.ReadAll(r)
}

Go 1.16 における ioutil.ReadDir() 関数の実装は以下の通り

io/ioutil/ioutil.go
// ReadDir reads the directory named by dirname and returns
// a list of fs.FileInfo for the directory's contents,
// sorted by filename. If an error occurs reading the directory,
// ReadDir returns no directory entries along with the error.
//
// As of Go 1.16, os.ReadDir is a more efficient and correct choice:
// it returns a list of fs.DirEntry instead of fs.FileInfo,
// and it returns partial results in the case of an error
// midway through reading a directory.
func ReadDir(dirname string) ([]fs.FileInfo, error) {
    f, err := os.Open(dirname)
    if err != nil {
        return nil, err
    }
    list, err := f.Readdir(-1)
    f.Close()
    if err != nil {
        return nil, err
    }
    sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
    return list, nil
}

返り値の型が新しい io/fs パッケージの fs.FileInfo 型(内容は 1.15 までの os.FileInfo と同じ)のスライスに変更されているので注意。

io/fs/fs.go
// A FileInfo describes a file and is returned by Stat.
type FileInfo interface {
    Name() string       // base name of the file
    Size() int64        // length in bytes for regular files; system-dependent for others
    Mode() FileMode     // file mode bits
    ModTime() time.Time // modification time
    IsDir() bool        // abbreviation for Mode().IsDir()
    Sys() interface{}   // underlying data source (can return nil)
}

なお 1.16 の os.FileInfo 型は

os/types.go
// A FileInfo describes a file and is returned by Stat and Lstat.
type FileInfo = fs.FileInfo

と fs.FileInfo 型の type alias として再定義されている。

一方,新しい io.ReadDir() 関数の方は

io/io.go
// A DirEntry is an entry read from a directory
// (using the ReadDir function or a File's ReadDir method).
type DirEntry = fs.DirEntry

// ReadDir reads the named directory,
// returning all its directory entries sorted by filename.
// If an error occurs reading the directory,
// ReadDir returns the entries it was able to read before the error,
// along with the error.
func ReadDir(name string) ([]DirEntry, error) {
    f, err := Open(name)
    if err != nil {
        return nil, err
    }
    defer f.Close()

    dirs, err := f.ReadDir(-1)
    sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() })
    return dirs, err
}

となっていて,返り値が fs.DirEntry 型(の type alias)のスライスになっている。

fs.DirEntry 型も interface 型で

io/fs/fs.go
// A DirEntry is an entry read from a directory
// (using the ReadDir function or a ReadDirFile's ReadDir method).
type DirEntry interface {
    // Name returns the name of the file (or subdirectory) described by the entry.
    // This name is only the final element of the path (the base name), not the entire path.
    // For example, Name would return "hello.go" not "/home/gopher/hello.go".
    Name() string

    // IsDir reports whether the entry describes a directory.
    IsDir() bool

    // Type returns the type bits for the entry.
    // The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
    Type() FileMode

    // Info returns the FileInfo for the file or subdirectory described by the entry.
    // The returned FileInfo may be from the time of the original directory read
    // or from the time of the call to Info. If the file has been removed or renamed
    // since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
    // If the entry denotes a symbolic link, Info reports the information about the link itself,
    // not the link's target.
    Info() (FileInfo, error)
}

と定義されている。うーん,コンパチじゃないのか。

Go 1.16 における ioutil.TempDir() 関数の実装は以下の通り

io/ioutil/tempfile.go
// TempDir creates a new temporary directory in the directory dir.
// The directory name is generated by taking pattern and applying a
// random string to the end. If pattern includes a "*", the random string
// replaces the last "*". TempDir returns the name of the new directory.
// If dir is the empty string, TempDir uses the
// default directory for temporary files (see os.TempDir).
// Multiple programs calling TempDir simultaneously
// will not choose the same directory. It is the caller's responsibility
// to remove the directory when no longer needed.
func TempDir(dir, pattern string) (name string, err error) {
    if dir == "" {
        dir = os.TempDir()
    }

    prefix, suffix, err := prefixAndSuffix(pattern)
    if err != nil {
        return
    }

    nconflict := 0
    for i := 0; i < 10000; i++ {
        try := filepath.Join(dir, prefix+nextRandom()+suffix)
        err = os.Mkdir(try, 0700)
        if os.IsExist(err) {
            if nconflict++; nconflict > 10 {
                randmu.Lock()
                rand = reseed()
                randmu.Unlock()
            }
            continue
        }
        if os.IsNotExist(err) {
            if _, err := os.Stat(dir); os.IsNotExist(err) {
                return "", err
            }
        }
        if err == nil {
            name = try
        }
        break
    }
    return
}

新しい os.MkdirTemp() 関数のほうは以下の通り。

os/tempfile.go
// MkdirTemp creates a new temporary directory in the directory dir
// and returns the pathname of the new directory.
// The new directory's name is generated by adding a random string to the end of pattern.
// If pattern includes a "*", the random string replaces the last "*" instead.
// If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir.
// Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory.
// It is the caller's responsibility to remove the directory when it is no longer needed.
func MkdirTemp(dir, pattern string) (string, error) {
    if dir == "" {
        dir = TempDir()
    }

    prefix, suffix, err := prefixAndSuffix(pattern)
    if err != nil {
        return "", &PathError{Op: "mkdirtemp", Path: pattern, Err: err}
    }
    prefix = joinPath(dir, prefix)

    try := 0
    for {
        name := prefix + nextRandom() + suffix
        err := Mkdir(name, 0700)
        if err == nil {
            return name, nil
        }
        if IsExist(err) {
            if try++; try < 10000 {
                continue
            }
            return "", &PathError{Op: "mkdirtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist}
        }
        if IsNotExist(err) {
            if _, err := Stat(dir); IsNotExist(err) {
                return "", err
            }
        }
        return "", err
    }
}

んー。微妙に違う。

Go 1.16 における ioutil.TempFile() 関数の実装は以下の通り

io/ioutil/tempfile.go
// TempFile creates a new temporary file in the directory dir,
// opens the file for reading and writing, and returns the resulting *os.File.
// The filename is generated by taking pattern and adding a random
// string to the end. If pattern includes a "*", the random string
// replaces the last "*".
// If dir is the empty string, TempFile uses the default directory
// for temporary files (see os.TempDir).
// Multiple programs calling TempFile simultaneously
// will not choose the same file. The caller can use f.Name()
// to find the pathname of the file. It is the caller's responsibility
// to remove the file when no longer needed.
func TempFile(dir, pattern string) (f *os.File, err error) {
    if dir == "" {
        dir = os.TempDir()
    }

    prefix, suffix, err := prefixAndSuffix(pattern)
    if err != nil {
        return
    }

    nconflict := 0
    for i := 0; i < 10000; i++ {
        name := filepath.Join(dir, prefix+nextRandom()+suffix)
        f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
        if os.IsExist(err) {
            if nconflict++; nconflict > 10 {
                randmu.Lock()
                rand = reseed()
                randmu.Unlock()
            }
            continue
        }
        break
    }
    return
}

新しい os.CreateTemp() 関数のほうは以下の通り。

os/tempfile.go
// CreateTemp creates a new temporary file in the directory dir,
// opens the file for reading and writing, and returns the resulting file.
// The filename is generated by taking pattern and adding a random string to the end.
// If pattern includes a "*", the random string replaces the last "*".
// If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by TempDir.
// Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file.
// The caller can use the file's Name method to find the pathname of the file.
// It is the caller's responsibility to remove the file when it is no longer needed.
func CreateTemp(dir, pattern string) (*File, error) {
    if dir == "" {
        dir = TempDir()
    }

    prefix, suffix, err := prefixAndSuffix(pattern)
    if err != nil {
        return nil, &PathError{Op: "createtemp", Path: pattern, Err: err}
    }
    prefix = joinPath(dir, prefix)

    try := 0
    for {
        name := prefix + nextRandom() + suffix
        f, err := OpenFile(name, O_RDWR|O_CREATE|O_EXCL, 0600)
        if IsExist(err) {
            if try++; try < 10000 {
                continue
            }
            return nil, &PathError{Op: "createtemp", Path: dir + string(PathSeparator) + prefix + "*" + suffix, Err: ErrExist}
        }
        return f, err
    }
}

これも微妙に違うな。なんか意図があるのかね?

Go 1.16 における ioutil.WriteFile() 関数の実装は以下の通り

io/ioutil/ioutil.go
// WriteFile writes data to a file named by filename.
// If the file does not exist, WriteFile creates it with permissions perm
// (before umask); otherwise WriteFile truncates it before writing, without changing permissions.
//
// As of Go 1.16, this function simply calls os.WriteFile.
func WriteFile(filename string, data []byte, perm fs.FileMode) error {
    return os.WriteFile(filename, data, perm)
}

ちなみにファイルのパーミッションを示す fs.FileMode 型は

io/fs/fs.go
// A FileMode represents a file's mode and permission bits.
// The bits have the same definition on all systems, so that
// information about files can be moved from one system
// to another portably. Not all bits apply to all systems.
// The only required bit is ModeDir for directories.
type FileMode uint32

// The defined file mode bits are the most significant bits of the FileMode.
// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
// The values of these bits should be considered part of the public API and
// may be used in wire protocols or disk representations: they must not be
// changed, although new bits might be added.
const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: non-regular file; nothing else is known about this file

    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

    ModePerm FileMode = 0777 // Unix permission bits
)

と定義されている。まぁ今まで通り。

あっ ioutil.ReadFile() 関数忘れてた。

Go 1.16 における ioutil.ReadFile() 関数の実装は以下の通り

io/ioutil/ioutil.go
// ReadFile reads the file named by filename and returns the contents.
// A successful call returns err == nil, not err == EOF. Because ReadFile
// reads the whole file, it does not treat an EOF from Read as an error
// to be reported.
//
// As of Go 1.16, this function simply calls os.ReadFile.
func ReadFile(filename string) ([]byte, error) {
    return os.ReadFile(filename)
}
このスクラップは14日前にクローズされました
ログインするとコメントできます