💡Запускаем Go-бинари прямо из репозитория
Про возможность установки бинаря из репозитория через
$
💬 В таком случае нужно всегда быть онлайн?
☑️
☑️Но мы можем обойти это поведение, заменив
#tip
Про возможность установки бинаря из репозитория через
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, без использования сторонних пакетов или других хаков?
◆ Например, вы захотели, чтобы функция возвращала либо
◆ Тип
📌 Возможно ли это в Go?
◆ Да, но для этого нужно немного изменить концепцию интерфейсов. Интерфейсы описывают общее поведение, перечисляя одну или несколько функций. Каждый тип, который реализует эти функции, является экземпляром этого интерфейса.
◆ Однако тип
◆ Взгляните на этот интерфейс:
◆ Функция
◆ И обратите внимание, что
◆ Два варианта,
◆ Вот и все! Наш тип
◆ Опытные разработчики могут сказать, что возвращать интерфейс считается плохим стилем! И на это есть причина: если вызывающий получает обратно интерфейс, ему придется анализировать возвращаемое значение, чтобы определить конкретный тип за интерфейсом.
◆ Однако этот анализ возвращаемого значения является намеренной частью подхода типа
◆ Не волнуйтесь, определение варианта и действия на его основе требуют только переключения по типу возвращаемого значения и отдельного
◆ Но будьте осторожны: где бы вы ни писали такой переключатель типа
◆ К счастью, для этого есть линтер go-check-sumtype:
#tip
◆ Например, вы захотели, чтобы функция возвращала либо
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
GitHub
GitHub - alecthomas/go-check-sumtype: A simple utility for running exhaustiveness checks on Go "sum types."
A simple utility for running exhaustiveness checks on Go "sum types." - alecthomas/go-check-sumtype
🔥11❤6🥱6👍3😁2
💡Стек или куча?
🤔 Живет ли переменная на стеке вызовов, или она динамически выделена в куче?
В большинстве случаев вам не стоит беспокоиться об этом. Go собирает мусор и автоматически очищает неиспользуемые переменные.
Однако сборка мусора имеет свою цену, поэтому чем меньше выделений делает ваш код, тем быстрее он может работать.
📌 Как узнать, выделяется ли переменная в куче?
Некоторые операции по умолчанию вызывают выделение памяти в куче и поэтому легко обнаруживаются и исправляются. Вот несколько примеров:
🔸Строковые переменные неизменяемы. Конкатенация двух строк приводит к новой аллокации и сборке мусора. В качестве альтернативы можно использовать
🔸Срезы, которые растут за пределы своей емкости, реаллоцируются. Решение: предварительно выделить срез с помощью
🔸Когда функция создает локальную переменную и возвращает указатель на эту переменную, переменная должна быть выделена в куче.
📌 Однако есть ситуации, когда выделения в куче неочевидны. Подумайте об указателях, скрытых внутри других типов данных, таких как срезы или мапы. Или рассмотрите массивы. Если массив слишком большой, чтобы жить на стеке, он выделяется в куче.
📌 Как найти эти случаи выделения в куче?
Запустите или скомпилируйте свой код с флагом сборки мусора "
#tip
🤔 Живет ли переменная на стеке вызовов, или она динамически выделена в куче?
В большинстве случаев вам не стоит беспокоиться об этом. Go собирает мусор и автоматически очищает неиспользуемые переменные.
Однако сборка мусора имеет свою цену, поэтому чем меньше выделений делает ваш код, тем быстрее он может работать.
📌 Как узнать, выделяется ли переменная в куче?
Некоторые операции по умолчанию вызывают выделение памяти в куче и поэтому легко обнаруживаются и исправляются. Вот несколько примеров:
🔸Строковые переменные неизменяемы. Конкатенация двух строк приводит к новой аллокации и сборке мусора. В качестве альтернативы можно использовать
strings.Builder.🔸Срезы, которые растут за пределы своей емкости, реаллоцируются. Решение: предварительно выделить срез с помощью
make().🔸Когда функция создает локальную переменную и возвращает указатель на эту переменную, переменная должна быть выделена в куче.
📌 Однако есть ситуации, когда выделения в куче неочевидны. Подумайте об указателях, скрытых внутри других типов данных, таких как срезы или мапы. Или рассмотрите массивы. Если массив слишком большой, чтобы жить на стеке, он выделяется в куче.
📌 Как найти эти случаи выделения в куче?
Запустите или скомпилируйте свой код с флагом сборки мусора "
-m", и команда Go выведет заметку каждый раз, когда переменная перемещается или уходит со стека в кучу:go run -gcflags "-m"
или
go tools compile -m
#tip
🔥61👍9⚡2🎉1💯1