Библиотека Go для собеса | вопросы с собеседований
6.88K subscribers
223 photos
7 videos
1 file
432 links
Вопросы с собеседований по Go и ответы на них.

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

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
💬Какие инструменты входят в стандартную библиотеку Go и зачем они нужны?

🔸В стандартную библиотеку Go входит набор инструментов, который помогает разработчикам в создании, анализе, тестировании и управлении исходным кодом.

📌Вот некоторые из ключевых инструментов:

▫️go fmt
— автоматически форматирует исходный код Go в соответствии со стандартным стилем. Это обеспечивает консистентность стиля кода во всей экосистеме Go и упрощает чтение кода.
▫️go get — используется для автоматической загрузки и установки пакетов из репозиториев. Он может клонировать репозиторий, собирать исходный код и устанавливать бинарные файлы.
▫️go test — используется для запуска тестов.
▫️go build — компилирует исходный код Go. Он создаёт исполняемый файл из исходного кода без необходимости явно указывать все файлы или зависимости.
▫️go run — используется для быстрого запуска программы Go без явной предварительной компиляции. По сути, это комбинация go build и выполнения полученного бинарного файла.
▫️go doc — позволяет получить документацию по пакетам, функциям и многому другому прямо из командной строки.
▫️go vet — используется для статического анализа исходного кода. Он ищет распространенные ошибки в коде, которые не всегда являются синтаксическими ошибками.
▫️go mod — используется для управления зависимостями. Он позволяет создавать новые модули, обновлять зависимости, и так далее.
15👍2
💬С управлением памятью в Go мы уже немного разобрались. А как можно вручную управлять сборщиком мусора?

В Go сборщик мусора (GC) автоматически управляет выделением и освобождением памяти. Однако иногда нам может потребоваться влиять на его работу или настраивать его поведение.

📌Управление сборщиком мусора

🔹Принудительный запуск GC: мы можем явно запустить сборщик мусора, вызвав функцию runtime.GC(). Однако в большинстве случаев лучше довериться автоматическому управлению сборщиком мусора.

🔹Отключение и включение GC

• Мы можем временно отключить сборщик мусора с помощью debug.SetGCPercent(-1).
• Чтобы включить его обратно, можно вызвать debug.SetGCPercent(100), где 100 — это стандартное значение.

📌Что такое GOGC?

🔹GOGC — это переменная окружения, которая контролирует частоту сборки мусора в Go. Она определяет процентное увеличение памяти, которое должно произойти между двумя последовательными сборками мусора.
• Если GOGC=100 (значение по умолчанию), это означает, что следующий GC будет запущен, когда общий объем выделенной памяти увеличится на 100% по сравнению с объемом памяти, освобожденным на последнем проходе GC.
• Если GOGC=50, сборщик мусора будет запускаться чаще: при увеличении выделенной памяти на 50%.
• Если GOGC=-1, сборщик мусора будет отключен.

🔹Значение GOGC можно установить перед запуском программы: GOGC=50 ./projname

🔹Или внутри программы с помощью debug.SetGCPercent().

🔹Важно понимать, что изменение частоты работы сборщика мусора может повлиять на производительность. Увеличение значения GOGC может уменьшить частоту сборок мусора, но при этом увеличит потребление памяти. Наоборот, уменьшение GOGC приведет к более частым сборкам мусора, что может уменьшить потребление памяти, но увеличить нагрузку на процессор.
👍7🔥71
💬Существуют ли в Go переменные окружения? Если да, какие наиболее часто используемые?

🔹В Go существует несколько переменных окружения, которые влияют на поведение инструментов компиляции, сборки и других аспектов языка.

📌Вот некоторые из наиболее часто используемых:

• GOEXPERIMENTAL: позволяет включать или выключать экспериментальные возможности компилятора
GOARCH: архитектура целевой системы
GOBIN: место, куда будут установлены скомпилированные исполняемые файлы при использовании go install
GOEXE: расширение исполняемых файлов для текущей ОС
GOFLAGS: флаги по умолчанию для всех команд Go
• GOOS: ОС для сборки
• GOPATH: местоположение рабочего окружения
• GOPROXY: URL-адрес прокси-сервера для скачивания модулей
• GOROOT: местоположение всей бинарной сборки Go и исходных кодов
• GOSUMDB: определяет имя используемой базы данных контрольных сумм и, при необходимости, ее открытый ключ и URL-адрес
• GOTMPDIR: директория для временных файлов
• GOVCS: используется для изменения разрешенных систем контроля версий для определенных модулей
• GO111MODULE: управляет режимом работы модулей
👍11🔥4
💬Есть ли в Go способ автоматического переключения контекста между горутинами?

🔻Планировщик Go автоматически переключает контекст между горутинами, когда это необходимо.

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

🤔Представьте, что у нас есть две горутины — одна выполняет сложные вычисления, а другая просто выводит текст. Если горутина с вычислениями использует runtime.Gosched(), это может дать горутине, которая выводит текст, шанс быстрее выполнить свою задачу.

package main

import (
"fmt"
"runtime"
"time"
)

func main() {
go func() {
for i := 0; i < 10; i++ {
fmt.Println("Вычисления:", i)
runtime.Gosched()
}
}()

go func() {
for i := 0; i < 10; i++ {
fmt.Println("Печать текста:", i)
time.Sleep(10 * time.Millisecond)
}
}()

time.Sleep(1 * time.Second)
}


Готовые к исполнению горутины выполняются в порядке очереди, то есть FIFO. Исполнение горутины прерывается только тогда, когда она уже не может выполняться: то есть из-за системного вызова или использования синхронизирующих объектов. Не существует никаких квантов времени на работу горутины, после выполнения которых она бы заново возвращалась в очередь. Чтобы позволить планировщику сделать это, нужно самостоятельно вызвать runtime.Gosched().

На практике это в первую очередь означает, что иногда стоит использовать runtime.Gosched(), чтобы несколько долгоживущих горутин не остановили на существенное время работу всех других.
👍51
💬Что из себя представляет select в контексте каналов Go?

🔹Оператор select похож на switch без аргументов, но он может использоваться только для операций с каналами.

🔹select используется для выполнения операции только с одним из множества каналов, условно выбранного блоком case.

📌Основные особенности select:

Ожидание нескольких каналов: мы можем использовать select для одновременного ожидания данных из нескольких каналов или возможности отправить данные в канал.
Неблокирующий: если ни один из каналов не готов к операции, select может немедленно продолжить выполнение с помощью default case.
Рандомный выбор: если несколько каналов готовы к операции одновременно, select выберет один из них случайным образом.

📌Пример использования select:

select {
case msg1 := <-ch1:
fmt.Println("Received", msg1)
case msg2 := <-ch2:
fmt.Println("Received", msg2)
case ch3 <- 3:
fmt.Println("Sent 3 to ch3")
default:
fmt.Println("No communication")
}


В примере select будет ждать, пока не получит сообщение из ch1 или ch2, или пока не сможет отправить значение в ch3. Если ни одна из этих операций не может быть выполнена, будет выполнен default case.

Таким образом, select предоставляет инструмент для работы с конкурентностью и каналами в Go, позволяя эффективно управлять множественными операциями ввода-вывода.
4👍4
💬Чем отличается make и new в Go? Почему бы не сравнить несравнимое…

🔸make и new — два встроенных механизма в Go для выделения памяти, но они используются в разных ситуациях и имеют разные особенности.

1️⃣new
new используется для выделения памяти для значения данного типа и возвращает указатель на этот тип.
Он инициализирует значение zero value для этого типа.
Например, new(int) вернет указатель на новое значение int, инициализированное нулем.

2️⃣make
make используется исключительно для создания и инициализации срезов, типа map и каналов.
Он возвращает инициализированный (не нулевой) экземпляр указанного типа.
Для срезов и каналов make также позволяет задавать длину и емкость.

🔸Основная разница заключается в том, что new возвращает указатель на тип с его zero value, в то время как make возвращает инициализированный тип, который готов к использованию сразу после создания.

📌Пример:

s := make([]int, 5) // создает срез длиной 5, готовый к использованию
p := new(int) // создает int со значением 0 и возвращает указатель на него
👍17🤔1
💬Для чего в Go используются синтаксическая конструкция ‘…’?

📌Синтаксическая конструкция ‘…’ в Go используется в нескольких контекстах, в частности для:

1️⃣Определения функции с переменным количеством аргументов (вариативная функция). Она позволяет функции принимать неопределенное количество аргументов одного типа. Например:

func sum(nums ...int) int {
total := 0
for _, num := range nums {
total += num
}
return total
}


2️⃣Передачи элементов среза как отдельных аргументов функции:

nums := []int{1, 2, 3, 4}
sum(nums...)


3️⃣Определения массивов неизвестной заранее длины:

x := [...]int{1, 2, 3} // Здесь Go определит длину массива на основе количества элементов
🔥12👍4👏1
💬В Go есть особенность, связанная с неиспользуемыми переменными и импортами. В чем она заключается?

🔻Особенность в том, что если в коде есть неиспользуемая локальная переменная, такой код не скомпилируется. Например:

package main

import "fmt"

func main() {
x := 10
fmt.Println("Hello, World!")
}

Компилятор выдаст ошибку: x declared but not used

🔻Неиспользуемое возвращаемое значение: если функция возвращает значение, но мы его не используем, это не вызовет ошибку компиляции. Однако, если мы явно присваиваем возвращаемое значение переменной и не используем эту переменную, тогда будет ошибка.

package main

import "errors"

func doSomething() (int, error) {
return 42, errors.New("an error occurred")
}

func main() {
doSomething() // Это допустимо
result, err := doSomething()
// Это вызовет ошибку, так как result и err не используются
}

🔻Неиспользуемый импорт: если мы импортируем пакет, но не используем его в коде:

package main

import "math"

func main() {
// Ничего не делаем
}


Компилятор выдаст ошибку: "math" imported and not used.

❗️Это правило не касается неиспользуемых параметров функций и глобальных переменных. Этот код скомпилируется:

package main

import "fmt"

var globalVar int

func greet(name string, age int) {
fmt.Println("Hello,", name)
}

func main() {
greet("Alice", 25)
fmt.Println("Hello, World!")
}
👍103
💬Как с помощью стандартной библиотеки создать простой веб-сервер на Go?

📌Вот базовый пример:

package main

import (
"fmt"
"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}

func main() {
http.HandleFunc("/", helloHandler)
http.ListenAndServe(":8080", nil)
}


1⃣Импортируем необходимые пакеты: fmt и net/http
2⃣Определяем функцию обработчика helloHandler, которая будет отвечать на все HTTP-запросы сообщением "Hello, World!"
3⃣В main регистрируем наш обработчик с помощью http.HandleFunc для корневого пути ("/")
4⃣И, наконец, запускаем веб-сервер на порту 8080 с помощью http.ListenAndServe

👉 Когда мы запустим код и перейдем по адресу http://localhost:8080, увидим сообщение "Hello, World!".

📌Либо еще проще:

package main

import "net/http"

func main() {
port := ":8080"
handler := http.FileServer(http.Dir("."))
http.ListenAndServe(port, handler)
}


📌Это что-то вроде python -m SimpleHTTPServer <port>, только на Go.

🚀 Кстати, недавно Eli Bendersky написал простой статический файловый сервер с поддержкой HTTP и HTTPS. Может быть полезен для локального тестирования веб-приложений.
Please open Telegram to view this post
VIEW IN TELEGRAM
5👍4
💬Что из себя представляет тип данных rune в Go?

🔸В современном мире невозможно работать только со строками, состоящими исключительно из ASCII-символов. Везде используются нестандартные знаки, отличные от латиницы языки и эмодзи. Для работы с такими Unicode-символами в Go представлен тип данных rune.

🔸Руна в Go представляет собой тип, который является псевдонимом для int32. Он используется для представления Unicode-кода символа.

🔸Если использовать конструкцию for range, строка автоматически будет преобразована в []rune, то есть обход будет по Unicode-символам.

📌Примеры:

1. Объявление и инициализация руны:

var r rune = 'A'

2. Преобразование строки в срез рун:

s := "Привет"
runes := []rune(s)


3. Итерация по рунам в строке:

for _, r := range "Привет" {
fmt.Printf("%c ", r)
}

// Вывод: П р и в е т


4. Обратное преобразование среза рун в строку:

runes := []rune{'П', 'р', 'и', 'в', 'е', 'т'}
s := string(runes) // "Привет"

5. Получение Unicode-кода руны:

r := 'A'
code := int32(r)
// 65


6. Проверка длины строки в рунах:

s := "Привет"
length := utf8.RuneCountInString(s)
// 6
👍113
💬Отличается ли обработка ошибок в Go от других ЯП? Если да, то чем?

🔺Обработка ошибок в Go существенно отличается от других ЯП и имеет некоторые ключевые особенности:

1. Явная обработка ошибок: в Go нет механизма исключений, как во многих других языках. Вместо этого функции, которые могут вызвать ошибку, обычно возвращают значение ошибки как один из своих возвращаемых результатов.

2. Множественные возвращаемые значения: функции часто возвращают результат (или результаты) и ошибку. Это позволяет легко проверять наличие ошибки после каждого вызова функции.

val, err := someFunction()
if err != nil {
// обработка ошибки
}


3. Кастомные типы ошибок: с помощью пакета errors можно создавать кастомные типы ошибок. Это дает возможность добавить дополнительную информацию к ошибке или создать проверяемые типы ошибок.

4. Добавление дополнительного контекста к ошибке: начиная с Go 1.13, были добавлены функции errors.Is, errors.As и fmt.Errorf для обертывания ошибок, что позволяет сохранить исходную ошибку и добавить дополнительный контекст.

func DoSomething() error {
if err := someOperation(); err != nil {
return fmt.Errorf("someOperation failed: %w", err)
}
return nil
}


5. Нет "finally": так как в Go нет исключений, нет и блока finally. Очистка ресурсов или другие завершающие действия обычно выполняются с использованием defer.

6. panic и recover: хотя Go предпочитает явную обработку ошибок, существуют механизмы panic и recover для обработки исключительных ситуаций. Однако их рекомендуется использовать осторожно и в основном для обработки действительно неожиданных ошибок, таких как выход за границы массива.
👍5🔥4
💬Что из себя представляют мьютексы в Go и какие типы мьютексов бывают?

📌Mutex означает mutual exclusion (взаимное исключение) и является способом защиты критической секции (critical section) программы. Мьютексы — один из наиболее распространенных примитивов синхронизации.

📌Критическая секция — область программы, которая требует эксклюзивного доступа к общему ресурсу. При нахождении в критической секции двух (или более) потоков возникает состояние гонки, а также возможны проблемы взаимной блокировки (deadlock).

📌Стандартная библиотека Go предоставляет два типа мьютексов для синхронизации доступа к общим ресурсам:

1⃣ sync.Mutex — стандартный мьютекс, который предоставляет эксклюзивную блокировку (exclusive lock). Только одна горутина может захватить мьютекс и получить доступ к общему ресурсу.

package main

import (
"fmt"
"sync"
)

var count int
var mu sync.Mutex

func increment() {
mu.Lock()
count++
mu.Unlock()
}

func main() {
var wg sync.WaitGroup

for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
increment()
wg.Done()
}()
}

wg.Wait()
fmt.Println(count)
}


🔹Здесь мы используем sync.Mutex для обеспечения безопасности при инкременте глобальной переменной count из множества горутин.

2⃣ sync.RWMutex — концептуально то же самое, что и Mutex. Тем не менее, RWMutex дает вам немного больше контроля над памятью.

🔸Он предоставляет доступ к критической секции произвольному количеству читателей и не более, чем одному писателю. При этом, если есть писатель, то читателей нет.

package main

import (
"fmt"
"sync"
"time"
)

var cache = make(map[string]string)
var mu sync.RWMutex

func set(key string, value string) {
mu.Lock()
cache[key] = value
mu.Unlock()
}

func get(key string) string {
mu.RLock()
defer mu.RUnlock()
return cache[key]
}

func main() {
set("name", "John")

var wg sync.WaitGroup

for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
fmt.Println(get("name"))
wg.Done()
}()
}

time.Sleep(1 * time.Second)
set("name", "Doe")

wg.Wait()
}


🔹Здесь мы используем sync.RWMutex для обеспечения безопасного доступа к кэшу. Множество горутин может одновременно читать из кэша, но только одна горутина может писать в кэш в данный момент времени.
🔥7👍4
💬В Go есть такое понятие, как «затенение» (англ. shadowing) переменной. Что оно из себя представляет?

🔹В Go происходит «затенение», когда переменная, объявленная во внутренней области видимости, имеет то же имя, что и переменная во внешней области видимости.

🔹В результате внутренняя переменная «затеняет» внешнюю, делая её недоступной в своей области видимости.

📌Пример:

package main

import (

"fmt"
)

func main() {
x := 10

if true {
x := 5 // здесь происходит затенение внешней переменной x
fmt.Println(x) // выводит 5, т. к. используется внутренняя переменная x
}

fmt.Println(x) // выводит 10, т. к. используется внешняя переменная x
}

🔹«Затенение» может быть особенно запутывающим, когда оно происходит с результатами функций, такими как err. Например, часто в Go вы можете видеть следующий код:

value, err := someFunction()
if err != nil {

// обработка ошибки
}

// ...

value2, err := anotherFunction() // здесь может произойти «затенение», если использовать := вместо =
if err != nil {
// обработка ошибки
}

Если вы случайно используете := вместо =, когда присваиваете результат anotherFunction(), вы создадите новую переменную err, которая «затенит» внешнюю переменную err. Это может привести к тому, что ошибки будут проигнорированы или обработаны неправильно.

📌Как обнаружить shadowing в коде? Есть не сколько способов: использовать встроенные инструменты Go или линтеры.

1. go vet -shadow ./...
2. golangci-lint run --enable shadow
3.
$ go get -u golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
$ go vet -vettool=$(which shadow)
👍13🔥11
💬Как можно оптимизировать использование памяти в Go, особенно при работе с большими структурами данных?

📌Для оптимизации использования памяти в Go необходимо выполнять некоторые рекомендации, в частности:

1. Избегать глобальных переменных: глобальные переменные остаются в памяти на протяжении всего времени выполнения программы. Используйте их, когда это действительно необходимо.

2. Использовать правильные типы данных: например, вместо использования int для небольших чисел можно использовать int8/int16 и т. д., в зависимости от диапазона значений.

3. sync.Pool: если в программе часто создаются и удаляются большие объекты, мы можем использовать sync.Pool для их повторного использования.

4. Ленивая инициализация: инициализировать сложные структуры данных или большие массивы желательно только тогда, когда они действительно нужны.

5. Использовать указатели на структуры: вместо передачи копии структуры мы можем передать указатель на нее. Важно знать, что это правило работает не всегда и не везде (подробнее можно прочитать здесь).

6. Срезы vs массивы: срезы могут менять свой размер и динамически выделять память. Если размер данных известен, лучше использовать массив.

7. Освобождать ресурсы: временные большие структуры данных, которые больше не нужны, следует явно освобождать, присваивая им значение nil, чтобы сборщик мусора мог быстрее их убрать.

8. Использовать буферизацию: буферизированный ввод/вывод или буферизированные каналы могут сократить количество выделений и освобождений памяти.

9. Оптимизировать структуры: структуры в Go выровнены по памяти. Переупорядочивание полей структуры может уменьшить ее размер.
👍10
💬Что из себя представляют примитивы синхронизации в контексте Go?

📌
В контексте Go примитивы синхронизации — это инструменты из пакета sync (и каналы), которые помогают нам гарантировать, что множество горутин может безопасно взаимодействовать с общими данными или координировать свою работу.

🔸sync.Mutex: основной примитив блокировки для исключения одновременного доступа к данным.

🔸sync.RWMutex: разрешает множественное чтение или одну операцию записи в текущий момент времени.

🔸sync.WaitGroup: используется для ожидания завершения группы горутин.

🔸sync.Once: гарантирует, что функция будет вызвана только один раз, несмотря на количество вызовов.

🔸sync.Cond: предоставляет механизм для блокирования горутины, пока не будет выполнено некоторое условие. Не так давно Расс Кокс отменил предложение удалить данные тип в будущей версии Go.

☝️Каналы в Go хоть и не являются примитивами синхронизации в традиционном понимании, они играют ключевую роль в синхронизации и координации горутин в Go.
👍5
💬Go — язык программирования, который отлично подходит для разработки облачных приложений. Что облачные технологии и облачные приложения из себя представляют теоретически?

📌Так звучат определения по мнению Cloud Native Computing Foundation:

Облачные технологии позволяют создавать и запускать масштабируемые приложения в современных динамических окружениях — общедоступных, частных и гибридных облаках.

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

📝Исходя из этого, облачные приложения — больше, чем просто приложения, которые работают в облаке. Они также должны отвечать некоторым требованиям/атрибутам:

👉Масштабируемость — способность показывать ожидаемое поведение в условиях значительных колебаний спроса вверх и вниз.
👉Слабая связанность — свойство системы и стратегия проектирования, согласно которой компоненты системы знают лишь самый минимум о любых других компонентах.
👉Устойчивость — способность системы восстанавливаться после ошибок и сбоев.
👉Управляемость — простота (или ее отсутствие), с которой можно изменить поведение системы для обеспечения безопасности, бесперебойной работы и соответствия меняющимся требованиям.
👉Наблюдаемость — способность определения внутреннего состояния системы по наблюдаемым результатам.
👍9
💬 Что такое data race (гонка данных) в контексте Go?

📌Гонки данных — одни из наиболее распространенных и самых сложных для отладки типов ошибок в конкурентных системах. Гонка данных возникает, когда две горутины одновременно обращаются к одной и той же переменной, и хотя бы одно из обращений — запись. Чтобы избежать эту проблемы, в Go предоставляются различные примитивы синхронизации.

📌Race Condition (состояние гонки) — более широкое понятие, чем гонка данных. Оно описывает ситуацию, когда поведение программы зависит от относительного порядка выполнения операций. Гонка данных — один из видов состояний гонки, но не единственный.

Пример гонки данных, которая может привести к сбоям и повреждению памяти:

func main() {
c := make(chan bool)
m := make(map[string]string)
go func() {
m["1"] = "a" //
Первый конфликтный доступ
c <- true
}()

m["2"] = "b" // Второй конфликтный доступ
<-c
for k, v := range m {
fmt.Println(k, v)
}
}


❗️Чтобы помочь диагностировать такие ошибки, Go включает встроенный детектор гонок данных. Для его использования добавьте флаг -race в команду go:

$ go test -race mypkg
$ go run -race mysrc.go
$ go build -race mycmd
$ go install -race mypkg

📌Переменная окружения GORACE устанавливает параметры детектора гонок данных, например:

$ GORACE="log_path=/tmp/race/report Strip_path_prefix=/my/go/sources/" go test -race

👉 Подробнее
🔥7👍4
💬Что из себя представляет пустой идентификатор в контексте языка Go?

📌Пустой идентификатор "_" в Go действует как своего рода анонимный заполнитель. Его можно использовать как любой другой идентификатор в объявлении, но с ним не будет связано никакое значение.

👉Несколько примеров:

1. Игнорирование возвращаемых значений функции: когда функция возвращает несколько значений, но нам нужно только одно или несколько из них.

value, _ := someFunction()

2. Игнорирование элементов в range: когда перебираются элементы среза или типа map, и нам нужен только ключ или только значение.

for k, _ := range myMap {
fmt.Println(k)
}


for _, v := range mySlice {
fmt.Println(v)
}


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

import _ "image/png"

4. Игнорирование переменных в множественном присваивании:

_, y := getCoordinates()

5. Игнорирование ошибок. Хотя так делать не рекомендуется, пустой идентификатор можно использовать для игнорирования ошибок.

result, _ := strconv.Atoi("123")

6. Использование в анонимных структурах: когда создается анонимная структура, и нам не нужно давать имя одному из ее полей.

person := struct {
Name string
_ int
}{"Alice", 30}


☝️Пустой идентификатор является полезным инструментом в Go и его можно безопасно использовать на практике, но злоупотреблять им точно не стоит, особенно когда речь идет об игнорировании ошибок.
👍9🔥3
💬Что из себя представляют анонимные функции и замыкания в Go?

🔸Функции в Go — обычные значения, с которыми можно работать так же, как с любыми другими объектами: они имеют типы, их можно присваивать переменным и даже передавать и возвращать другим функциям.

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

🔸Особенностью Go является доступность состояния внешней функции из анонимных функций, даже после ее завершения. Фактически это позволяет определять замыкания. Замыкание — вложенная функция, сохраняющая доступ к переменным внешней функции даже после завершения последней.

📌Возьмем функцию incrementor. Она имеет состояние в виде переменной i и возвращает анонимную функцию, которая увеличивает значение перед возвратом. Можно сказать, что возвращаемая функция «замкнута» на переменной i.

func incrementer() func() int {
i := 0


return func() int {
i++

return i
}
}


📌Вызов incrementor создаст свою локальную копию i и вернет новую анонимную функцию, увеличивающую значение этой копии. Последующие вызовы incrementor будут создавать новые копии i:

func main() {
increment := incrementer()
fmt.Println(increment())
// 1
fmt.Println(increment())
// 2
fmt.Println(increment())
// 3

newIncrement := incrementer()
fmt.Println(newIncrement())
// 1
}
🔥16👍1
💬Как проверить тип переменной во время выполнения программы в Go?

📌Оператор switch — лучший способ проверить тип переменной во время выполнения. Каждый switch содержит по крайней мере один оператор case, который действует как условный оператор, и default case, который выполняется, если ни один из блоков case не является истинным.

👉 Например, мы можем создать swich, который проверяет, содержит ли значение интерфейса i тип int или string:

func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Double %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know type %T!\n", v)
}
}

func main() {
do(21)
do("hello")
do(true)
}


👉 Результат:
Double 21 is 42
"hello" is 5 bytes long
I don't know type bool!
👍9