Библиотека Go-разработчика | Golang
23.5K subscribers
2.33K photos
47 videos
87 files
4.74K links
Все самое полезное для Go-разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/32d20779

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4a8c2468
Download Telegram
💡Запускаем Go-бинари прямо из репозитория

Про возможность установки бинаря из репозитория через go install знают все. Менее известным фактом является то, что мы можем запускать двоичные файлы прямо из репозитория, например, так:

$ go run github.com/cosmtrek/air@latest

🤩Никакой установки, никакого клонирования, никакого Makefile, и ваш бинарь всегда в актуальном состоянии.

💬В таком случае нужно всегда быть онлайн?

☑️go run подключается к go proxy при каждом вызове для проверки на наличие более новой версии.

☑️Но мы можем обойти это поведение, заменив @latest фиксированным номером версии (например, @v1.45.0). Тогда команда будет нормально работать в автономном режиме.

#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍35😁2
💡 Как можно тривиально реализовать типы sum/option на чистом Go, без использования сторонних пакетов или других хаков?

◆ Например, вы захотели, чтобы функция возвращала либо data value, либо error. Вот как это может выглядеть псевдокодом:

func f() Option {
result, err := DoSomething()
if err != nil {
return Error("oops:", err)
}
return Data(result)
}


◆ Тип Option объединяет два варианта: Data и Error. Если все идет хорошо, функция возвращает Data, в противном случае — Error.

📌 Возможно ли это в Go?

◆ Да, но для этого нужно немного изменить концепцию интерфейсов. Интерфейсы описывают общее поведение, перечисляя одну или несколько функций. Каждый тип, который реализует эти функции, является экземпляром этого интерфейса.

◆ Однако тип sum не обязательно имеет какие-либо функции для реализации. Ему нужно только представлять разные виды значений. Поэтому нам придется использовать пустой интерфейс. Но это тоже не сработает: любой тип удовлетворяет пустому интерфейсу.

◆ Взгляните на этот интерфейс:

type Option interface {
isOption()
}


◆ Функция isOption() служит только для того, чтобы сделать этот интерфейс отличным от пустого интерфейса. Только типы, реализующие isOption, являются вариантами Option.

◆ И обратите внимание, что isOption() не экспортируется. Это предотвращает добавление вариантов к типу Option сторонним кодом. Другими словами, эта функция «‎запечатывает» интерфейс Option.

◆ Два варианта, Data и Error, следовательно, реализуются следующим образом:

type Data[T any] struct {
Value T
}
func (Data[T]) isOption() {}

type Error struct {
Err error
}
func (Error) isOption() {}


◆ Вот и все! Наш тип Option готов к использованию. Вот функция, которая возвращает тип Option вместо известной пары (value, error):

func DoSomething(b bool) Option {
if b {
return Data[int]{Value: 42}
}
return Error{
Err: fmt.Errorf("oops"),
}
}


◆ Опытные разработчики могут сказать, что возвращать интерфейс считается плохим стилем! И на это есть причина: если вызывающий получает обратно интерфейс, ему придется анализировать возвращаемое значение, чтобы определить конкретный тип за интерфейсом.

◆ Однако этот анализ возвращаемого значения является намеренной частью подхода типа sum в Go. Разные варианты типа sum требуют разных действий, поэтому естественной частью обработки типа sum является ветвление в обработке возвращаемого типа, специфичного для варианта.

◆ Не волнуйтесь, определение варианта и действия на его основе требуют только переключения по типу возвращаемого значения и отдельного case для каждого из возможных вариантов, вот так:

func main() {
opt := DoSomething(true)
switch option := opt.(type) {
case Data[int]:
fmt.Println(option.Value)
case Error:
fmt.Println(option.Err)
}
default:
}
}


◆ Но будьте осторожны: где бы вы ни писали такой переключатель типа sum, вы должны включить все варианты как case.

◆ К счастью, для этого есть линтер go-check-sumtype:

//sumtype:decl
type Option interface {
isOption()
}


#tip
🔥116🥱6👍3😁2
💡Стек или куча?

🤔 Живет ли переменная на стеке вызовов, или она динамически выделена в куче?

В большинстве случаев вам не стоит беспокоиться об этом. Go собирает мусор и автоматически очищает неиспользуемые переменные.

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

📌 Как узнать, выделяется ли переменная в куче?

Некоторые операции по умолчанию вызывают выделение памяти в куче и поэтому легко обнаруживаются и исправляются. Вот несколько примеров:

🔸Строковые переменные неизменяемы. Конкатенация двух строк приводит к новой аллокации и сборке мусора. В качестве альтернативы можно использовать strings.Builder.
🔸Срезы, которые растут за пределы своей емкости, реаллоцируются. Решение: предварительно выделить срез с помощью make().
🔸Когда функция создает локальную переменную и возвращает указатель на эту переменную, переменная должна быть выделена в куче.

📌 Однако есть ситуации, когда выделения в куче неочевидны. Подумайте об указателях, скрытых внутри других типов данных, таких как срезы или мапы. Или рассмотрите массивы. Если массив слишком большой, чтобы жить на стеке, он выделяется в куче.

📌 Как найти эти случаи выделения в куче?

Запустите или скомпилируйте свой код с флагом сборки мусора "-m", и команда Go выведет заметку каждый раз, когда переменная перемещается или уходит со стека в кучу:

go run -gcflags "-m" 
или
go tools compile -m


#tip
🔥61👍92🎉1💯1