😀

[Go] strings: illegal use of non-zero Builder copied by value

2024/12/21に公開

事の発端

こんな雰囲気のコードを書いていました。
※記事用に簡略化しています

package main

import (
	"fmt"
	"strings"
)

func main() {
	sb := strings.Builder{}
	sb.Grow(30)

	returnedStr := BuildString(sb)
	fmt.Println(returnedStr)
}


func BuildString(sb strings.Builder) string {
	strSlice := []string{"a", "s", "d", "f", "g", "h"}
	for _, s := range strSlice{
		sb.WriteString(s)
	}
	return sb.String()
}

するとこんなエラーが起きます。

$ go run main.go
panic: strings: illegal use of non-zero Builder copied by value
goroutine 1 [running]:
strings.(*Builder).copyCheck(...)
        C:/Program Files/Go/src/strings/builder.go:35
strings.(*Builder).WriteString(...)
        C:/Program Files/Go/src/strings/builder.go:107
main.BuildString(...)
        C:/Users/**/**/**/**/currentDir/main.go:20
main.main()
        C:/Users/**/**/**/**/currentDir/main.go:12 +0x2d0
exit status 2

解決策

これはstrings.Builderを別関数の引数にして別関数で使おうとしたことにより、
BuildString関数の中のWriteStringメソッドの中で定義されているcopyCheck関数でpanicが起きています。

WriteStringメソッド

func (b *Builder) WriteString(s string) (int, error) {
	b.copyCheck()
	b.buf = append(b.buf, s...)
	return len(s), nil
}

その中のcopyCheck関数

func (b *Builder) copyCheck() {
	if b.addr == nil {
		b.addr = (*Builder)(abi.NoEscape(unsafe.Pointer(b)))
	} else if b.addr != b {
		panic("strings: illegal use of non-zero Builder copied by value")
	}
}

この関数のelse if部分に引っかかったのでしょう。
illegal use of non-zero Builder copied by valueと書かれており
翻訳すると「値によりコピーされたゼロでないBuilderの不正使用」とあります。

なので別関数の引数にして別関数で使おうとしたためBuilderの値のみコピーされて別アドレスだったため起きたパニックということでしょうか。

つまりstrings.Builderは定義した関数内で使わなくてはならないのかも。

下記のようにしたらエラーが消えました。

package main

import (
	"fmt"
	"strings"
)

func main() {
	sb := strings.Builder{}
	sb.Grow(30)
	strSlice := []string{"a", "s", "d", "f", "g", "h"}

	for _, s := range strSlice{
		sb.WriteString(s)
	}

	str := sb.String()

	fmt.Println(str)
}
$ go run main.go
asdfgh

Discussion