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

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

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

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

РКН: https://gosuslugi.ru/snet/67a4a8c2468
Download Telegram
errors.Is и errors.As на практике: совет по обработке ошибок от Matt Boyle для Go-разработчика.

#tip
👍18
💡Запускаем 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
💡Перепроверьте ваши embedding структуры

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

📌 Предположим, у вас есть структура:

type Person struct {
Name string
Address string
SSN string
}


и вы хотите создать тип customer.

type Customer struct {
Person
ID int
}


Person является embedded структурой. Все ее поля и методы встраиваются во внешнюю структуру.

◆ Приложению, которому нужен тип Customer, не интересен номер социального страхования клиента и не должен быть, но SSN неизбежно встраивается в Customer вместе с полями Name и Address.

◆ Любой код, имеющий доступ к переменной Customer, таким образом, имеет доступ и к SSN клиента.

func main() {
p := Person{
Name: "John Doe",
Address: "Doe Blvd, Doe City"
SSN: "123-45-6789",
}
c := Customer{
Person: p,
}
fmt.Println(c.SSN)
}


◆ Будьте осторожны с встраиванием структур. Это очень полезная техника, но вы можете случайно открыть доступ к полям или функциям, к которым клиентский код не должен иметь доступа.

#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱32👍20🤔6🔥21
🤔 Context vs структура: следует ли передавать информацию, специфичную для запроса, через context.Context?

HTTP-обработчики могут использовать контекст для управления тайм-аутами запросов или отменой в рамках запроса. Тип Context также позволяет передавать значения (например, идентификатор пользователя, связанный с запросом) другим функциям.

Следует ли использовать этот механизм для передачи информации по цепочке вызовов запроса? Или лучше использовать обычную структуру?

Вот две причины, по которым использование Context для передачи значений может быть плохой идеей:

Значения в Context представляют собой пары ключ/значение, где ключ и значение являются пустыми интерфейсами (т. е. any). Другими словами, значения в контексте — это как мешок со всем подряд. Компилятор не может помочь вам отловить ошибки типов или даже проверить, есть ли информация. Удачи в устранении неполадок 😉

Если вы видите функцию, принимающую параметр Context, вы не можете сказать, что внутри. Если вы видите функцию, которая принимает параметр структуры, вы можете ясно видеть, что данные передаются, и что это за данные.

Но разве doSomething(ctx) не выглядит намного чище, чем doSomething(ctx, someStruct)?

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

Так context.WithValue() следует избегать?

Значения в контексте могут быть полезны, если они не критичны для бизнес-логики приложения. Например, совершенно нормально передавать идентификаторы запросов для логирования или измерения метрик. Читатель все равно сможет понять логику кода, и если что-то пойдет не так с этим идентификатором, это повлияет только на логирование или метрики приложения, но не на результат запроса.

💡Если данные важны для вашей бизнес-логики, не помещайте их в Context.

#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍17💯5🥱31
🤔 Где искать библиотеки и инструменты для своих проектов?

🧰 Подборка для Go-разработчика:

🔗 pkg.go.dev

Здесь вы можете найдете пакеты по различным критериям и получите информацию о версии, лицензии и многом другом.

🔗 GitHub

Вводите в поисковую строку language:go + свой запрос и анализируете результаты.

🔗 Awesome Go

Один из самых старых и самых поддерживаемых кураторских списков по Go.

🔗 Libhunt.com

Коллекция Go-проектов, отфильтрованных по количеству упоминаний, звезд или другим критериям.

🔗 go-recipes

Тщательно подобранный список полезных Go-инструментов, разделенных на категориям.

#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍123
🤔 Вы когда-нибудь путались в «первом» и «последнем» индексах при операциях вроде этой:

s := a[3:7]


Она создает срез из массива a, начиная с третьего элемента (с нулевым индексом) и до, но не включая, элемента седьмого элемента.

Таким образом, «последний» индекс кажется указывающим за пределы a.

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

| G | o | p | h | e | r | s |   |
^ ^ ^ ^ ^ ^ ^ ^
0 1 2 3 4 5 6 7?

...представьте индексы, как будто они находятся между элементами.

| G | o | p | h | e | r | s |
^ ^ ^ ^ ^ ^ ^ ^
0 1 2 3 4 5 6 7

Тогда срез a[3:7] вполне естественно относится к элементам между индексом №3 и индексом №7.

#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍49🥱511🤔1
💡Как тривиально проверить значения интерфейса на nil?

В Go новички часто сталкиваются с проблемой интерфейсных переменных, которым присваивается nil указатель. В таком случае, хотя значение в интерфейсе является nil, сама переменная интерфейса не равна nil.

Пример: создаем переменную x как указатель на int, который по умолчанию nil, и переменную y как пустой интерфейс, который тоже nil по умолчанию. После присваивания x переменной y, интерфейс y уже не является nil, хотя x все еще nil.


var x *int
var y any
y = x


📌 Что вернет y == nil?

Вернет false. Это потому, что интерфейс не просто представляет значение, которое ему присвоено, а действует как контейнер для этого значения.

Для проверки, является ли значение в интерфейсе nil, нужно использовать утверждение типа. Например, для проверки y на nil, используем:


y.(*int) == nil


Это показывает, что интерфейс y не nil, но содержащееся в нем значение — nil. Полный пример здесь.

#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍50💯3
🤔 Как исключить зависимости разработки из бинарных файлов в Go: по мотивам статьи Redowan Delowar

В настоящее время, в отличие от Python или NodeJS, Go не позволяет указывать зависимости разработки отдельно от зависимостей приложения. Однако автор предпочитает явно указывать зависимости разработки для лучшей воспроизводимости.

Работая над новым CLI-инструментом для проверки неработающих URL-адресов в файлах markdown, автор столкнулся с интересным соглашением: можно указать зависимости разработки в файле tools.go и затем исключить их при сборке бинарного файла, используя тег сборки.

Вот как это работает. Предположим, у нашего проекта foo есть следующая структура:

foo
├── go.mod
├── go.sum
└── main.go


Файл main.go содержит простую функцию "hello-world", использующую стороннюю зависимость:

package main

import (
"fmt"

// Cowsay - это сторонняя зависимость приложения
cowsay "github.com/Code-Hex/Neo-cowsay"
)

func main() {
fmt.Println(cowsay.Say(cowsay.Phrase("Hello, World!")))
}


Здесь Neo-cowsay — это зависимость приложения. Для инициализации проекта запускаются следующие команды:

go mod init example.com/foo   # создает файлы go.mod и go.sum
go mod tidy # устанавливает зависимости приложения


Теперь предположим, что мы хотим добавить следующие зависимости разработки: golangci-lint для линтинга проекта в CI и gofumpt как более строгий gofmt. Поскольку эти инструменты не импортируются напрямую, они не отслеживаются инструментарием сборки.

Но можно воспользоваться следующим воркфлоу:

1. Разместить файл tools.go в корневой директории.
2. Импортировать зависимости разработки в этом файле.
3. Запустить go mod tidy, чтобы отслеживать как зависимости приложения, так и зависимости разработки через go.mod и go.sum.
4. Указать тег сборки в tools.go, чтобы исключить зависимости разработки из бинарного файла.

В этом случае файл tools.go выглядит следующим образом:

// go:build tools

package tools

import (
// Зависимости разработки
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
_ "mvdan.cc/gofumpt"
)


Теперь, если вы запустите go mod tidy, инструментарий Go будет отслеживать зависимости через файлы go.mod и go.sum. Вы можете проверить зависимости в go.mod.

Хотя зависимости разработки отслеживаются вместе с зависимостями приложения, тег сборки // go:build tools в начале файла tools.go скажет инструментарию сборки игнорировать их при создании бинарного файла.

Из корневой директории foo можно собрать проект, запустив:

go build main.go


Это создаст бинарный файл main в корневой директории. Чтобы убедиться, что бинарный файл не содержит зависимости разработки, запустите:

go tool nm main | grep -Ei 'golangci-lint|gofumpt'


Это не вернет ничего, если зависимости разработки не упакованы в бинарный файл.

Но если вы сделаете это для зависимости приложения, она выведет артефакты:

go tool nm main | grep -Ei 'cowsay'


Команда выведет:

1000b6d40 T github.com/Code-Hex/Neo-cowsay.(*Cow).Aurora
1000b6fb0 T github.com/Code-Hex/Neo-cowsay.(*Cow).Aurora.func1
1000b5610 T github.com/Code-Hex/Neo-cowsay.(*Cow).Balloon
1000b6020 T github.com/Code-Hex/Neo-cowsay.(*Cow).Balloon.func1
...


Если по какой-то причине вы хотите включить зависимости разработки в свой бинарный файл, вы можете передать тег tools при сборке бинарного файла:

go build --tags tools main.go


#tip
Please open Telegram to view this post
VIEW IN TELEGRAM
👍332🤔2🥱2