Посмотрим, как применять типовые параметры к структурам. На практике это позволяет создавать переиспользуемые контейнеры и паттерны, знакомые по Rust и другим языкам.
➡️ Дженерик-стек
Классический пример. Стек, который работает с любым типом данных:
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) {
s.items = append(s.items, item)
}
func (s *Stack[T]) Pop() (T, bool) {
if len(s.items) == 0 {
var zero T
return zero, false
}
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}Обратите внимание на паттерн
var zero T. В Go нельзя вернуть nil для произвольного типа, поэтому создаётся нулевое значение через объявление переменной.Использование выглядит так:
intStack := &Stack[int]{}
intStack.Push(1)
intStack.Push(2)
val, _ := intStack.Pop() // 2Тип указывается явно при создании, дальше компилятор всё отслеживает сам.
Pair
Ещё один частый паттерн. Пара из двух значений разных типов:
type Pair[T, U any] struct {
First T
Second U
}
func NewPair[T, U any](first T, second U) Pair[T, U] {
return Pair[T, U]{First: first, Second: second}
}Удобно, когда нужно вернуть из функции два связанных значения, не создавая под это отдельную структуру.
➡️ Result
Паттерн из Rust. Вместо пары
(value, error) используется единый тип, который содержит либо значение, либо ошибку:type Result[T any] struct {
value T
err error
}
func Ok[T any](value T) Result[T] {
return Result[T]{value: value}
}
func Err[T any](err error) Result[T] {
return Result[T]{err: err}
}
func (r Result[T]) Unwrap() T {
if r.err != nil {
panic(r.err)
}
return r.value
}
func (r Result[T]) UnwrapOr(defaultValue T) T {
if r.err != nil {
return defaultValue
}
return r.value
}На практике это может выглядеть так:
func divide(a, b float64) Result[float64] {
if b == 0 {
return Err[float64](errors.New("division by zero"))
}
return Ok(a / b)
}
result := divide(10, 2)
fmt.Println(result.Unwrap()) // 5Стоит ли использовать
Result вместо обычного (T, error)? Для большинства Go проектов привычные множественные возвраты по-прежнему предпочтительнее. Но
Result может быть полезен, если вы строите библиотеку с цепочками вызовов или хотите добавить метод UnwrapOr для значений по умолчанию.➡️ Optional
Похожий паттерн, но для случая «значение есть или его нет».
В Go это обычно решается через указатели, но
Optional делает намерение явным:type Optional[T any] struct {
value *T
}
func Some[T any](value T) Optional[T] {
return Optional[T]{value: &value}
}
func None[T any]() Optional[T] {
return Optional[T]{value: nil}
}
func (o Optional[T]) UnwrapOr(defaultValue T) T {
if o.value == nil {
return defaultValue
}
return *o.value
}Как и с
Result, это не замена идиоматичному Go, а дополнительный инструмент для случаев, когда нужна явная семантика «пусто/не пусто».Дженерик-типы позволяют создавать универсальные контейнеры и паттерны без потери типобезопасности.
Стек, пара,
Result, Optional — всё это переиспользуемые строительные блоки, которые раньше приходилось реализовывать через interface{}.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoDeep