Спасибо, что обратил(ся/ась) к нам, оставь свое честное мнение
Если вам нужно мок-собеседование, смело пишите нам @lovelygopher | @prettygopher | channel direct
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11❤4 3👌1 1 1
Forwarded from myStack
if err != nil остаётся
Команда Go решила не менять синтаксис обработки ошибок и закрывает все предложения по упрощению error handling - ни один вариант не получил широкой поддержки ни в команде, ни в сообществе.
Причины:
- Нет консенсуса, нужен ли такой синтаксический сахар
- Привычный способ error handling признан рабочим и идиоматичным
- Изменения усложнят язык и приведут к массовым изменениям в коде
Команда Go решила не менять синтаксис обработки ошибок и закрывает все предложения по упрощению error handling - ни один вариант не получил широкой поддержки ни в команде, ни в сообществе.
Причины:
- Нет консенсуса, нужен ли такой синтаксический сахар
- Привычный способ error handling признан рабочим и идиоматичным
- Изменения усложнят язык и приведут к массовым изменениям в коде
👍44❤7 3 3😱2
Доставайте тетрадки и ручки
Тыкались мы в 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🔥6 5❤4 2☃1 1
Это мой первый митап в жизни и я просто в восторге и преисполнен эмоциями. Прошло все феерично и тепло
— Интереснейшие доклады, из которых я подчерпнул оверполезные знания
— Уютная, окутывающая и успокаивающая атмосфера
— Огромное колличество коллег, с которыми можно понетворкаться, что немаловажно для меня
— Про кухню я вообще молчу (отпад всего)
— Познакомился с очень крутыми ребятами, меня это очень радует и драйвит
Организаторам огромная благодарность, вы умнички
@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
🔥36❤15👍6 3 1
Небольшая затравка. Нет-нет, мы не будем вас травить, а просто оставим ссылку на код: тык
Будет ли здесь датарейс, если да, то почему? Пишите в комментах
Если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал
BOOST BOOST BOOST
Stay tuned
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥3 2😁1 1 1
В наших планах не было настолько душнить по слайсам, но это вынужденная мера. Я надеюсь, вы еще не устали от них. Да и даже если устали - терпите!
Кратко по содержанию: тут будут фичи, которые частенько спрашивают на собесах
Разберем приемы, которые помогут использовать слайсы очень изящно
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
Stay tuned
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18 5❤4 3👍1👌1🥴1 1
Готовим много новых статей. Приготовьтесь, мы взлетаем.
По известной схеме: если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал. Помогите нам сохранить уют, это очень важно
BOOST BOOST BOOST
Stay tuned
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13 2❤1
gIT - это Go To IT, канал, знакомящий с IT под новыми ракурсами и рассказывающий об интересных проектах и технологиях
Ребята во всю берут интервью у лидеров сообществ и компаний, драйвящих рынок, держат в курсе новостей и трендов продуктовой разработки
Так что велком!
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Go To IT
Go To IT — здесь мы раскрываем для вас информационные технологии с неожиданных ракурсов!
Бьемся челом и на коленях просим прощения за целых 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
Наш гофер спасся от гремучих змей и готов исследовать defer дальше!
По известной схеме: если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал. Помогите нам сохранить уют, это очень важно
BOOST BOOST BOOST
Stay tuned
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6❤🔥2❤1👀1
Что ты знаешь об итераторах в Go?
🗓 24 июня в 19:00 по МСК — бесплатный открытый урок с ex-Team Lead из Яндекса
Разберём итераторы в Go так, как не объясняют в туториалах
Поговорим про:
— зачем нужны итераторы в Go
— где их стоит применять в практике
— их использование для пагинации, работы с БД и вводом-выводом
— как push и pull итераторы устроены внутри
— корутины (не горутины)
Если ты:
• используешь Go, но хочешь познакомиться с новыми возможностями языка
• уже сталкивался(лась) с итераторами и хочешь глубже понять, как они работают
• хочешь понять, где и когда следует использовать итераторы в практике
Этот урок точно стоит твоего времени
Подходящий уровень — любой, если ты знаешь синтаксис Go. Будет практика, кейсы, реальные примеры
📎 Регистрация по ссылке
Разберём итераторы в Go так, как не объясняют в туториалах
Поговорим про:
— зачем нужны итераторы в Go
— где их стоит применять в практике
— их использование для пагинации, работы с БД и вводом-выводом
— как push и pull итераторы устроены внутри
— корутины (не горутины)
Если ты:
• используешь Go, но хочешь познакомиться с новыми возможностями языка
• уже сталкивался(лась) с итераторами и хочешь глубже понять, как они работают
• хочешь понять, где и когда следует использовать итераторы в практике
Этот урок точно стоит твоего времени
Подходящий уровень — любой, если ты знаешь синтаксис Go. Будет практика, кейсы, реальные примеры
Please open Telegram to view this post
VIEW IN TELEGRAM
👍21🔥5 3
Творческий и экзистенциальный кризис закончился, мы снова готовы радовать (доставать) вас с 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👍4 2❤1😱1
Гоферу надоели эти мерзкие подземелья, поэтому в этот раз он отправится на небо
По известной схеме: если вам не терпится, можете получить Early Access. Для этого достаточно просто забустить наш канал. Помогите нам сохранить уют, это очень важно
BOOST BOOST BOOST
Stay tuned
Please open Telegram to view this post
VIEW IN TELEGRAM
2 6👍2🤮2 2❤1👌1🤣1 1
По просьбам почтенной публики разберем примеры возможных случаев обработки паники:
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🔥5 3🍓2❤1❤🔥1 1
В этот раз поговорим про сравнения интерфейсов и развеем все мифы, которые вы могли встретить на своем пути
Нюансы в этой теме важны для понимания ввиду возможности паник при использовании некорректных нижележащих сущностей
Для начала взглянем на структуру не пустого интерфейса:
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🔥7 3😘1 1 1