Как правильно сравнить два среза (slice) в Go на равенство?
Почему срезы нельзя сравнивать через ==?
В Go срез — это дескриптор, который содержит три поля: указатель на базовый массив (ptr), длину (len) и ёмкость (cap). Оператор == для срезов не определён, потому что сравнение указателей не гарантирует равенства содержимого: два среза могут ссылаться на разные массивы, но содержать одинаковые элементы. Единственное допустимое сравнение — это проверка на nil.
Правильный способ: reflect.DeepEqual
Самый простой способ сравнить два среза — использовать функцию reflect.DeepEqual из стандартной библиотеки. Она рекурсивно сравнивает любые типы данных, включая срезы, карты, структуры и т.д.
package main
import (
"fmt"
"reflect"
)
func main() {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
c := []int{1, 2, 4}
fmt.Println(reflect.DeepEqual(a, b)) // true
fmt.Println(reflect.DeepEqual(a, c)) // false
}
Плюсы:
- Работает для любых типов (int, string, struct и т.д.)
- Рекурсивно сравнивает вложенные структуры
- Встроен в стандартную библиотеку
Минусы:
- Использует рефлексию, что медленнее ручного сравнения
- Не подходит для критичных к производительности участков кода
Ручное сравнение (для производительности)
Если вам нужно часто сравнивать срезы в высоконагруженном коде, лучше написать свою функцию:
func intSliceEqual(a, b []int) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func main() {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(intSliceEqual(a, b)) // true
}
Для разных типов вам придётся писать отдельные функции, но с помощью дженериков (Go 1.18+) можно сделать универсальную версию:
func sliceEqual[T comparable](a, b []T) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func main() {
fmt.Println(sliceEqual([]int{1,2,3}, []int{1,2,3})) // true
fmt.Println(sliceEqual([]string{"a","b"}, []string{"a","b"})) // true
}
Важные нюансы
1. Порядок элементов важен — срезы [1,2] и [2,1] считаются разными.
2. Срезы с разными длинами — всегда неравны.
3. nil и пустой срез — reflect.DeepEqual считает их разными (nil != []int{}). Если вам нужно считать их равными, добавьте дополнительную проверку:
func slicesEqual(a, b []int) bool {
if len(a) == 0 && len(b) == 0 {
return true
}
return reflect.DeepEqual(a, b)
}
В большинстве случаев reflect.DeepEqual — это лучшее решение для быстрого прототипирования и тестов. Для production-кода, где важна скорость, используйте ручное сравнение или дженерики.
Альтернативный подход: пакет slices (Go 1.21+)
Начиная с Go 1.21 в стандартной библиотеке появился пакет slices, который содержит удобную функцию Equal для сравнения срезов:
import "slices"
func main() {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
fmt.Println(slices.Equal(a, b)) // true
}
Особенности:
- Работает только для срезов с элементами, поддерживающими оператор
==(comparable типы). - Быстрее, чем
reflect.DeepEqual, так как не использует рефлексию. - Корректно обрабатывает
nilи пустые срезы (считает их равными).
Если у вас Go 1.21+ — это самый простой и производительный способ сравнить срезы. Если версия старше — используйте reflect.DeepEqual или пишите свою функцию.
Также в пакете slices есть функция EqualFunc для кастомной логики сравнения:
slices.EqualFunc(a, b, func(x, y int) bool {
return x*2 == y // нестандартное сравнение
})
Это особенно полезно при работе со структурами, где нужно сравнивать не все поля.
Ответов пока нет.
Войдите чтобы оставить ответ