The slicescontains modernizer rewrites a for/range loop with an equality check into slices.ContainsFunc (or slices.Contains), but this changes how many times the needle expression is evaluated. In the original loop, the needle is evaluated once per iteration (until break). With slices.ContainsFunc, the needle is hoisted and evaluated once before iteration.
Before (go fix):
package main
import "fmt"
func main() {
n := 0
f := func() int { n++; return 3 }
s := []int{1, 2, 3}
found := false
for _, elem := range s {
if elem == f() {
found = true
break
}
}
_ = found
fmt.Println(n)
}
Output: 3 (f() called once per iteration: elem=1 vs 1, elem=2 vs 2, elem=3 vs 3 — match, break)
After (GOTOOLCHAIN=go1.26.0 go fix):
package main
import (
"fmt"
"slices"
)
func main() {
n := 0
f := func() int { n++; return 3 }
s := []int{1, 2, 3}
found := slices.Contains(s, f())
_ = found
fmt.Println(n)
}
Output: 1 (f() called once before iteration, needle=3 used for all comparisons)
The modernizer should not apply the transformation when the needle expression has side effects.
CC @adonovan
The
slicescontainsmodernizer rewrites afor/rangeloop with an equality check intoslices.ContainsFunc(orslices.Contains), but this changes how many times the needle expression is evaluated. In the original loop, the needle is evaluated once per iteration (until break). Withslices.ContainsFunc, the needle is hoisted and evaluated once before iteration.Before (
go fix):Output:
3(f() called once per iteration: elem=1 vs 1, elem=2 vs 2, elem=3 vs 3 — match, break)After (
GOTOOLCHAIN=go1.26.0 go fix):Output:
1(f() called once before iteration, needle=3 used for all comparisons)The modernizer should not apply the transformation when the needle expression has side effects.
CC @adonovan