go-with-me
1.59K subscribers
67 photos
80 links
go-with-me — уютный канал, где Golang становится дружелюбным

Мы — Go-инженеры в BigTech, обожаем делиться своей экспертизой,
пишем статьи о скрытых ловушках, объясняя все на пальцах

— Проводим Go-моки

questions @lovelygopher | @prettygopher
Download Telegram
✏️ Mock-собеседования. Отзывы

Спасибо, что обратил(ся/ась) к нам, оставь свое честное мнение

Если вам нужно мок-собеседование, смело пишите нам @lovelygopher | @prettygopher | channel direct
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1143👌111
Forwarded from myStack
if err != nil остаётся

Команда Go решила не менять синтаксис обработки ошибок и закрывает все предложения по упрощению error handling - ни один вариант не получил широкой поддержки ни в команде, ни в сообществе.

Причины:
- Нет консенсуса, нужен ли такой синтаксический сахар
- Привычный способ error handling признан рабочим и идиоматичным
- Изменения усложнят язык и приведут к массовым изменениям в коде
👍44733😱2
🌰🌰 Slices. Part 2. Slice Escaping. Fast slice allocations

Доставайте тетрадки и ручки ✏️🗒

Тыкались мы в make, да не дотыкались. Сегодня будем дотыкиваться и оптимизировать аллокацию наших байтиков так, чтобы она происходила максимально эффективно

Перед прочтением данного поста вам стоит углубиться в тему Escape Analysis тык

Кратко, как работает Escape Analysis. Значение уходит в секцию Heap, когда:
— Из функции возвращается указатель на эту переменную, находящуюся в рамках scope’a этой функции
— Прокидываем аргумент в interface параметр функции, который может быть оценен компилятором как изменяемое значение (к примеру fmt.Println(v … any))
— Размер объекта превышает его максимальный в соответствии с его size class
— Прокидываем в канал указатели
— Используем переменную в замыкании (обращаемся к переменной outer функции в inner функции)

На берегу обсудим пару моментов:
Slice header может лежать как на стэке, так и на хипе. Все зависит от того, эскейпился он или нет, при этом нижележащий массив также может находиться в одном из этих пространств памяти
— Максимальный размер нижележащего массива слайса, когда он может лежать на стэке - 64 KB. Если превысить данный порог, моментально уйдем на хип

Чтобы узнать, эксейпятся ваши переменные или нет, используйте:
go build -gcflags="-m" main.go

Учтите, что чем больше вы укажете флагов `-m`, тем более развернутый ответ получите


Потоптались на месте, приступим к раскопке гробниц фараонов (примерам):
func ExampleA() []byte {
const size = 64 * 1 << 10 // 64 KB

return make([]byte, 0, size) // Не улетит в хип т.к. капасити известна на момент компиляции
}

func ExampleB(size int) []byte {
return make([]byte, 0, size) // Улетит в хип, т.к это динамически вычисляемый параметр в рантайме
}


С эскейпом разобрались. Перейдем к оптимизациям:

1. Нам нужна быстрая аллокация слайса на стэке, но мы хотим это сделать в обход ограничения в 64 KB. Используем sparse literal оптимизацию: явно определяем значение для индекса maxSize - indexPad с занулением всех элементов до него, а сам слайс обрезаем до [:0]. Получаем len = 0, cap = maxSize
func ExampleC() []byte {
const (
maxSize = 1 << 30
indexPad = 1
)

return []byte{maxSize - indexPad: 0}[:0] // 1 GB попадет на стэк несмотря на ограничение в 64 KB. Это фича, которую я отрыл в исходниках (работает с версии Go 1.17)
}


2. Нам нужно инициализировать слайс быстрее make:
— Наращиваем буфер до нужного размера через strings.Builder (который под капотом динамически расширяет []buf до нужного size class)
— Добираемся до первого байта байт-последовательности нашей строки с помощью unsafe.StringData(...)
— Передаем данный указатель в unsafe.Slice, который вернет нам наш созданный слайс
— Сбрасываем len до нуля с помощью реслайсинга по самому же себе (то есть [:0])
— Заметка: Для работы с unsafe желательно понимать нюансы его использования и иметь высокую экспертизу тык
Этот метод позволит кратно увеличить перформанс!
func ExampleD(size int) []byte {
if size <= 0 {
return nil
}

var b strings.Builder
b.Grow(size)

return unsafe.Slice(unsafe.StringData(b.String()), len(b.String()))[:0] // Работает быстрее дефолтного make
}


Проверим, насколько наши способы эффективнее относительно создания с помощью make
Имеем вот такие бенчмарки:
// goos: darwin
// goarch: arm64
// pkg: workspace
// cpu: Apple M3 Pro
// BenchmarkExampleB-12 100 42463998 ns/op 1073742102 B/op 1 allocs/op
// BenchmarkExampleC-12 100 39844979 ns/op 1073741835 B/op 1 allocs/op
// BenchmarkExampleD-12 100 26116605 ns/op 1073741884 B/op 1 allocs/op

Важно отметить, что для всех бенчмарков использовалась размерность 1<<30 bytes (1 GB)

Выводы:
— Создание через unsafe ExampleD в сравнении с make ExampleA быстрее на 38.50%
— Создание через sparse инициализацию ExampleС в сравнении с ExampleA быстрее на 6.17%

Ваши готовые конспекты и вопросы ждем в комментах 📕

Статью писали с Дашей: @dariasroom

Stay tuned!
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥654211
❤️‍🔥 GolangConf X Как это было?

Это мой первый митап в жизни и я просто в восторге и преисполнен эмоциями. Прошло все феерично и тепло

Интереснейшие доклады, из которых я подчерпнул оверполезные знания
Уютная, окутывающая и успокаивающая атмосфера
Огромное колличество коллег, с которыми можно понетворкаться, что немаловажно для меня
Про кухню я вообще молчу (отпад всего)
Познакомился с очень крутыми ребятами, меня это очень радует и драйвит

Организаторам огромная благодарность, вы умнички 💗
@onokonem
@Filiko_floro

Я обязательно буду присутствовать на следующей конференции в качестве спикера. Думаю, что настало мое время зажечь!

🔍 Следите за обновлениями и обязательно приходите!
golangconf.ru/2025

В качестве знакомства с вами заливаю чуть-чуть себя, живого и радостного 💗

Stay tuned!
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3615👍631
💣 Последний пост про слайсы не просто бомба, а большущий крипер из майнкрафта

Небольшая затравка. Нет-нет, мы не будем вас травить, а просто оставим ссылку на код: тык

Будет ли здесь датарейс, если да, то почему? Пишите в комментах

Если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал ❗️

BOOST BOOST BOOST

Stay tuned 😮
Please open Telegram to view this post
VIEW IN TELEGRAM
9🔥32😁111
🌰🌰 Slices. Part 3. Slice subtleties

В наших планах не было настолько душнить по слайсам, но это вынужденная мера. Я надеюсь, вы еще не устали от них. Да и даже если устали - терпите!

Кратко по содержанию: тут будут фичи, которые частенько спрашивают на собесах

Разберем приемы, которые помогут использовать слайсы очень изящно

1. Slice Length Zeroing
Занулив длину слайса конструкцией: s[:0], мы можем впоследствии зааппендить только нужные нам элементы без лишних аллокаций, так как работаем с тем же нижележащим массивом

— Когда? - фильтрация по определенному предикату

— Пример: исключаем пустые строки из слайса s
func Filter(s []string) []string {
if len(s) == 0 {
return s
}

filtered := s[:0]

for i := range s {
if s[i] == "" {
continue
}

filtered = append(filtered, s[i])
}

return filtered
}


2. Len raising
— Когда? - у вас не должно возникать этого вопроса, просто используйте и кайфуйте

— Пример:
func RaiseLen(s []int, n int) []int {
if n <= 0 || len(s)+n > cap(s) {
return s
}

return s[:len(s)+n]
}


3. Append and Array

Помним, что массив - это отдельная структура данных, а append(dest, src) принимает в себя только слайсы и прямые перечисления элементов

— Когда? - скорее всего вас это спросят как-нибудь на собесе, вы поплывете и пойдете плакать в угол, так что просто держите это в голове

— Заметка: чтобы сделать аппенд массива в слайс вам нужно сделать реслайсинг с помощью конструкции arr[:]

— Примеры (правильный и нет):
func AppendArrayWrong(s []int, arr [1 << 8]int) []int {
return append(s, arr...) // cannot use arr as int value in argument
}

func AppendArrayRight(s []int, arr [1 << 8]int) []int {
return append(s, arr[:]...)
}


4.
FSE (Full Slice Expression)
Наверняка вы видели такое выражение s[0:5:7], где 1 и 2 элемент означают границы слайса, а последний (7) указывает на тот порог емкости, перейдя который, произойдет отвязка от слайса, по которому мы реслайсимся

Поздравляю, вы грокнули FSE. Занавес!

— Когда? - хотим наложить констрейнт на количество добавляемых элементов в реслайснутый слайс

— Пример:
func FullSliceExpressionUsage() {
s := make([]int, 5, 10)

boundedS := append(s[0:5:7], []int{1, 2, 3}...)

s[0] = -1

fmt.Printf("1st elem of \"s\": %d\n", s[0])
fmt.Printf("1st elem of \"s\": %d\n", boundedS[0])
}


5. Slice thread safety

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

Первый пример достаточно большой, поэтому мы засунули его в плейграунд тык. Чтобы проверить, будет ли датарейс в этом случае, запустите код с флагом -race, тобишь:
go run -race main.go


— Примеры:
Правильный: Worker Pool, где каждый из воркеров меняет элемент слайса по отдельному индексу (тык). Вас может смутить семантика sync.RWMutex, но не пугайтесь, он нужен для разделения контекста операций с реверсом логики sync.RWMutex. То есть воркеры конкурентно пишут в слайс используя RLock() с целью сохранить толк от concurrent записей, а отдельный поток чтения выполняет fmt.Println() под Lock() для фиксации снимка слайса

Неправильный: data race из за изменения элемента по одному и тому же индексу
// WARNING: DATA RACE
// ...
func SliceDataRace() {
var wg sync.WaitGroup

s := make([]int, 4, 8)

wg.Add(2)
go func() {
defer wg.Done()

s[3] = -1
}()

go func() {
defer wg.Done()

fmt.Println(s[3])
}()

wg.Wait()
}


Со слайсами мы закончили, слава богу! Теперь вы готовы разрубать их как самураи

Будем двигаться дальше. Куда? - не скажем

Статью писали с Дашей: @dariasroom

💭 В качестве личной рекомендации советую курс "Глубокий Go" Владимира Балуна. Там вы сможете получить исчерпывающую информацию по гошечке

➡️ Подробнее тык

🛍 Для вас мы подготовили промокод на скидку в 5%GO_WITH_ME

Stay tuned 🙃
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18543👍1👌1🥴11
Друзья, новый материал уже не за горами

Готовим много новых статей. Приготовьтесь, мы взлетаем.

По известной схеме: если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал. Помогите нам сохранить уют, это очень важно

❤️ Кто бустил ранее и хочет заполучить ранний доступ, отпишите в личку @lovelygopher

BOOST BOOST BOOST

Stay tuned 🐐
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1321
Рекомендуем отличный канал моих коллег — Ани, Кирилла и Эдгара из команды gIT

gIT - это Go To IT, канал, знакомящий с IT под новыми ракурсами и рассказывающий об интересных проектах и технологиях

Ребята во всю берут интервью у лидеров сообществ и компаний, драйвящих рынок, держат в курсе новостей и трендов продуктовой разработки

🔗 У них есть и телеграм канал, и YouTube && RuTube

Так что велком!
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍1
🍀 Tricky defers. Part 1. Key features

Бьемся челом и на коленях просим прощения за целых 3 лонгрида по слайсам и даем вам глотнуть свежего воздуха

По нашему опыту при работе с defer'ами многие упускают тонкости, описанные в данной статье

Пристегните ваши ремни, откройте рты пошире. Дышать будем медленно и глубоко!


1. Передача параметров в defer функцию

1.1 Прямая передача аргументов
Здесь используется тот же механизм, что и в обычных функциях: переданные аргументы копируются внутрь стека функции defer on the fly (фиксируем стейты передаваемых аргументов в момент вызова defer)
— Пример
// the world
// the sun is shining
// tasty bebra
func DirectPassing() {
s := "bebra"

defer func(s string) {
fmt.Println("tasty", s)
}(s)

s = "the sun"

defer func(s string) {
fmt.Println(s, "is shining")
}(s)

s = "the world"
fmt.Println(s)
}


1.2 Захват переменных замыканием
При использовании внутри defer переменных из outer функции получаем ситуацию равносильную передаче аргументов по указателю, следовательно в defer вызове получим конечные стейты переменных
— Пример
// the world
// the world is shining
// tasty the world
func ClosurePassing() {
s := "bebra"

defer func() {
fmt.Println("tasty", s)
}()

s = "the sun"

defer func() {
fmt.Println(s, "is shining")
}()

s = "the world"
fmt.Println(s)
}



2. Именованные и неименованные возвратные значения
Если данная часть будет вам непонятна, можете почитать про момент вызова defer в Go Specification

2.1 Неименованные возвратные значения
Если мы используем привычную конструкция func A() int, а после попытаемся изменить переменную int, которая была объявлена в рамках scope’а функции, в defer, изменения не затронут возвратное значение функции A(), потому что int копируется в return-регистр ДО вызова defer
— Пример
// num value before function exiting: 200
// num value in the 2nd defer `before`: 200
// num value in the 2nd defer `after`: 300
// num value in the 1st defer `before`: 100
// num value in the 1st after `after`: 777
// Func result: 200
func UnnamedReturn() int {
num := 100

defer func(num int) {
fmt.Printf("num value in the 1st defer `before`: %d\n", num)
num = 777
fmt.Printf("num value in the 1st after `after`: %d\n", num)
}(num)

num = 200

defer func() {
fmt.Printf("num value in the 2nd defer `before`: %d\n", num)
num = 300
fmt.Printf("num value in the 2nd defer `after`: %d\n", num)
}()

fmt.Printf("num value before function exiting: %d\n", num)
return num
}


2.2 Именованные возвратные значения
Когда мы определяем возвратные значения как именуемые в сигнатуре функции, эти аргументы живут ровно до выхода из функции, а значит наши return регистры будут содержать актуальные данные
— Заметка: в примере ниже используется naked return. Идиоматичным подходом является не мешать явные и неявные возвраты, имейте это в виду
— Пример
// Defer with closure close phuong-secrets.txt: file already closed
// Func result: close phuong-secrets.txt: file already closed
func NamedReturn() (err error) {
const (
filePath = "phuong-secrets.txt"
)

f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("opening the file %q: %w", filePath, err)
}
defer func() {
err = errors.Join(err, f.Close())
fmt.Println("Defer with closure", err)
}()

sr := bufio.NewScanner(f)
for sr.Scan() {
_ = sr.Text()
}

// It's made deliberately to show the changes in defer statement
f.Close()

return
}



Надеюсь, вы очень хорошо надышались и полны сил. Отпускаем вас на заслуженный отдых!

Статью писали с Дашей: @dariasroom

Stay tuned 🌷
Please open Telegram to view this post
VIEW IN TELEGRAM
13🔥5❤‍🔥321👎111
🍄 Новая статья на подходе!

Наш гофер спасся от гремучих змей и готов исследовать defer дальше!

По известной схеме: если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал. Помогите нам сохранить уют, это очень важно

Кто бустил ранее и хочет заполучить ранний доступ, отпишите в личку @lovelygopher

BOOST BOOST BOOST

Stay tuned ⚔️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤‍🔥21👀1
Что ты знаешь об итераторах в Go?

🗓24 июня в 19:00 по МСК — бесплатный открытый урок с ex-Team Lead из Яндекса

Разберём итераторы в Go так, как не объясняют в туториалах

Поговорим про:
— зачем нужны итераторы в Go
— где их стоит применять в практике
— их использование для пагинации, работы с БД и вводом-выводом
— как push и pull итераторы устроены внутри
— корутины (не горутины)

Если ты:
• используешь Go, но хочешь познакомиться с новыми возможностями языка
• уже сталкивался(лась) с итераторами и хочешь глубже понять, как они работают
• хочешь понять, где и когда следует использовать итераторы в практике

Этот урок точно стоит твоего времени

Подходящий уровень — любой, если ты знаешь синтаксис Go. Будет практика, кейсы, реальные примеры

📎 Регистрация по ссылке
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥53
🍀 Tricky defers. Part 2. Warnings

Творческий и экзистенциальный кризис закончился, мы снова готовы радовать (доставать) вас с defer'ами :)

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

Сейчас кратко покажем еще одну пачку важных моментов, которые стоит держать в голове


1. Запрещаем вам вызывать defer внутри цикла
В случае, если на каждой итерации зарегистрировать defer, у нас будут накоплены отложенные вызовы, исполняемые по выходе из функции, а не на каждой итерации, как вы могли бы предположить
— Заметка: defer по своей природе является такой же структурой (как slice, interface, chan и т.д.). В случае defer в цикле происходит escaping структур в секцию Heap
— Пример
// iter num:  1
// iter num: 2
// iter num: 3
// iter num: 4
// iter num: 5
// 4
// 3
// 2
// 1
// 0
func rangeDefer() {
var i int


for range 5 {
defer func(i int) {
fmt.Println(i)
}(i)


i++
fmt.Println("iter num: ", i)
}
}

— Заметка: в качестве решения вызываем отдельную функцию с логикой для каждой итерации с defer - в таком случае каждый вызов отработает по выходе из этой функции
// iter num: 0
// 1
// iter num: 1
// 2
// iter num: 2
// 3
// iter num: 3
// 4
// iter num: 4
// 5
func rangeDeferPerIteration() {
for i := range 5 {
fmt.Println("iter num:", i)
_ = simpleAdd(i)
}
}

func simpleAdd(i int) (res int) {
defer func() {
fmt.Println(res)
}()

return i + 1
}



2. Учитывайте тип method receiver'а на момент регистрации defer
Возьмем за основу следующую структуру с методами:
type Person struct {
name string
isMale bool
}


func (p *Person) PrintPointer() {
fmt.Printf("%+v\n", p)
}


func (p Person) PrintValue() {
fmt.Printf("%+v\n", p)
}


2.1 По значению
Вызванный метод с ресивером по значению зафиксирует текущий стейт ресивера на момент регистрации defer
— Пример
// {name:Bob isMale:false}
// {name:Johh isMale:true}
func ReceiverA() {
p := Person{
name: "Johh",
isMale: true,
}


defer p.PrintValue()


p.name, p.isMale = "Bob", false


defer p.PrintValue()
}


2.2 По ссылке
В случае указательного ресивера defer будет иметь конечный актуальный стейт структуры
— Пример
// &{name:Bob isMale:false}
// &{name:Bob isMale:false}
func ReceiverB() {
p := &Person{
name: "Johh",
isMale: true,
}


defer p.PrintPointer()


p.name, p.isMale = "Bob", false


defer p.PrintPointer()
}



3. Регистрация defer с middleware
Так как вычисление аргументов происходит перед вызовом defer, мы можем зарегистрировать defer с middleware функцией, логика которой будет исполнена до вызова defer, а возвращаемая функция - после
— Пример
func evaluatingDefer() {
defer printElapsedTime()()


// some tough logic here ...


time.Sleep(300 * time.Millisecond)
}


func printElapsedTime() func() {
start := time.Now() // Будет определено в момент регистрации defer


return func() {
fmt.Printf("Time elapsed: %s\n", time.Since(start))
}
}


// Time elapsed: 301.044459ms
func Test_evaluatingDefer(t *testing.T) {
evaluatingDefer()
}


На этом с defer полностью покончено! Сейчас мы уже готовим материал по многопоточке, когда-нибудь вы его точно дождетесь 🙂

Статью писали с Дашей: @dariasroom

Stay tuned 😂
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥17👍421😱1
🛫 Новая статья не на подходе, а на подлете

Гоферу надоели эти мерзкие подземелья, поэтому в этот раз он отправится на небо

По известной схеме: если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал. Помогите нам сохранить уют, это очень важно

Кто бустил ранее и хочет заполучить ранний доступ, отпишите в личку @lovelygopher

BOOST BOOST BOOST

Stay tuned 🥲
Please open Telegram to view this post
VIEW IN TELEGRAM
26👍2🤮221👌1🤣11
🍀 Defer 3. Panics

По просьбам почтенной публики разберем примеры возможных случаев обработки паники:
1. Подъем значения паники из функции
2. Попытка отловить панику в отдельном потоке
3. Два способа прерывания жизненного цикла N-1 горутин в случае паники одной из N

1. Подъем значения паники из функции
— Задача: отловить панику, которая возникла в вызываемой функции
— Подзадача: как-то пробросить значение паники на верхний уровень
— Решение: в вызывающей функции регистрируем defer с методом recover(). Когда вызываемая функция паникует, паника поднимается до первого встреченного defer
— Заметка: в случае паники она будет захендлена первым recover в блоке defer. А чтобы поднять значение отловленной паники "наверх", нам необходимо повторно запаничить для поиска следующего хендлера
func Outer() {
defer func() {
if v := recover(); v != nil {
fmt.Println("panic again:", v)
}
}()

Inner()
}

func Inner() {
defer func() {
if v := recover(); v != nil {
fmt.Println("panic:", v)
panic(v)
}
}()

panic(1337)
}


2. Попытка отловить панику в отдельном потоке
Каждый recover() относится к тому потоку, где он был зарегистрирован. Поэтому, когда мы defer'им recover в одном потоке, а паника случается в другом, то, соответственно, паника отловлена не будет
func PanicInGoroutine() {
var wg sync.WaitGroup

defer func() {
if v := recover(); v != nil {
fmt.Println("panic in goroutine:", v)
}
}()

wg.Add(1)
go func() {
defer func() {
wg.Done()
}()

time.Sleep(500 * time.Millisecond)
panic(1337)
}()

wg.Wait()
}


3. Отмена N-1 горутин в случае паники одной N
— Задача: прервать жизненный цикл N-1 горутин в случае паники какой-то первой горутины из этих N
— Решение: отмена через context. В случае паники одной из горутин вызываем cancel(), в то же время в каждой горутине на критических этапах выполнения кода контекст прослушивается через select. Таким образом мы максимально быстро реагируем на панику и не тратим ресурсы на выполнение горутин
— Заметка: ввиду того, что повторный cancel() ничего не делает, мы безопасно можем прожимать cancel() нескольно раз, в разных потоках, если они запаниковали, без сторонних эффектов
func RunTasksWithContext(ctx context.Context, tasksCnt int, taskFunc func(context.Context)) {
var wg sync.WaitGroup

ctx, cancel := context.WithCancel(ctx)
defer cancel()

for i := range tasksCnt {
wg.Add(1)
go func(i int) {
defer wg.Done()

defer func() {
if r := recover(); r != nil {
cancel()
}
}()

select {
case <-ctx.Done():
return

default:
taskFunc(ctx)
}
}(i)
}

wg.Wait()
}


3. Extra
Данный способ также решает проблему, описанную в пункте 3, но при помощи errgroup.
Помните, что, если не прокинуть в errgroup контекст, то в случае паники первой горутины метод errgroup Wait() дождется выполнения всех горутин и только после этого отдаст ошибку из первой горутины.
Поэтому создаем группу через errgroup.WithContext(ctx)
— Go Playground: тык


Теперь можно точно сказать, что с defer раз и навсегда покончено. В ближайшем времени будут выходить статьи по многопоточке

Stay tuned 🐈🐈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18🔥53🍓21❤‍🔥11
🎨 Interface Comparisons

В этот раз поговорим про сравнения интерфейсов и развеем все мифы, которые вы могли встретить на своем пути

Нюансы в этой теме важны для понимания ввиду возможности паник при использовании некорректных нижележащих сущностей

Для начала взглянем на структуру не пустого интерфейса:
type iface struct {
tab *itab
data unsafe.Pointer
}

— С полем data все прозрачно - это непосредственно те данные, которые мы закладываем при присваивании интерфейсу какого-то значения
itab содержит метаданные об интерфейсе, о реализующем его типе и методах

Ключевое: при сравнении не пустых интерфейсов мы сравниваем две структуры iface: itab и data. В случае с пустыми интерфейсами eface сравниваем не itab, а сам тип _type

Приступим к рассмотрению боевых примеров!


1. Сравнение простых композитных типов
В данном случае все достаточно тривиально, itab одинаковы и, следовательно можем приступить к сравнению data, а они как раз являются разнымии. Смело получаем false и идем дальше
func A() {
var v1, v2 any

p1 := Person{
Name: "Bob",
Age: 18,
}

p2 := Person{
Name: "John",
Age: 19,
}

v1, v2 = p1, p2

fmt.Println(v1 == v2)
}



2. Несравнимые типы
Первое, что необходимо зарубить себе на носу - такие сущности как map, slice, func являются uncomparable. При попытке сравнить таковые мы моментально получаем panic и идем в люльку (в близлежащий defer)

Go Playground (тык)


3. Композитные типы с вложенными несравнимыми сущностями
Нос мы свой не щадим и зарубаем еще одну штуку - несравнимый тип, содержащийся в структуре, аффектит сравнение всей структуры. Иначе говоря, все поля структуры (включая вложенные) должны быть сравнимыми
type UncomparablePerson struct {
Name string
Age uint8
Pets []string
Things map[string]struct{}
Action func()
}



3.1 Различные типы и вложенно несравнимые сущности
Мы дошли до нетривиального случая. Имеем следующий расклад:
— Наши _type разные, ввиду того, что типы разные
— Один из типов вложенно хранит в себе множество несравнимых типов

Наивным предположением было бы думать, что мы словим панику, но как бы не так!

Сравнение двух структур (eface или iface) происходит линейно (как и с массивами): сначала сравниваются поля (tab или _type), потом data. Так как типы у нас разные, поэтому сравнения полей data не будет

Получаем заслуженный false и идем дальше!
func C() {
var v1, v2 any

p := Person{
Name: "Bob",
Age: 18,
}

up := UncomparablePerson{
Name: "John",
Age: 21,
Pets: nil,
Things: map[string]struct{}{
"ball": {},
},
}
up.Action = func() {
fmt.Printf("My age is %s\n", up.Name)
}

v1, v2 = p, up

fmt.Println(v1 == v2) // false
}



3.2. Одинаковые типы и вложенно несравнимые сущности
Комментарий с выводом говорит сам за себя. Типы одинаковые, data содержит несравнимые сущности, получаем panic. Все тривиально

Go Playground(тык)


4. Сравнение интерфейса напрямую с неинтерфейсным значением
Мало тех, кто знает о том, что можно сравнить перменную интерфейса напрямую с каким-то значением. Происходить все будет аналогично сравнению двух интерфейсов
func E() {
var v1 any
p1 := Person{
Name: "A",
Age: 12,
}

v1 = p1

fmt.Println(v1 == p1)
}



5. Сравнение алиасов и новых типов в интерфейсах
Напоминание о том, как работают синонимы и типовые переопределения
type (
AliasedInteger = int // Синоним (type alias) для int:идентичен int, взаимозаменяем с ним
AnotherInteger int // Новый тип на базе int: не совместим с int напрямую, но имеет ту же внутреннюю структуру
)

func F() {
var v1, v2 any
i, ai, ali := 1, AnotherInteger(1), AliasedInteger(1)

v1, v2 = i, ali
fmt.Println(v1 == v2) // true

v1, v2 = i, ai
fmt.Println(v1 == v2) // false

castAiToInt := int(ai)
v1, v2 = i, castAiToInt
fmt.Println(v1 == v2) // true
}


Надеемся, что материал для вас был полезным. Если возникли какие-нибудь вопросы, пишите в комментарии!

Статью писали с Дашей: @dariasroom

Stay tuned 👀👀
Please open Telegram to view this post
VIEW IN TELEGRAM
30👍15🔥73😘111