Ответ
Тип-сумма (sum type), также известный как "помеченное объединение" (tagged union) или "вариантный тип" (variant), — это тип данных, который в любой момент времени может хранить значение одного из нескольких заранее определенных, различных типов.
Классические примеры из других языков — enum
в Rust или алгебраические типы данных в Haskell. Например, тип Result
может быть либо Ok(value)
, либо Err(error)
.
В Go нет встроенной поддержки типов-сумм, но их можно эффективно эмулировать с помощью "запечатанных" интерфейсов (sealed interfaces).
Идея состоит в том, чтобы определить интерфейс и набор типов, которые его реализуют, в одном пакете. При этом сам интерфейс содержит неэкспортируемый метод-маркер. Это не позволяет типам из других пакетов реализовывать этот интерфейс, делая набор вариантов исчерпывающим в рамках вашего пакета.
Пример эмуляции:
package shapes
import "math"
// Shape - это наш тип-сумма.
// Он может быть либо Circle, либо Rectangle.
type Shape interface {
// isShape() - неэкспортируемый метод-маркер.
// Он "запечатывает" интерфейс, не позволяя типам
// из других пакетов его реализовывать.
isShape()
}
// Circle - один из возможных вариантов для Shape
type Circle struct {
Radius float64
}
func (c Circle) isShape() {}
// Rectangle - второй возможный вариант для Shape
type Rectangle struct {
Width, Height float64
}
func (r Rectangle) isShape() {}
// Для работы с типом-суммой используется type switch,
// который позволяет безопасно проверить конкретный тип.
func Area(s Shape) float64 {
switch shape := s.(type) {
case Circle:
return math.Pi * shape.Radius * shape.Radius
case Rectangle:
return shape.Width * shape.Height
default:
// Этот случай теоретически недостижим, если мы контролируем
// все реализации интерфейса Shape.
panic("unknown shape")
}
}
Ключевые моменты:
- Интерфейс-контейнер:
Shape
определяет общий тип. - Метод-маркер: Неэкспортируемый метод
isShape()
гарантирует, что только типы из текущего пакета (Circle
,Rectangle
) могут удовлетворять интерфейсу. - Исчерпывающая проверка:
type switch
позволяет компилятору (через статические анализаторы, напримерgo vet
) проверить, что все возможные варианты типа обработаны.
Современные альтернативы (Go 1.18+):
Хотя дженерики не создают полноценных типов-сумм, они позволяют реализовать похожие по духу контейнеры, такие как Result[T, E]
или Optional[T]
, которые решают проблему представления одного из двух состояний (например, значение или ошибка).