дженерики в go с версии Go 1.18

С введением дженериков (с версии Go 1.18) можно писать функции и типы, которые работают с различными типами данных, обеспечивая при этом строгую типизацию и переиспользование кода.

 

 

 

Пример кода

package main

import "fmt"

// Обобщённая функция Swap меняет местами два значения любого типа.
// T — параметр типа, а ограничение any означает, что функция принимает аргументы любого типа.
func Swap[T any](a, b T) (T, T) {
	return b, a
}

// Обобщённый тип Box хранит значение произвольного типа T.
type Box[T any] struct {
	value T
}

// Метод Get возвращает значение, хранящееся в Box.
func (b Box[T]) Get() T {
	return b.value
}

// Метод Set присваивает новое значение полю value.
func (b *Box[T]) Set(value T) {
	b.value = value
}

func main() {
	// Пример использования функции Swap с числами
	a, b := 10, 20
	a, b = Swap(a, b)
	fmt.Println("После обмена:", a, b)

	// Пример использования типа Box для хранения значения int
	intBox := Box[int]{value: 100}
	fmt.Println("Значение в intBox:", intBox.Get())

	// Пример использования типа Box для хранения значения string
	strBox := Box[string]{value: "hello"}
	fmt.Println("Значение в strBox:", strBox.Get())
}


 

Как это работает

  1. Параметры типа и ограничения
    В объявлении функции Swap[T any] параметр типа T записывается в квадратных скобках сразу после имени функции. Ограничение any (это синоним для interface{}) указывает, что тип T может быть любым. Таким образом, функция Swap способна принимать на вход значения любых типов, например, int, string и другие.

  2. Обобщённый тип
    Тип Box[T any] представляет контейнер, способный хранить значение любого типа. Методы Get и Set работают с этим значением, не теряя информации о типе. При создании экземпляра типа Box компилятор подставляет конкретный тип, например, Box[int] или Box[string].

  3. Компиляция и безопасность типов
    При вызове обобщённой функции или создании обобщённого типа компилятор Go подставляет конкретный тип вместо параметра T. Это позволяет избежать дублирования кода и одновременно гарантирует, что типизация будет проверяться на этапе компиляции.

  4. Преимущества дженериков
    Благодаря обобщённым типам и функциям можно писать более универсальный и переиспользуемый код, не прибегая к использованию небезопасных преобразований типов и интерфейсов. Это увеличивает читаемость и поддерживаемость кода, снижая количество ошибок.

 

Компилятор Go создаёт отдельную, специализированную версию функции (или типа) для каждого уникального набора типовых параметров, используемых в коде. Такой подход называется монопорфизацией.

Разъяснения

  • Монопорфизация:
    При компиляции для каждой инстанциации обобщённого кода создаётся конкретный вариант с подставленным типом. Например, если функция Swap[T any] вызывается для int и для string, то компилятор сгенерирует две версии — одну для int, другую для string.

  • Преимущества:
    Такой механизм позволяет:

    • Оптимизировать код под конкретные типы, что может улучшать производительность.

    • Сохранять строгую типизацию и выявлять ошибки на этапе компиляции.

    • Избегать накладных расходов, связанных с преобразованиями типов во время выполнения.

  • Возможные нюансы:
    Хотя для каждой инстанциации создаётся своя версия, компилятор может применять оптимизации (например, инлайнинг или другие методы оптимизации кода), что помогает снизить потенциальное увеличение размера бинарника.

Таким образом, реализация дженериков в Go через монопорфизацию делает возможным написание обобщённого кода без потери производительности и безопасности типов.

PG go

Если входящий массив IDs []int64

то в pg можно автомато развернуть
WHERE id = ANY($1::int[]) 

добавив потом $1

rows, err := gw.db.Query(gw.ctx, query, IDs)

Server Sent Event (SSE) with Go

Source https://github.com/evgensr/sse

package main

import (
 "fmt"
 "net/http"
 "time"
)

func main() {
 http.HandleFunc("/events", eventsHandler)
 http.ListenAndServe(":8080", nil)
}

func eventsHandler(w http.ResponseWriter, r *http.Request) {
 // Set CORS headers to allow all origins. You may want to restrict this to specific origins in a production environment.
 w.Header().Set("Access-Control-Allow-Origin", "*")
 w.Header().Set("Access-Control-Expose-Headers", "Content-Type")

 w.Header().Set("Content-Type", "text/event-stream")
 w.Header().Set("Cache-Control", "no-cache")
 w.Header().Set("Connection", "keep-alive")

 // Simulate sending events (you can replace this with real data)
 for i := 0; i < 10; i++ {
 fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("Event %d", i))
 time.Sleep(2 * time.Second)
 w.(http.Flusher).Flush()
 }

 // Simulate closing the connection
 closeNotify := w.(http.CloseNotifier).CloseNotify()
 <-closeNotify
}

 

<!DOCTYPE html>
<html>
<head>
 <title>SSE Example</title>
</head>
<body>
 <div id="sse-data"></div>

 <script>
 const eventSource = new EventSource('http://localhost:8080/events');
 eventSource.onmessage = function(event) {
 const dataElement = document.getElementById('sse-data');
 dataElement.innerHTML += event.data + '<br>';
 };
 </script>
</body>
</html>

Оптимизация кода golang

Когда мы фильтруем фрагменты в Go, типичным решением является создание нового фрагмента для отфильтрованных элементов (var filtered []int).

Однако этот метод приводит к дополнительному выделению памяти, потому что мы создаём пустой слайс, и при каждом добавлении элемента нужно будет заново аллоцировать память.

Более разумным способом решения этой проблемы является такое создание filtered := numbers[:0]

Так мы создадим слайс с ёмкостью numbers и избегаем дополнительных выделений памяти в дальнейшем

В выражении numbers[:0], 0 указывает на индекс, до которого (не включая его) будут скопированы элементы из среза numbers. По сути, это создает новый срез, который имеет ту же емкость, что и исходный срез numbers, но не содержит никаких элементов.

Вот как это работает:

  • numbers[:] создает копию среза numbers, включая все его элементы.
  • numbers[:n], где n — это неотрицательное целое число, создает новый срез, который содержит первые n элементов numbers.
  • numbers[:0] создает новый срез, который не содержит элементов, но имеет ту же емкость, что и numbers. Это означает, что при добавлении элементов в срез filtered с помощью функции append память будет выделена из исходного массива, который лежит в основе среза numbers, до тех пор, пока не будет превышена его емкость.