В материале мы рассмотрим, как операторы сравнения работают в Go.
В рамках статьи частично будут рассмотрены generics, но подробности мы раскрывать не будем, так как речь о generics пойдет в следующих разделах.
Comparable
Начиная с версии 1.18 в Go появился предопределенный интерфейс comparable, который позволяет описывать сравнимые типы.
// comparable - интерфейс, реализуемый всеми сравнимыми типами (логическими значениями, числами, строками, указателями, каналами, массивами сравнимых типов, структурами, поля которых являются всеми сравнимыми типами).
type comparable interface{ comparable }Стоит отметить, что интерфейс comparable проверяется компилятором через type system и правила сравнимости, поэтому вы не сможете реализовать его самостоятельно для своей структуры.
То есть
comparable≠ интерфейс типа с методами, какfmt.Stringer.Он является ограничителем типа и используется в дженериках.
Нельзя создать свой объект, реализующий методы интерфейса comparable и ожидать, что это сделает структуры, реализующие его, сравнимыми с помощью операторов == и !=.
Ordered
Интерфейс ordered также имеет ряд ограничений. Его можно применять только к типам, которые поддерживают встроенное сравнение: строки и числа.
Также как и comarable,
ordered≠ интерфейс с методами, а является ограничителем типа в дженериках.
Таким образом, нельзя описать и ordered интерфейс, чтобы операторы <, >, <=, >= работали для кастомных типов.
В Go 1.21 Ordered тип переехал в стандартный пакет cmp, а в более ранних версиях использовался пакет golang.org/x/exp/constraints
package cmp
// Ordered — это ограничение (constraint), которое разрешает любой упорядоченный тип: любой тип, который поддерживает операторы <, <=, >=, >.
// Если в будущих версиях Go будут добавлены новые упорядоченные типы,
// это ограничение будет изменено, чтобы включать их.
//
// Обратите внимание, что типы с плавающей запятой могут содержать значения NaN ("не число").
// Операторы, такие как == или <, всегда будут возвращать false при
// сравнении значения NaN с любым другим значением, включая другой NaN.
// См. функцию [Compare] для корректного и согласованного способа сравнения значений NaN.
type Ordered interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
~float32 | ~float64 |
~string
}Что может пойти не так?
Специальные значения float
В типе данных float могут использоваться специальные значения: +Inf, -Inf, NaN, которые ломают логику сравнения.
// не работает
NaN == NaN // false
NaN < x // false
NaN > x // false
// Используйте
cmp.Compare(float64(NaN), float64(NaN)) // 0Сравнение map, slice, array
Так как типы map, slice ссылочными (что такое ссылочный тип мы рассмотрим в следующих разделах), то их нельзя сравнивать, даже если они одинаковые. Проблема заключается в том, что непонятно, что именно мы должны сравнить: элементы или структуры и ссылки этих типов.
При этом массивы, если их содержимое можно сравнивать будут являться сравнимыми.
Map
var mapOne, mapTwo map[int]string
// Map сравнивать нельзя
if mapOne == mapTwo {}
// Но сравнение на nil допустимо
if mapOne == nil {}Slice
var sliceOne, sliceTwo []int{}
// Сравнение слайсов приведет к ошибке на этапе компиляции
if sliceOne == sliceTwo {}
// а сравнение с nil допустимо
if sliceOne == nil {}Array
var arrayOne, arrayTwo [2]int{}
// Массивы будут работать корректно
if arrayOne == arrayTwo {
// будет true, если содержимое массивов идентично
}
// так как массив не является ссылочным типом, то сравнение с nil приведет к ошибке. Так делать нельзя
if arrayOne == nil {}
// Для сравнения с nil нужно иницилизировать переменную как ссылку на массив
var arrayRef *[2]int{}
// теперь так можно
if arrayRef == nil {}Как работают ссылки, что такое ссылочные типы данных мы рассмотрим в следующих разделах
Сравнение структур
Важно понимать, что структуры можно сравнивать, только если все поля внутри структуры можно сравнивать.
type A struct {
X int
Y []byte // slice — несравнимый тип
}
var one, two A
// Сравнение вызовет ошибку:
if one == two {}Но при этом, если изменить код
type A struct {
X int
Y [2]byte // массив можно сравнивать, если его элементы сравнимы
}
var one, two A
// Теперь можно сравнивать:
if one == two {}Сравнение интерфейсов
Что выведет следующий код?
package main
func main() {
// напомним, тип any (interface{}) сравинвать можно, а слайсы - нельзя
var one, two any = []int{}, []int{}
if one == two {
}
}Так как мы не можем сравнивать слайсы, то запуск кода приведет к панике
panic: runtime error: comparing uncomparable type []int
Leave a Reply