Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 1658] ファイルの概要

このコミットは、Go言語の初期のosパッケージにおけるディレクトリ読み取り機能、特にReaddirnamesReaddirの挙動を改善し、コードの移植性を高めるためのものです。Linux環境でのReaddirnamesの動作を修正し、Readdir関数をOS固有の実装から切り離し、よりポータブルな共通コードとして再構築しています。

コミット

commit 00b3d48f13957d60e1d5029ca35bb8069c069e02
Author: Rob Pike <r@golang.org>
Date:   Tue Feb 10 11:55:48 2009 -0800

    Make Readdirnames work properly on Linux.
    Refactor so Readdir is portable code.
    
    R=rsc
    DELTA=192  (50 added, 130 deleted, 12 changed)
    OCL=24770
    CL=24772

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/00b3d48f13957d60e1d5029ca35bb8069c069e02

元コミット内容

    Make Readdirnames work properly on Linux.
    Refactor so Readdir is portable code.

変更の背景

Go言語の初期段階において、osパッケージのディレクトリ読み取り機能は、OSごとに異なるシステムコール(Linuxではgetdents、Darwin/BSDではgetdirentries)を使用しており、その実装がOS固有のファイルに分散していました。特にLinux環境でのReaddirnames(ディレクトリ内のファイル名のみを読み取る関数)の実装には問題があり、正しく動作しないケースがあったと考えられます。

また、Readdir(ディレクトリ内のファイル情報(Dir構造体)を読み取る関数)は、Readdirnamesと同様にOS固有のファイルに実装されており、コードの重複や移植性の問題がありました。このコミットの目的は、これらの問題を解決し、コードベースの保守性と堅牢性を向上させることにありました。具体的には、ReaddirnamesのLinux実装を修正し、ReaddirReaddirnamesを基盤としたポータブルな実装にすることで、OS固有のコードを最小限に抑えることを目指しています。

前提知識の解説

1. osパッケージとファイルシステム操作

Go言語の標準ライブラリのosパッケージは、オペレーティングシステムとの基本的なインタラクションを提供します。これには、ファイルやディレクトリの作成、読み取り、書き込み、削除などの操作が含まれます。ReaddirReaddirnamesは、ディレクトリの内容をリストアップするための関数です。

  • Readdirnames(fd *FD, count int) ([]string, *os.Error): 指定されたファイルディスクリプタfdが指すディレクトリから、count個のファイル名(文字列スライス)を読み取ります。countが負の場合、EOFまで全て読み取ります。
  • Readdir(fd *FD, count int) ([]Dir, *os.Error): 指定されたファイルディスクリプタfdが指すディレクトリから、count個のファイル情報(Dir構造体のスライス)を読み取ります。Dir構造体にはファイル名、サイズ、パーミッションなどのメタデータが含まれます。

2. システムコールとディレクトリ読み取り

Unix系OSでは、ディレクトリの内容を読み取るために低レベルのシステムコールが提供されています。

  • getdents (Linux): Linuxカーネルが提供するシステムコールで、ディレクトリの内容をバッファに読み込みます。バッファにはdirent構造体の配列が格納されます。
  • getdirentries (Darwin/BSD): Darwin(macOS)やBSD系OSが提供するシステムコールで、getdentsと同様にディレクトリの内容をバッファに読み込みます。

これらのシステムコールは、ディレクトリ内の各エントリ(ファイルやサブディレクトリ)に関する情報(inode番号、レコード長、ファイルタイプ、ファイル名など)を含むdirent構造体を返します。dirent構造体の具体的な定義はOSによって若干異なりますが、基本的な情報は共通しています。

3. syscallパッケージ

Go言語のsyscallパッケージは、低レベルのOSプリミティブへのアクセスを提供します。これには、ファイルディスクリプタの操作、システムコールの直接呼び出しなどが含まれます。syscall.Direntは、OSのdirent構造体に対応するGoの型です。

4. FD構造体とDirInfo構造体

  • FD: Goのosパッケージ内部で使用されるファイルディスクリプタを表す構造体です。OSが提供する生のファイルディスクリプタ(整数値)をラップし、ファイル名などの追加情報を持つことがあります。
  • DirInfo: このコミットで導入された、ディレクトリ読み取りの状態を管理するための内部構造体です。getdentsなどのシステムコールで読み取ったバッファとその現在の読み取り位置を保持します。

5. ポータビリティとOS固有の実装

Go言語はクロスプラットフォームを強く意識して設計されています。しかし、OSの低レベルな機能にアクセスする際には、OS固有のシステムコールやデータ構造を使用せざるを得ない場合があります。このような場合、Goでは慣習的に_GOOS_GOARCH.goのようなファイル名(例: dir_amd64_linux.go, dir_amd64_darwin.go)を用いて、特定のOSとアーキテクチャに特化した実装を提供します。このコミットでは、Readdirを共通化することで、OS固有のコードをReaddirnamesに集約し、全体的なポータビリティを向上させています。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. Readdirのポータブル化:

    • 以前はsrc/lib/os/dir_amd64_darwin.gosrc/lib/os/dir_amd64_linux.goにそれぞれOS固有のReaddir実装が存在していました。
    • このコミットでは、これらのOS固有のReaddir実装を削除し、src/lib/os/os_file.goに共通のReaddir実装を導入しました。
    • 新しいReaddirは、まずOS固有のReaddirnamesを呼び出してファイル名のリストを取得します。
    • 次に、取得した各ファイル名に対してStat(またはLstat)システムコールを呼び出し、そのファイルのメタデータ(Dir構造体)を取得します。
    • これにより、Readdirのロジック自体はOSに依存せず、ReaddirnamesStatというOS固有のプリミティブに依存する形になりました。これは、コードの重複を排除し、保守性を高める上で重要な変更です。
  2. Linux Readdirnamesのバッファ管理の改善:

    • LinuxのReaddirnames実装(src/lib/os/dir_amd64_linux.go)が大幅にリファクタリングされました。
    • 以前は、syscall.Getdentsの呼び出しごとに新しいバッファを作成し、その場で処理していました。
    • 新しい実装では、FD構造体内にDirInfoという新しいフィールドを導入し、このDirInfoがディレクトリ読み取りの状態(バッファbuf、バッファ内の現在の読み取り位置bufp、バッファに読み込まれたバイト数nbuf)を保持するようにしました。
    • これにより、Readdirnamesが複数回呼び出された場合でも、前回の読み取り状態を維持し、バッファを効率的に再利用できるようになりました。これは、getdentsが一度に全てのディレクトリエントリを返すとは限らず、複数回の呼び出しでディレクトリ全体を読み取る必要がある場合に特に重要です。
    • syscall.Getdentsは、ディレクトリのファイルディスクリプタ、dirent構造体を格納するバッファ、およびバッファのサイズを引数に取ります。返り値は実際に読み込まれたバイト数です。このバイト数とdirent構造体のReclen(レコード長)を使って、バッファ内の次のエントリに移動します。
    • dirent.Ino == 0のチェックは、ファイルシステムによっては削除されたエントリや特殊なエントリを示すためにinode番号が0になる場合があるため、それらをスキップするための一般的な慣習です。
  3. blockSize定数の導入:

    • src/lib/os/dir_amd64_linux.goblockSize = 4096という定数が導入されました。これは、ディレクトリ読み取りバッファの最小サイズとして使用されます。コメントにはTODO(r): use statfsとあり、将来的にはファイルシステムのブロックサイズを動的に取得する改善の余地があることが示唆されています。
  4. Darwin Readdirの削除:

    • src/lib/os/dir_amd64_darwin.goからOS固有のReaddir実装が完全に削除されました。これにより、Darwin環境でも共通のReaddir実装が使用されるようになります。

これらの変更により、Goのosパッケージは、ディレクトリ読み取りのロジックをよりクリーンに分離し、OS固有の複雑さを抽象化することで、コードの可読性、保守性、および移植性を大幅に向上させています。

コアとなるコードの変更箇所

src/lib/os/dir_amd64_darwin.go

  • Readdir関数の実装が完全に削除されました。
--- a/src/lib/os/dir_amd64_darwin.go
+++ b/src/lib/os/dir_amd64_darwin.go
@@ -64,63 +64,3 @@ func Readdirnames(fd *FD, count int) (names []string, err *os.Error) {
 	}\n \treturn names, nil\n }\n-\n-// TODO(r): see comment in dir_amd64_linux.go\n-\n-// Negative count means read until EOF.\n-func Readdir(fd *FD, count int) (dirs []Dir, err *os.Error) {\n-\tdirname := fd.name;\n-\tif dirname == \"\" {\n-\t\tdirname = \".\";\n-\t}\n-\tdirname += \"/\";\n-\t// Getdirentries needs the file offset - it\'s too hard for the kernel to remember\n-\t// a number it already has written down.\n-\tbase, err1 := syscall.Seek(fd.fd, 0, 1);\n-\tif err1 != 0 {\n-\t\treturn nil, os.ErrnoToError(err1)\n-\t}\n-\t// The buffer must be at least a block long.\n-\t// TODO(r): use fstatfs to find fs block size.\n-\tvar buf = make([]byte, blockSize);\n-\tdirs = make([]Dir, 0, 100);\t// TODO: could be smarter about size\n-\tfor {\n-\t\tif count == 0 {\n-\t\t\tbreak\n-\t\t}\n-\t\tret, err2 := syscall.Getdirentries(fd.fd, &buf[0], int64(len(buf)), &base);\n-\t\tif ret < 0 || err2 != 0 {\n-\t\t\treturn dirs, os.ErrnoToError(err2)\n-\t\t}\n-\t\tif ret == 0 {\n-\t\t\tbreak\n-\t\t}\n-\t\tfor w, i := uintptr(0),uintptr(0); i < uintptr(ret); i += w {\n-\t\t\tif count == 0 {\n-\t\t\t\tbreak\n-\t\t\t}\n-\t\t\tdirent := unsafe.Pointer((uintptr(unsafe.Pointer(&buf[0])) + i)).(*syscall.Dirent);\n-\t\t\tw = uintptr(dirent.Reclen);\n-\t\t\tif dirent.Ino == 0 {\n-\t\t\t\tcontinue\n-\t\t\t}\n-\t\t\tcount--;\n-\t\t\tif len(dirs) == cap(dirs) {\n-\t\t\t\tndirs := make([]Dir, len(dirs), 2*len(dirs));\n-\t\t\t\tfor i := 0; i < len(dirs); i++ {\n-\t\t\t\t\tndirs[i] = dirs[i]\n-\t\t\t\t}\n-\t\t\t\tdirs = ndirs;\n-\t\t\t}\n-\t\t\tdirs = dirs[0:len(dirs)+1];\n-\t\t\tfilename := string(dirent.Name[0:dirent.Namlen]);\n-\t\t\tdirp, err := Lstat(dirname + filename);\n-\t\t\tif dirp == nil || err != nil {\n-\t\t\t\tdirs[len(dirs)-1].Name = filename;\t// rest will be zeroed out\n-\t\t\t} else {\n-\t\t\t\tdirs[len(dirs)-1] = *dirp;\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn dirs, nil;\n-}\n```

### `src/lib/os/dir_amd64_linux.go`

*   `blockSize`定数が追加されました。
*   `Readdirnames`関数が大幅にリファクタリングされ、`FD`の`dirinfo`フィールド(`DirInfo`構造体)を使用してバッファ管理を行うようになりました。
*   `Readdir`関数の実装が完全に削除されました。

```diff
--- a/src/lib/os/dir_amd64_linux.go
+++ b/src/lib/os/dir_amd64_linux.go
@@ -10,6 +10,10 @@ import (\n 	\"unsafe\";\n )\n \n+const (\n+\tblockSize = 4096\t// TODO(r): use statfs\n+)\n+\n func clen(n []byte) int {\n \tfor i := 0; i < len(n); i++ {\n \t\tif n[i] == 0 {\n@@ -21,28 +25,38 @@ func clen(n []byte) int {\n \n // Negative count means read until EOF.\n func Readdirnames(fd *FD, count int) (names []string, err *os.Error) {\n-\t// The buffer should be at least a block long.\n-\t// TODO(r): use fstatfs to find fs block size.\n-\tvar buf = make([]syscall.Dirent, 8192/unsafe.Sizeof(*new(syscall.Dirent)));\n-\tnames = make([]string, 0, 100);\t// TODO: could be smarter about size\n-\tfor {\n-\t\tif count == 0 {\n-\t\t\tbreak\n-\t\t}\n-\t\tret, err2 := syscall.Getdents(fd.fd, &buf[0], int64(len(buf) * unsafe.Sizeof(buf[0])));\n-\t\tif ret < 0 || err2 != 0 {\n-\t\t\treturn names, os.ErrnoToError(err2)\n-\t\t}\n-\t\tif ret == 0 {\n-\t\t\tbreak\n-\t\t}\n-\t\tfor w, i := uintptr(0),uintptr(0); i < uintptr(ret); i += w {\n-\t\t\tif count == 0 {\n-\t\t\t\tbreak\n+\t// If this fd has no dirinfo, create one.\n+\tif fd.dirinfo == nil {\n+\t\tfd.dirinfo = new(DirInfo);\n+\t\t// The buffer must be at least a block long.\n+\t\t// TODO(r): use fstatfs to find fs block size.\n+\t\tfd.dirinfo.buf = make([]byte, blockSize);\n+\t}\n+\td := fd.dirinfo;\n+\tsize := count;\n+\tif size < 0 {\n+\t\tsize = 100\n+\t}\n+\tnames = make([]string, 0, size);\t// Empty with room to grow.\n+\tfor count != 0 {\n+\t\t// Refill the buffer if necessary\n+\t\tif d.bufp == d.nbuf {\n+\t\t\tvar errno int64;\n+\t\t\tdbuf := unsafe.Pointer(&d.buf[0]).(*syscall.Dirent);\n+\t\t\td.nbuf, errno = syscall.Getdents(fd.fd, dbuf, int64(len(d.buf)));\n+\t\t\tif d.nbuf < 0 {\n+\t\t\t\treturn names, os.ErrnoToError(errno)\n \t\t\t}\n-\t\t\tdirent := unsafe.Pointer((uintptr(unsafe.Pointer(&buf[0])) + i)).(*syscall.Dirent);\n-\t\t\tw = uintptr(dirent.Reclen);\n-\t\t\tif dirent.Ino == 0 {\n+\t\t\tif d.nbuf == 0 {\n+\t\t\t\tbreak\t// EOF\n+\t\t\t}\n+\t\t\td.bufp = 0;\n+\t\t}\n+\t\t// Drain the buffer\n+\t\tfor count != 0 && d.bufp < d.nbuf {\n+\t\t\tdirent := unsafe.Pointer(&d.buf[d.bufp]).(*syscall.Dirent);\n+\t\t\td.bufp += int64(dirent.Reclen);\n+\t\t\tif dirent.Ino == 0 {\t// File absent in directory.\n \t\t\t\tcontinue\n \t\t\t}\n \t\t\tcount--;\n@@ -59,64 +73,3 @@ func Readdirnames(fd *FD, count int) (names []string, err *os.Error) {\n \t}\n \treturn names, nil;\n }\n-\n-// TODO(r): Readdir duplicates a lot of Readdirnames. The other way would\n-// be to have Readdir (which could then be portable) call Readdirnames and\n-// then do the Stats.  The existing design was chosen to avoid allocating a\n-// throwaway names array, but the issue should be revisited once we have\n-// a better handle on what that overhead is with a strong garbage collector.\n-// Also, it\'s possible given the nature of the Unix kernel that interleaving\n-// reads of the directory with stats (as done here) would work better than\n-// one big read of the directory followed by a long run of Stat calls.\n-\n-// Negative count means read until EOF.\n-func Readdir(fd *FD, count int) (dirs []Dir, err *os.Error) {\n-\tdirname := fd.name;\n-\tif dirname == \"\" {\n-\t\tdirname = \".\";\n-\t}\n-\tdirname += \"/\";\n-\t// The buffer must be at least a block long.\n-\t// TODO(r): use fstatfs to find fs block size.\n-\tvar buf = make([]syscall.Dirent, 8192/unsafe.Sizeof(*new(syscall.Dirent)));\n-\tdirs = make([]Dir, 0, 100);\t// TODO: could be smarter about size\n-\tfor {\n-\t\tif count == 0 {\n-\t\t\tbreak\n-\t\t}\n-\t\tret, err2 := syscall.Getdents(fd.fd, &buf[0], int64(len(buf) * unsafe.Sizeof(buf[0])));\n-\t\tif ret < 0 || err2 != 0 {\n-\t\t\treturn dirs, os.ErrnoToError(err2)\n-\t\t}\n-\t\tif ret == 0 {\n-\t\t\tbreak\n-\t\t}\n-\t\tfor w, i := uintptr(0),uintptr(0); i < uintptr(ret); i += w {\n-\t\t\tif count == 0 {\n-\t\t\t\tbreak\n-\t\t\t}\n-\t\t\tdirent := unsafe.Pointer((uintptr(unsafe.Pointer(&buf[0])) + i)).(*syscall.Dirent);\n-\t\t\tw = uintptr(dirent.Reclen);\n-\t\t\tif dirent.Ino == 0 {\n-\t\t\t\tcontinue\n-\t\t\t}\n-\t\t\tcount--;\n-\t\t\tif len(dirs) == cap(dirs) {\n-\t\t\t\tndirs := make([]Dir, len(dirs), 2*len(dirs));\n-\t\t\t\tfor i := 0; i < len(dirs); i++ {\n-\t\t\t\t\tndirs[i] = dirs[i]\n-\t\t\t\t}\n-\t\t\t\tdirs = ndirs;\n-\t\t\t}\n-\t\t\tdirs = dirs[0:len(dirs)+1];\n-\t\t\tfilename := string(dirent.Name[0:clen(dirent.Name)]);\n-\t\t\tdirp, err := Stat(dirname + filename);\n-\t\t\tif dirp ==  nil || err != nil {\n-\t\t\t\tdirs[len(dirs)-1].Name = filename;\t// rest will be zeroed out\n-\t\t\t} else {\n-\t\t\t\tdirs[len(dirs)-1] = *dirp;\n-\t\t\t}\n-\t\t}\n-\t}\n-\treturn dirs, nil;\n-}\n```

### `src/lib/os/os_file.go`

*   `Readdirnames`の宣言(OS固有の実装へのフォワード宣言)が追加されました。
*   ポータブルな`Readdir`関数が新しく追加されました。

```diff
--- a/src/lib/os/os_file.go
+++ b/src/lib/os/os_file.go
@@ -160,3 +160,30 @@ func Lstat(name string) (dir *Dir, err *Error) {\n \t}\n \treturn dirFromStat(name, new(Dir), stat), nil\n }\n+\n+// Non-portable function defined in operating-system-dependent file.\n+func Readdirnames(fd *FD, count int) (names []string, err *os.Error)\n+\n+// Negative count means read until EOF.\n+func Readdir(fd *FD, count int) (dirs []Dir, err *os.Error) {\n+\tdirname := fd.name;\n+\tif dirname == \"\" {\n+\t\tdirname = \".\";\n+\t}\n+\tdirname += \"/\";\n+\tnames, err1 := Readdirnames(fd, count);\n+\tif err1 != nil {\n+\t\treturn nil, err1\n+\t}\n+\tdirs = make([]Dir, len(names));\n+\tfor i, filename := range names {\n+\t\tdirp, err := Stat(dirname + filename);\n+\t\tif dirp ==  nil || err != nil {\n+\t\t\tdirs[i].Name = filename\t// rest is already zeroed out\n+\t\t} else {\n+\t\t\tdirs[i] = *dirp\n+\t\t}\n+\t}\n+\treturn\n+}\n+\n```

## コアとなるコードの解説

### `src/lib/os/dir_amd64_linux.go` の `Readdirnames`

この変更の核心は、Linuxにおける`Readdirnames`の堅牢性の向上です。

```go
func Readdirnames(fd *FD, count int) (names []string, err *os.Error) {
	// If this fd has no dirinfo, create one.
	if fd.dirinfo == nil {
		fd.dirinfo = new(DirInfo);
		// The buffer must be at least a block long.
		// TODO(r): use fstatfs to find fs block size.
		fd.dirinfo.buf = make([]byte, blockSize);
	}
	d := fd.dirinfo;
	size := count;
	if size < 0 {
		size = 100
	}
	names = make([]string, 0, size);	// Empty with room to grow.
	for count != 0 {
		// Refill the buffer if necessary
		if d.bufp == d.nbuf {
			var errno int64;
			dbuf := unsafe.Pointer(&d.buf[0]).(*syscall.Dirent);
			d.nbuf, errno = syscall.Getdents(fd.fd, dbuf, int64(len(d.buf)));
			if d.nbuf < 0 {
				return names, os.ErrnoToError(errno)
			}
			if d.nbuf == 0 {
				break	// EOF
			}
			d.bufp = 0;
		}
		// Drain the buffer
		for count != 0 && d.bufp < d.nbuf {
			dirent := unsafe.Pointer(&d.buf[d.bufp]).(*syscall.Dirent);
			d.bufp += int64(dirent.Reclen);
			if dirent.Ino == 0 {	// File absent in directory.
				continue
			}
			count--;
			names = append(names, string(dirent.Name[0:clen(dirent.Name)]));
		}
	}
	return names, nil;
}
  • fd.dirinfoの導入: FD構造体にdirinfoフィールド(*DirInfo型)が追加され、ディレクトリ読み取りの状態(バッファ、読み取り位置など)をFDインスタンスに関連付けて保持するようになりました。これにより、Readdirnamesが複数回呼び出されても、前回の読み取りの続きから処理を再開できます。
  • バッファの初期化と再利用: fd.dirinfonilの場合、新しいDirInfoが作成され、blockSize(4096バイト)のバッファが割り当てられます。このバッファは、syscall.Getdentsからのデータを格納するために使用されます。
  • syscall.Getdentsの呼び出し: d.bufp == d.nbuf(バッファが空になった)の場合、syscall.Getdentsを呼び出してバッファを補充します。Getdentsは、ファイルディスクリプタ、バッファのポインタ、バッファサイズを引数に取り、実際に読み込んだバイト数を返します。エラーが発生した場合はos.ErrnoToErrorでGoのエラーに変換されます。nbufが0の場合はEOF(ディレクトリの終端)に達したことを意味します。
  • バッファの処理: バッファが補充されたら、d.bufpd.nbufを使ってバッファ内のdirentエントリを順に処理します。unsafe.Pointerと型アサーションを使って、バイトスライスからsyscall.Dirent構造体へのポインタを取得しています。
  • dirent.Reclen: 各dirent構造体のReclenフィールドは、そのエントリのレコード長(次のエントリまでのバイト数)を示します。これを使ってd.bufpを更新し、バッファ内の次のエントリに移動します。
  • dirent.Ino == 0のスキップ: inode番号が0のエントリは、通常、ファイルシステムによっては無効なエントリや削除されたエントリを示すため、スキップされます。
  • ファイル名の抽出: dirent.Nameからファイル名を抽出し、namesスライスに追加します。clen関数はCスタイルのヌル終端文字列の長さを計算するために使用されます。

src/lib/os/os_file.goReaddir

この変更により、ReaddirはOS非依存の共通関数となりました。

func Readdir(fd *FD, count int) (dirs []Dir, err *os.Error) {
	dirname := fd.name;
	if dirname == "" {
		dirname = ".";
	}
	dirname += "/";
	names, err1 := Readdirnames(fd, count);
	if err1 != nil {
		return nil, err1
	}
	dirs = make([]Dir, len(names));
	for i, filename := range names {
		dirp, err := Stat(dirname + filename);
		if dirp ==  nil || err != nil {
			dirs[i].Name = filename	// rest is already zeroed out
		} else {
			dirs[i] = *dirp
		}
	}
	return
}
  • Readdirnamesの呼び出し: まず、OS固有のReaddirnames関数を呼び出して、ディレクトリ内のファイル名のみのリストを取得します。
  • Statによるメタデータの取得: 取得した各filenameに対して、Stat関数(またはLstat)を呼び出します。Statは、指定されたパスのファイルに関するメタデータ(サイズ、パーミッション、更新時刻など)を含むDir構造体を返します。
  • Dirスライスの構築: Statから返されたDir構造体を、結果として返すdirsスライスに格納します。Statがエラーを返した場合(例: ファイルが読み取り中に削除された場合など)、ファイル名のみをDir構造体に設定し、他のフィールドはゼロ値のままにします。
  • ポータビリティの実現: この実装により、ReaddirのロジックはどのOSでも共通となり、OS固有の詳細はReaddirnamesStatという下位レベルの関数にカプセル化されました。これにより、コードの重複が解消され、将来的なメンテナンスが容易になります。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • Go言語の初期のコミット履歴
  • Unix/Linuxシステムプログラミングに関する一般的な知識
  • Go言語のunsafeパッケージに関するドキュメント (ポインタ操作の理解のため): https://pkg.go.dev/unsafe