標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

既存パッケージ(uncozip) を Go1.22 の rangefunc 対応に

意外と簡単でした(Lua に慣れてる人は、コールバック関数を戻り値として返すというやり方はおなじみかも)

uncozip のパッケージは、ZIPファイルの各ファイルをリストアップする関数として (*CorruptedZip) Scan() bool というメソッドがあります。この実装は、内部の繰り返し関数 (*CorruptedZip) scan() error を次のようにラッピングしているだけの関数です。

// Scan advances the CorruptedZip to the next single file in a ZIP archive.
func (cz *CorruptedZip) Scan() bool {
    err := cz.scan()
    if err == io.EOF {
        cz.err = nil // means no error
    } else {
        cz.err = err
    }
    return err == nil
}

これをちょっと組み変えた関数 Each を用意します。(*CorruptedZip) Scan() は繰り返し要素のデータは元々自分自身のメソッドで提供していたので、Each は自分自身をコールバック関数に渡すという手抜きをしています。

( これ、将来的には、Scan は廃止して、各要素は本体のフィールドから外部に追い出すように最適化したいものですが、とりあえずは簡単対応で )

// Each is the function for rangefunc.
func (cz *CorruptedZip) Each(f func(*CorruptedZip) bool) {
    for {
        err := cz.scan()
        if err != nil {
            if err != io.EOF {
                cz.err = err
            }
            break
        }
        if !f(cz) {
            break
        }
    }
}

呼び出し側を少々書き変えます。

diff --git a/cmd/uncozip/main.go b/cmd/uncozip/main.go
index 40d1c6bd23...4f777afa23 100644
--- a/cmd/uncozip/main.go
+++ b/cmd/uncozip/main.go
@@ -165,13 +165,13 @@
        })
    }
 
-   for cz.Scan() {
+   for entry := range cz.Each {
        var err error
        var checksum uint32
        if *flagTest {
-           checksum, err = testEntry(cz, patterns)
+           checksum, err = testEntry(entry, patterns)
        } else {
-           checksum, err = extractEntry(cz, patterns)
+           checksum, err = extractEntry(entry, patterns)
        }
        if err == errSkipEntry {
            continue
@@ -179,17 +179,17 @@
        if err != nil {
            return err
        }
-       if checksum != cz.CRC32() {
+       if checksum != entry.CRC32() {
            if *flagStrict {
                return fmt.Errorf("%s: CRC32 is expected %X in header, but %X",
-                   cz.Name(), cz.CRC32(), checksum)
+                   entry.Name(), entry.CRC32(), checksum)
            }
            fmt.Fprintf(os.Stderr,
                "NG:   %s: CRC32 is expected %X in header, but %X\n",
-               cz.Name(), cz.CRC32(), checksum)
+               entry.Name(), entry.CRC32(), checksum)
        } else if *flagDebug {
            fmt.Fprintf(os.Stderr, "%s: CRC32: header=%X , body=%X\n",
-               cz.Name(), cz.CRC32(), checksum)
+               entry.Name(), entry.CRC32(), checksum)
        }
    }
    if err := cz.Err(); err != io.EOF {

最後に Makefile を修正します。

--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@
 all:
        go fmt
        $(SET) "CGO_ENABLED=0" && go build $(GOOPT)
-       cd "cmd/uncozip" && go fmt && $(SET) "CGO_ENABLED=0" && go build -o ../../$(NAME)$(EXE)
$(GOOPT)
+       cd "cmd/uncozip" && go fmt && $(SET) "CGO_ENABLED=0" && $(SET) "GOEXPERIMENT=rangefunc"
& go build -o ../../$(NAME)$(EXE) $(GOOPT)

 _dist:
        $(MAKE) all

ね、簡単でしょ? (そりゃ、手抜きだし…)

この対応では、実行ファイル作成部分では GOEXPERIMENT=rangefunc を指定していますが、パッケージ部分では rangefunc が要求する型の関数を用意しているだけで、言語の拡張部分は使っていません。それゆえ、Go 1.22以前のコードからでも従来どおり普通に import して使えます。