tgindex
Thank Go!

Здравый взгляд на язык программирования Go. Злой админ @nalgeon. Добрый админ @mikeberezin. Рекламы нет.

Последний пост
12 авг.
Последнее чтение
18:20
Постов за неделю
2
Всего постов
20
Тип
открытый
Язык
русский
Категория
Технологии
В каталоге с
12 авг.
Подписчики
3 282
+1 за 4 дн.
Сутки
0
0,00%
Неделя
 
Месяц
 
Просмотров на пост
2 540
20 постов
Вовлечённость
77,4%
к подписчикам
Постов в день
0,3
всего 20
Упоминаний
2
каналов
Охват размещения
оценка
1/24сутки в ленте
1 020
1/48двое суток
1 168
1/72трое суток
1 260

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • 12 авг.1 029265

    strings.CutLast Из небольших изменений в Go 1.27 я больше всего рад strings.CutLast. Эта функция делит строку по последнему вхождению разделителя. Конечно, реализовать ее несложно и самому: // CutLast делит s на части до и после последнего вхождения sep. func CutLast(s, sep string) (before, after string, found bool) { if i := strings.LastIndex(s, sep); i >= 0 { return s[:i], s[i+len(sep):], true } return s, "", false } Но приятно, что теперь делать этого не придется: s := "go|for|it" _, last, ok := strings.CutLast(s, "|") fmt.Println(last, ok) // it true Интересно, что Cut добавили еще в 1.20, а CutLast почему-то пробыла в немилости аж 7 релизов :)

  • 10 авг.1 272278

    Generic-методы и их последствия Думаю, «джавафикация» экосистемы Go только усилится с появлением в 1.27 дженерик-методов (это методы, у которых может быть собственный параметр типа). // Sum - сумма целых чисел. type Sum[T int | int64] struct { v T } // Add добавляет число к сумме, приводя его к целому, если нужно. func (s *Sum[T]) Add[V int | int64 | float64](v V) *Sum[T] { s.v += T(v) return s } В этом примере T — параметр типа на структуре, а V — параметр типа на методе. В предыдущих версиях Go параметры вроде V приводили к ошибке "method must have no type parameters", а теперь будут разрешены. var s Sum[int] s.Add(30).Add(10).Add(2.3) fmt.Println(s.Val()) // 42 Вряд ли мы увидим ненужные дженерик-абстракции прямо в стдлибе, но многие авторы сторонних пакетов и приложений с радостью начнут (точнее, продолжат с удвоенной силой) их городить.

  • 6 авг.2 1341730

    Тур по Go 1.27 Со дня на день выйдет Go 1.27, так что самое время почитать, что там внутри. Я слегка задолбался делать обзоры релизов, но ребята из VictoriaMetrics решили принять эстафету, так что читайте у них в блоге. С интерактивными примерчиками, все как мы любим. Краткое содержание для ленивых: — Generic-методы с собственными (не привязанными к типу) параметрами — Компактные литералы для embedded-структур — Диагностика утечек горутин в продакшене (было experiment в 1.26) — UUID v4 и v7 — JSON v2 в продакшене (был experiment в 1.26) — Универсальный SIMD (experiment) — strings.CutLast (давно о ней мечтал) — Интерфейс maphash.Hasher — synctest.Sleep (= time.Sleep + synctest.Wait) https://victoriametrics.com/blog/go-1-27

  • 1 авг.2 6142415

    Делаем себе сложно Хотел я вам написать, как можно начать с очень простой реализации Backward, и прийти к чему-то похожему на стдлибу. Но заметка получилась великовата для канала, поэтому вынес ее на хабр. Не уверен, что тамошние обитатели будут в восторге. Вам же заметку рекомендую — в ней есть jira, жабы, и нелепый авторский юмор. https://habr.com/ru/articles/1065634

  • 31 июл.2 523611

    видео или голосовое, без подписи

  • 28 июл.2 7234022

    Что внутри у интерфейса Многие знают, что интерфейс в Go реализован как обертка, внутри которой два указателя — на конкретное значение и на его тип. Но строго говоря, все несколько сложнее. Вот интерфейс: type iface struct { tab *abi.ITab data unsafe.Pointer } data — это действительно указатель. Если мы заворачиваем в интерфейс значение, в data будет указатель на него (а само значение переедет в кучу при необходимости). Если же заворачиваем указатель (например *os.File в io.Reader), то в data будет ровно этот самый указатель. А вот tab — не просто конкретный тип, а целая пачка метаданных: type ITab struct { Inter *InterfaceType Type *Type Hash uint32 Fun [1]uintptr } Inter — это тип интерфейса (например, io.Reader), Type — конкретный тип (например, *os.File). Hash — это хеш от конкретного типа. Он используется для быстрого выбора подходящей ветки в переключателях типа (type switch). О нем еще поговорим отдельно. А Fun — это массив указателей на методы типа, которые реализуют интерфейс (например, (*os.File).Read). Из объявления Fun [1]uintptr может показаться, что у интерфейса может быть только один метод (что, как мы знаем, не так). Но на самом деле это фикция. Указателей будет ровно столько, сколько методов у интерфейса, просто Fun[0] — первый из них. Ах да, пустой интерфейс (any) реализован иначе. О нем тоже отдельно поговорим. Уфф.

  • 24 июл.2 2532719

    Метод-выражение Все, конечно, знают, что такое метод структуры: type counter struct { val uint } func (c *counter) inc() { c.val++ } Но не все знают, что метод реализован как обычная функция, у которой первым параметром идет значение получателя (или указатель на него): // реализованы одинаково func (c *counter) inc() func inc(c *counter) На практике это означает, что вы можете использовать метод как функцию: inc := (*counter).inc c := new(counter) inc(c) inc(c) inc(c) println(c.val) // 3 Называется такое чудо «метод-выражение» (method expression). P.S. А бывает еще метод-значение — это более причудливый зверь, замыкание на получателе.

  • 20 июл.2 5593713

    Рубрика «ненормальное программирование» Запихиваем максимальное количество встроенных функций в одно выражение: println(max( len(append(make([]int, 1, 2), *new(int))), cap(make(chan struct{}, 3)), copy(make([]byte, 5), "hello"), min(int(real(complex(1, 2))), int(imag(complex(3, 4)))), map[any]int{}[recover()], )) Здесь их 13 🤓 В порядке появления: println max len append make new cap copy min real complex imag recover Для большего погружения, попробуйте завтра писать код в таком стиле, и обязательно отправьте его на ревью.

  • 20 июл.2 3263110

    18 встроенных функций Go Вам, конечно же, не терпится увидеть полный список встроенных функций (нет). Ну раз вы так настаиваете, то вот он: make создает новый срез, карту или канал. new аллоцирует память под переменную. append добавляет одно или несколько значений в срез. copy копирует элементы из одного среза в другой (или байты из строки в срез). delete удаляет элемент из карты. clear очищает срез или карту. len и cap возвращают длину/емкость контейнера (строка, срез, карта, канал). close закрывает канал. min и max возвращают минимальный/максимальный из аргументов. complex, real и imag собирают и разбирают комплексные числа. panic выбрасывает панику, а recover ее ловит. print и println печатают аргументы. Можете ради интереса посчитать, сколько из них вы реально используете :)

  • 18 июл.2 2184513

    Встроенные функции Все регулярно используют встроенные функции (built-in). Это функции, которые не требуют импорта пакета, вроде len, make или panic. Но что такое вообще встроенная функция? Например, len в документации заявлена как часть пакета builtin: func len(v Type) int Во-первых, что это еще за builtin-пакет и тип Type в нем? Во-вторых, len явно дженерик, так где же наша любимая абракадабра вида len[S ~[]E, E any](x S), ну или хотя бы len[T any](s []T)? И как вообще это добро работало начиная с версии Go 1.0, когда никаких дженериков даже на горизонте не было? Суровая правда в том, что len — вообще не функция. У нее нет собственного фрейма на стеке, как у обычных функций, и других присущих им особенностей. На самом деле, компилятор превращает вызов len(s) в прямое чтение s.len, где len — это обычное поле структуры среза (или аналогичное поле строки, карты или канала): type slice struct { array unsafe.Pointer len int cap int } Не все билтины настолько просты. Например, make и panic компилируются в реальные вызовы функций (runtime.makeslice, runtime.gopanic) — с фреймом и всем полагающимся. Общее у всех встроенных функций одно: компилятор не вызывает их буквально как написано, а подставляет вместо этого что-то свое. Что касается Type, то это просто заглушка. Пакет builtin не компилируется и существует только ради документации. Такое вот коварство.

  • 13 июл.2 272107

    Функциональные опции Должен признаться, что терпеть не могу паттерн «функциональных опций», полюбившийся многим программистам на Go. И особенно я не рад, что он пробрался в стдлибу вместе с новым пакетом json/v2 (финализирован в Go 1.27): type Options interface { ... } func Deterministic(v bool) Options func FormatNilMapAsNull(v bool) Options func FormatNilSliceAsNull(v bool) Options // ... func Marshal(in any, opts ...Options) (out []byte, err error) Обычная структура (см. скриншот) лучше во всех отношениях, на мой взгляд. А вы что думаете?

  • 8 июл.2 8165215

    string ⇄ []byte Все знают, как преобразовать байтовый срез в строку и обратно: b := []byte{...} s := string(b) // 1 s := "..." b := []byte(s) // 2 И ➀ string(b) и ➁ []byte(s) создают копию данных длиной len(x) — на стеке или в куче, в зависимости от ситуации. Цикл с преобразованием string(b) тоже создает копию: for _, r := range string(b) { ... } А вот цикл с преобразованием []byte(s) копию никогда не создает — итерация идет прямо по байтам оригинальной строки: for _, ch := range []byte(s) { ... } Такая оптимизация компилятора.

  • 2 июл.3 3274010

    Флаг -race Флаг -race включает проверку на гонки в коде (потенциальный конфликт при доступе к общим данным из нескольких горутин). Если при выполнении рантайм видит гонку, он пишет, где именно та произошла. Несложно догадаться, что проверка эта небесплатная. С включенным детектором гонок приложение использует в 5–10 раз больше памяти, а работает в 2–20 раз медленнее. Надо ли собирать продакшен с включенным -race? Разве что если в ваши планы входит потом «оптимизировать» производительность в 10 раз и получить за это почет и уважение. Ну или по башке, если кто-то проверит, что именно вы там наоптимизировали 😅

  • 2 июл.2 966224

    видео или голосовое, без подписи

  • 30 июн.2 9483018

    Грязный make А хотите по-настоящему злую штуку? В стандартной библиотеке есть функция, которая создает срез байт, не зануляя память: // MakeNoZero makes a slice of length n // and capacity of at least n bytes // without zeroing the bytes. // It is the caller's responsibility // to ensure uninitialized bytes // do not leak to the end user. func MakeNoZero(n int) []byte Функция находится в пакете internal/bytealg, так что напрямую ее не вызвать. Но через go:linkname — можно: //go:linkname makeNoZero internal/bytealg.MakeNoZero func makeNoZero(n int) []byte func main() { data := makeNoZero(10) // ... } Никогда так не делайте 😈

  • 29 июн.2 587457

    Нулевое значение Приятное отличие Go от C заключается в том, что он всегда инициализирует переменные нулевым значением (zero value) данного типа: var n int // 0 var b bool // false var s string // "" var p *T // nil Для массивов и срезов (через make) это тоже так работает: var arr [3]int // [0 0 0] s := make([]int, 3) // [0 0 0] Но что будет, если создать срез с емкостью большей, чем длина? s := make([]int, 3, 6) Понятно, что первый-второй-третий элементы равны 0. А что с четвертым-пятым-шестым? Они тоже инициализированы нулями. Go абсолютно всегда инициализирует память, выделенную под переменные. Не может быть такого, чтобы память выделена, а значения в ней мусорные (те что были до объявления переменной). Приятная определенность.

  • 25 июн.2 9345130

    Массив ⇄ срез Все знают, как обернуть массив в срез: arr := [3]int{11, 22, 33} s := arr[:] s[1] = 100 println(arr[1]) // 100 Но не все в курсе, что из среза можно извлечь указатель на находящийся под ним массив: s := []int{11, 22, 33} parr := (*[3]int)(s) parr[1] = 100 println(s[1]) // 100 В обоих случаях занятая массивом память не копируется. А вот если преобразовать срез в массив (не указатель), то массив под срезом будет полностью скопирован: s := []int{11, 22, 33} arr := [3]int(s) arr[1] = 100 println(s[1]) // 22 Теперь знаете, чем щегольнуть на собеседовании :)

  • 23 июн.2 7224810

    Break и continue-метки Все мы любим Go за простой синтаксис. Но, на самом деле, всякой «дичи» в языке тоже хватает («дичь» здесь не обязательно что-то плохое, а скорее редко используемые возможности). Вы наверняка в курсе существования меток и goto. Но обычным goto в Go дело не ограничивается. Break + метка break в сочетании с меткой выходит из цикла любой вложенности: outer: for i, row := range rows { for j, val := range row { if val == target { fmt.Println("found at", i, j) break outer // выходит из обоих циклов } } } Обычный break, без метки, вышел бы только из внутреннего цикла по j. Continue + метка continue в сочетании с меткой переходит к следующей итерации внешнего цикла: outer: for _, order := range orders { for _, item := range order.Items { if outOfStock(item) { continue outer // переходим к следующему заказу } } process(order) } Обычный continue, без метки, перешел бы к следующему item, а не order. Обе возможности применяются довольно редко. Чаще внутренний цикл выносят в отдельную функцию или метод и используют return. Но если код органичнее смотрится в одной функции, то break/continue с меткой вполне себе вариант.

  • 2 июн.3 7265140

    UUID в Go 1.27 Много лет команду Go просили добавить генерацию uuid в стдлибу, и много лет они отказывались (что совершенно нелогично — ведь уиды есть в каждом втором проекте, это лучший кандидат на добавление). Но здравый смысл наконец возобладал, и в Go 1.27 добавят пакет uuid для генерации UUIDv4 (полностью случайные айдишники) и UUIDv7 (частично упорядочены по времени с точностью до 1 мс). Так вот. Если вы посмотрите исходники, то можете заметить кое-что странное — мьютекс v7mu, объявленный на уровне пакета. Вообще-то генерация уидов не требует глобального состояния, так зачем нужен мьютекс? Оказывается, команда Go хотела гарантировать, что функция NewV7 (которая создает новый UUIDv7) всегда возвращает строго возрастающие UUID (если только системные часы не пошли назад). Чтобы это реализовать, им пришлось добавить общее состояние на уровне пакета (переменные v7lastSecs и v7lastTimestamp), и защитить его мьютексом v7mu. NewV7 использует их, чтобы убедиться, что каждый новый UUID идет после предыдущего. Получается, нарушили «архитектурную красоту» ради удобства использования.

  • 1 июн.2 8562010

    netip.Addr и уникальная ручка netip.Addr в Go — любопытная штука. В основе типа лежит пара uint64-чисел (старшая и младшая части IP-адреса) — это компактное и эффективное представление. Но есть нюанс: IPv6-адреса могут иметь "зоны". Зона определяет конкретный сетевой интерфейс для локального IP-адреса. На уровне передачи данных зона — это uint32 (например, 12), но команда Go решила хранить зоны как строки (например, "eth0"). Как сохранить строку в структуре Addr, не ухудшая размер и производительность? И здесь появляется unique.Handle. Пакет unique хранит глобальный кэш всех зон (addrDetails), которые используются в программе в данный момент. Структура Addr хранит ссылку на конкретную запись в этом кэше. Некрасиво, но эффективно.