Anonymous Quiz
6%
data.val = <nil>
7%
data.val = 0
85%
data.val = 5
3%
data.val = undefined
😁3❤1👍1👎1
🐹 Go error handling nuance
В стандартной библиотеке
оно не считает `ValueErrorType` и `*ValueErrorType` одним и тем же типом ошибки.
В отличие от него,
👉 Документация: https://docs.go101.org/std/pkg/go101.org/nstd.html#name-TrackErrorOf
Пример:
💡 Помни, что в Go T и *T — это разные типы, даже если они реализуют один интерфейс.
nstd.TrackErrorOf упрощает такие случаи и делает обработку ошибок гибче.
В стандартной библиотеке
errors.As
есть тонкость: оно не считает `ValueErrorType` и `*ValueErrorType` одним и тем же типом ошибки.
В отличие от него,
nstd.TrackErrorOf
из go101.org/nstd
обрабатывает оба варианта как одинаковые типы ошибок. 👉 Документация: https://docs.go101.org/std/pkg/go101.org/nstd.html#name-TrackErrorOf
Пример:
package main
import "errors"
type E struct{}
func (E) Error() string { return "" }
func main() {
e := &E{}
println(
errors.As(*e, &e), // false
errors.As(*e, e), // false
errors.As(e, e), // true
errors.As(e, &e), // true
)
}
💡 Помни, что в Go T и *T — это разные типы, даже если они реализуют один интерфейс.
nstd.TrackErrorOf упрощает такие случаи и делает обработку ошибок гибче.
❤3👍2
🧩 Хитрая задача по Go — «Пул, который не течёт, не зависает и сохраняет порядок»
Задача:
Реализуй обобщённую функцию MapOrdered — ограниченный по параллелизму пул воркеров, который применяет функцию
- не теряет отмену
- не допускает утечек горутин и зависаний,
- корректно обрабатывает
- поддерживает «раннее завершение» (как только достаточно результатов),
- обеспечивает backpressure (не раздувает буферы).
Сигнатуры:
Требования и тонкости
- Строгий порядок.
Выходные элементы должны соответствовать порядку поступления во input. Параллелизм допустим, но публикация результата — строго по индексу.
- Отмена и завершение.
- При отмене ctx функция должна без утечек завершить все горутины и закрыть выходной канал.
- Если EarlyStopN > 0, как только выдали N успешных результатов — корректно останавливаем обработку оставшихся задач (не зависаем, не «подвешиваем» воркеров).
🟠 Backpressure.
Никаких неограниченных буферов. Учитывай MaxInFlight, чтобы не переполнять память при медленном fn.
🟠 Panic-handling.
Если PanicAsError=true, паники из fn перехватываются и превращаются в error. Если false — паника должна «пробить» наружу (но без гонок и утечек).
🟠 Timeout на задачу.
При TaskTimeout>0 каждая задача исполняется с отдельным контекстом-дедлайном; таймаут — это ошибка задачи, а не общий стоп пула.
🟠 Гарантия отсутствия утечек.
После закрытия input и/или отмены контекста пул завершает все свои горутины. Проверь runtime.NumGoroutine() до/после и убедись в стабильном числе.
🟠 Без гонок.
Решение обязано проходить -race.
🟠 Zero-copy по возможности.
Не копируй большие данные лишний раз; не «складывай» всё в память — обрабатывай потоково. Допустимы минимальные накладные структуры (индексы, слоты).
Подсказки (но не решение)
- Для сохранения порядка пригодится «кольцо результатов» или слайс «слотов» с публикацией по индексу и «ползунком» выдачи.
- Отдельно продумай: кто закрывает выходной канал и когда (все задачи обработаны, или ранний стоп).
- Аккуратно обращайся с контекстами: каждому fn — свой дочерний ctx (для таймаутов), общий ctx — для остановки пула.
- Не забудь про range-variable capture в горутинах.
- Паники оборачивай через recover, если включён PanicAsError.
Задача:
Реализуй обобщённую функцию MapOrdered — ограниченный по параллелизму пул воркеров, который применяет функцию
fn
к входным элементам и отдаёт результаты строго в порядке входа, при этом:- не теряет отмену
context.Context
,- не допускает утечек горутин и зависаний,
- корректно обрабатывает
panic
внутри fn
,- поддерживает «раннее завершение» (как только достаточно результатов),
- обеспечивает backpressure (не раздувает буферы).
Сигнатуры:
type Result[R any] struct {
Value R
Err error
}
type Options struct {
Workers int // >0, число воркеров
MaxInFlight int // ≥ Workers, ограничение внутренних буферов
EarlyStopN int // если >0, остановиться после N успешных результатов
PanicAsError bool // если true, паники маппятся в error
TaskTimeout time.Duration // если >0, дедлайн на одну задачу
}
func MapOrdered[T any, R any](
ctx context.Context,
input <-chan T,
fn func(context.Context, T) (R, error),
opt Options,
) <-chan Result[R]
Требования и тонкости
- Строгий порядок.
Выходные элементы должны соответствовать порядку поступления во input. Параллелизм допустим, но публикация результата — строго по индексу.
- Отмена и завершение.
- При отмене ctx функция должна без утечек завершить все горутины и закрыть выходной канал.
- Если EarlyStopN > 0, как только выдали N успешных результатов — корректно останавливаем обработку оставшихся задач (не зависаем, не «подвешиваем» воркеров).
Никаких неограниченных буферов. Учитывай MaxInFlight, чтобы не переполнять память при медленном fn.
Если PanicAsError=true, паники из fn перехватываются и превращаются в error. Если false — паника должна «пробить» наружу (но без гонок и утечек).
При TaskTimeout>0 каждая задача исполняется с отдельным контекстом-дедлайном; таймаут — это ошибка задачи, а не общий стоп пула.
После закрытия input и/или отмены контекста пул завершает все свои горутины. Проверь runtime.NumGoroutine() до/после и убедись в стабильном числе.
Решение обязано проходить -race.
Не копируй большие данные лишний раз; не «складывай» всё в память — обрабатывай потоково. Допустимы минимальные накладные структуры (индексы, слоты).
Подсказки (но не решение)
- Для сохранения порядка пригодится «кольцо результатов» или слайс «слотов» с публикацией по индексу и «ползунком» выдачи.
- Отдельно продумай: кто закрывает выходной канал и когда (все задачи обработаны, или ранний стоп).
- Аккуратно обращайся с контекстами: каждому fn — свой дочерний ctx (для таймаутов), общий ctx — для остановки пула.
- Не забудь про range-variable capture в горутинах.
- Паники оборачивай через recover, если включён PanicAsError.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3❤1
🧩 Go: как работает errors.As с указателями и значениями
Ниже — мини-пример, который показывает, почему иногда нужно передавать
Код:
Ожидаемый вывод:
Ключевая идея:
🟢 Если в err лежит значение типа E, то для совпадения target должен быть *E (элемент указателя — E).
🟢 Если в err лежит *E, то для совпадения target должен быть **E (элемент указателя — *E).
🟢 Шаблон «поймать конкретный тип ошибки-пойнтер» обычно выглядит так:
var te *T; if errors.As(err, &te) { ... } — именно двойной указатель в target.
Ниже — мини-пример, который показывает, почему иногда нужно передавать
&ptr
, а иногда — сам ptr
. Главное правило: errors.As(err, target)
принимает указатель на тип (или интерфейс), и сопоставление происходит по типу элемента этого указателя.Код:
package main
import "errors"
type E struct{}
func (E) Error() string { return "" }
func main() {
e := &E{}
println(
errors.As(*e, &e), // err: E, target: **E -> ищем *E в цепочке, но у нас E
errors.As(*e, e), // err: E, target: *E -> элемент target = E, совпало
errors.As(e, e), // err: *E, target: *E -> элемент target = E, не совпало
errors.As(e, &e), // err: *E, target: **E -> элемент target = *E, совпало
)
}
Ожидаемый вывод:
false true false true
Ключевая идея:
var te *T; if errors.As(err, &te) { ... } — именно двойной указатель в target.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
Anonymous Quiz
14%
Паника во время выполнения
19%
Result:
56%
Result: <empty>
11%
Ошибка компиляции
❤5🤔1
Anonymous Quiz
17%
errors.Fatal
3%
Автоматическая перезагрузку программы
5%
throw и catch
75%
panic и recover
😁7
⚡ Полезный приём в Go — используем
Например, при загрузке ENV-переменных:
Теперь переменные окружения загружаются лишь один раз, а дальше берутся из памяти.
#golang #concurrency #tips
sync.Once
, чтобы инициализировать значение только один раз. Например, при загрузке ENV-переменных:
package config
import (
"os"
"sync"
)
var (
loadOnce sync.Once
envVars map[string]string
)
func GetEnvVars() map[string]string {
loadOnce.Do(func() {
envVars = map[string]string{
"DB_URL": os.Getenv("DB_URL"),
"API_KEY": os.Getenv("API_KEY"),
}
})
return envVars
}
Теперь переменные окружения загружаются лишь один раз, а дальше берутся из памяти.
#golang #concurrency #tips
👎8❤3👍1🔥1
👍4❤1