How to Replace Manual Search Loops with slices.Contains in Go
You can replace verbose for-loops with the generic slices.Contains function (available in the standard library since Go 1.21) to check if a slice contains a specific value with a single, readable function call.
Manual search loops are one of the most common boilerplate patterns in Go codebases. In the JetBrains/go-modern-guidelines repository, you can find several examples where developers iterate over slices to check for membership or locate specific elements. Learning to replace manual search loops with slices.Contains eliminates redundant code and makes your intent explicit.
The Problem with Manual Search Loops
Before Go 1.21, checking whether a slice contains a specific value required writing an explicit for loop with a temporary boolean flag:
found := false
for _, v := range items {
if v == target {
found = true
break
}
}
This pattern is verbose, error-prone, and obscures the actual intent of the code. You must initialize a variable, handle the break statement correctly, and maintain the loop logic. When searching for elements in internal/guidelines/guidelines.go or validating uniqueness in internal/guidelines/schema.go, this boilerplate repeats across the codebase, increasing maintenance burden.
Using slices.Contains from the Go Standard Library
Since Go 1.21, the slices package in the standard library provides a generic Contains function that collapses the search pattern into a single call. For projects on earlier Go versions, the same function is available in golang.org/x/exp/slices.
Checking for Membership
Replace the manual loop with a direct call to slices.Contains:
import "slices" // Go 1.21+ (use "golang.org/x/exp/slices" for older releases)
// Example: check if a slice of strings contains a specific name.
func hasName(names []string, target string) bool {
return slices.Contains(names, target)
}
This approach eliminates the temporary found variable and the break statement. In internal/goversion/goversion.go, where the code searches for a Go version string in a list, switching to slices.Contains would make the presence check immediate and self-documenting.
Locating the Index
If you need the position of the element rather than just a boolean, use slices.Index:
import "slices"
func indexOf(names []string, target string) int {
return slices.Index(names, target) // returns -1 if not found
}
This is useful in internal/guidelines/guidelines.go when iterating over slices to locate a matching guideline ID, as the index can be used to retrieve the full object or determine insertion points.
Refactoring Real-World Code
The JetBrains/go-modern-guidelines repository contains several files that would benefit from this refactoring:
internal/guidelines/guidelines.go: Lines that iterate over slices to locate a matching guideline ID can replace equality checks withslices.Containsfor cleaner ID lookup.internal/guidelines/schema.go: Validation loops that verify uniqueness of IDs can useslices.Containsto detect duplicates more concisely instead of nested iteration.internal/goversion/goversion.go: Loops that search for a Go version string in a list can be simplified usingslices.Containsfor the presence check.
When to Keep Manual Loops
Despite its convenience, slices.Contains is not appropriate for every scenario. Retain manual loops when:
- You need side effects: Counting occurrences, logging matches, or performing work on each element requires iterating the full slice.
- The condition is complex:
slices.Containsonly checks equality with the==operator. For custom predicates, use a manual loop orslices.IndexFunc. - Early termination depends on external state: If you must stop the search based on conditions other than simple equality, keep the explicit loop structure.
Summary
- Import
"slices"for Go 1.21+ (or"golang.org/x/exp/slices"for earlier versions) to access generic slice utilities. - Replace boolean flag and
breakpatterns withslices.Contains(slice, target)for membership tests. - Use
slices.Indexwhen you need the position of the element rather than just existence. - Keep manual loops for complex search logic, side effects, or when working with custom comparison functions.
Frequently Asked Questions
Is slices.Contains available in all Go versions?
slices.Contains was added to the standard library in Go 1.21. If you are maintaining code for earlier Go versions, you can import golang.org/x/exp/slices to access the same generic function with identical behavior.
What if I need to find the position instead of just checking existence?
Use slices.Index(slice, target), which returns the index of the first matching element or -1 if not found. This avoids the manual loop while still providing the location data you need.
Can I use slices.Contains with custom comparison logic?
No, slices.Contains uses the == operator for comparison. If you need to search based on a custom predicate, such as case-insensitive string matching or struct field comparison, you must write a manual loop or use slices.IndexFunc with a custom function.
Does slices.Contains perform better than a manual for-loop?
The slices.Contains helper is optimized and may be inlined by the compiler, eliminating function call overhead. While the Big-O complexity remains O(n), the implementation avoids the accumulator variables and branching overhead typical of hand-written loops, often resulting in identical or better performance with significantly cleaner source code.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →