Функции
Last updated
Was this helpful?
Last updated
Was this helpful?
В этом разделе речь пойдет про функции - основной инструмент для декомпозиции программы на более простые блоки. Функция в Go объявляется через служебное слово func, после которого следует имя функции, перечисление параметров в скобках и тип возвращаемого значения после скобок. Результат из функции возвращается, с помощью оператора return, как и в других языках.
При этом вы можете записать туда значение и написать пустой return без имени этой переменной. И она вернет вам то, что вы записали. В данном случае это переменная out. Впрочем, можно написать,например, return 3, даже в таком случае функция будет корректно работать. В Go функции могут возвращать несколько результатов. Чаще всего всего это используется для возврата ошибки в качестве второго параметра.
При этом параметров не обязательно должно быть два, их может быть три или более. Но очень многие служебные функции Go из стандартной библиотеки возвращают как раз два параметра. При этом при использовании return для возврата значения необходимо каждый раз указывать оба параметра через запятую. В данном случае указываем сначала первый параметр in, потом второй параметр ошибку. На 5-й строчке указан возвращаемый результат in и nil в качестве ошибки. Также можно вернуть несколько именованных результатов. Для этого достаточно объявить у них имена. Стоит обратить внимание, что все именованные возвращаемые результаты сразу инициализируются значениями по умолчанию.
То есть, если в теле функции вы не переприсвоите значение, то будет возвращаться значение по умолчанию. Например, по умолчанию rez = 0, после присвоения rez = 1, функция будет возвращать 1. Если теперь присвоить какое-то значение ошибке на строчке 5, то return будет аналогичен return rez, err. Однако, еще раз обратим внимание,что никто не заставляет использовать именованные результаты. Мы все так же вправе вернуть самостоятельно, например, 3 и ошибку. В Go функции могут принимать неограниченное количество параметров, но только одно типа. Такие функции называются вариативными, по английски variative. Для того чтобы так сделать, нужно указать перед типом параметра троеточие.
В этом случае на вход, в данном случае в переменную in, поступит slice интов, по которым вы сможете проитерироваться, используя цикл for range. Обратим внимание на одну тонкость привызове функции с вариативным числом параметров.
У нас есть слайс nums, если передать в сумму просто слайс, то компилятор в этом случае будет ругаться, потому что это отдельный тип. А ожидается какое-то количество повторяющихся одиночных параметров. Поэтому необходимо использовать троеточие после имени слайса. Это распаковывает слайс в одиночные аргументы, которые передаются функции. Кстати, функция Println — как раз-таки пример вариативной функции. Она принимает множество параметров и выводит их в строковое представление.
Функция, как объект первого класса, анонимные функции А теперь поговорим о функциях, как об объектах первого класса. Слова функция как объект первого класса означают, что вы можете присваивать функцию в какую-то переменную, принимать функцию, как аргумент в другую функцию и возвращать функцию, как результат работы какой-то функции. А также иметь функцию, как поле какой-то структуры. Например, обычная функция, определяется в отдельном блоке. // обычная функция
func doNothing() {
fmt.Println("i’m regular function")
}
То есть она имеет имя, в данном случае doNothing. А анонимная функция не имеет имени и определяется там, где вы захотите. Например, вот так можно объявить анонимную функцию.
Мы не указываем имя, а сразу после ключевого слова func указываем входныепараметры. И сразу же вызываем эту функцию на строке 5, передавая в качестве параметра строчку nobody. То есть nobody — это параметр анонимной функции, который выведется в Println. Также анонимную функцию можно присвоить какой-то переменную и потом вызвать ее.
То есть мы будум обращаться к такой переменной, как к функции. Иногда случается так, что вам приходится много где объявлять функции, передавать их куда-то, по- этому вы можете определить специальный тип функции. Так же, как и другие типы он определяется при помощи ключевого слова type, имени типа и базовой переменной.
// определяем тип функции
type strFuncType func(string)
В данном определение функции сигнатура функции тоже может выступать базовым типом. При этом можно указать как входящие параметры, так и возвращаемые значения, Теперь давайте рассмотрим следующий вариант, когда мы хотим передавать функцию, как параметр, в другую функцию. Например, определим анонимную функцию worker, которая будет принимать перемен- ную, которая называется callback типа strFuncType, определенного выше. Присвоим анонимную функцию переменной и будем её вызывать.
Передадим определенную выше функцию printer, которая будет выводить то, что в нее напечатали. На экран будут выведено as callback, то есть функция printer была вызвана, как callback. Callback-и полезны, если вы необходимо выполнить какую-то функцию по завершении какой-то работы или в зависимости от разных условий выполнить разную логику какой-то другой функции. Теперь рассмотрим замыкание. Замыкание — это такая функция, которая обращается к переменным, которые были объявлены вне ее блока, вне ее объявления. Как это выглядит?
Мы определяем функцию prefixer, которая принимает строчку prefix и возвращает функцию определен- ного типа. Определяем возвращаемую функцию в строчке return func... У неё есть свой входной параметр in, но она — самое интересное — будет использовать переменную prefix, которая была объявлена не в ее контексте, а в более в контексте более высокого уровня, Это называется замыкание, то есть функция за- мкнулась на что-то. Конечно, поскольку Go — язык со сборщиком мусора, переменная prefix не удалится сразу, как мы вернем значение, а будет существовать до тех пор, пока кто-то использует эту перемен- ную. Давайте посмотрим, как это работает. Когда мы вызываем функцию prefixer, передавая туда строку 18 SUCCESS, то в successLogger записывается функция, принимающая на вход строку и печатающая её с пре- фиксом SUCCESS. После вызова successLogger(”expectedbehaviour”), как и ожидалось, будет выведено [SUCCES] expectedbehaviour.
Теперь поговорим про такие понятия, как отложенное выполнение, паника и восстановление после па- ники. В Go есть возможность отложенного выполнения функции. Это значит, что какая-то работа будет выполнена после завершения функции. Чаще всего этот подход используется, когда вам нужно посчи- тать, например, время работы функции, либо закрыть какой-то ресурс, например, сетевое соединение либо файловый дескриптор. Причем, если у вас много условий и много returns, в каждом из них руками прописывать это бывает не очень удобно и довольно утомительно. Для этого в Go есть отложенное вы- полнение — вы можете объявить один раз отложенное выполнение на закрытие какого-то ресурса - это делается сразу же после открытия - и забыть о нем. Давайте посмотрим, как это работает.
У нас есть функция main. Вначале мы определяем, что нужно выполнить какую-то функцию отложенно, то есть не сейчас, а в конце. Определяется это через ключевое слово defer, и непосредственно описание вы- зова. При запуске строка Some useful work выведется раньше, чем after work. Если используется несколько отложенных функций, то они выполняются в порядке, обратном их объявлению. В данном случае сначала выполнится функция fmt.Println(getSomeVars()), а потом fmt.Println(”Afterwork”) . Cтоит обратить отдельное внимание на аргументы, которые передаются в отложенные функции. Дело в том, что аргументы, передеваемые в отложенные функции, вычисляются на момент их объявления.Поэтому у нашей программы вывод будет такой: getSomeVars execution
Some userful work
getSomeVars result
After work
Потому что сначала выполнилась GetSomeVars, потому что результат её выполнения является аргументом отложенной функции. Потом выполнилась какая-то полезная работа, потом вывелся результат, который вернула нам функция, GetSomeVars, и потом вызвался первый блок defer. Иногда такое поведение бывает не очень удобно, поэтому чаще используется подход, когда мы объявляем анонимную функцию, которая должна быть вызвана в конце.
Конструкции с defer очень полезны при восстановлении из паники. Сначала давайте рассмотрим, что такое вообще паника. Представьте, у нас есть какая-то функция, defer test. Она делает какую-то работу и потом паникует.
Паника это служебная функция, которая останавливает выполнение работы программы, то есть вся про- грамма крашится. Как это работает? При выполнении данной функции выполнится какая-то полезная работа, потом вызовется паника и напечатается стек трейс. На самсом деле аника штука не очень хорошая, и завершать работающий демон в продакшене, когда обрабатывается много параллельных запросов, не очень хорошо. Паника — это абсолютно исключительная ситуация. И паники надо как-то отлавливать. Отлавливаются они с помощью блока defer. Сначала необходимо объявить, собственно, блок, defer, который будет выполнен в любом случае после завершения функции, Внутри блока надо воспользоваться функцией recover, которая возвращает ошиб- ку, выброшенную последней паникой.
Получаем ошибку, если она вообще была, если err != nil, и обрабатываем её. Теперь программа будет завершаться корректно. Это может показаться немножко похожим на блок try-catch. Но следует особо отметить, что никогда не следует использовать панику и восстановление после паники как эмуляцию блока try-catch. Они для этого не предназначены. Паника это абсолютная исключительная ситуация, которая обычно приводит к краху программы. Она ловится для того, чтобы другие запросы, которые выполняются этой программой, не затронулись. Естественно, иногда бывает, что функция в defer случайно тоже упали в панику.
Другие функции, которые выполняются позже, могут поймать эту панику, но вообще бросать панику в восстановлении очень плохая практика. Так работает паника. Пользуйтесь ею с осторожностью!
Утверждение "defer" вызывает функцию, выполнение которой откладывается до момента, когда окружающая функция возвращается, потому что выполнила утверждение return, либо когда достигла конца своего тела функции, либо когда соответствующая go-процедура (goroutine) запаниковала.
Выражение должно быть вызовом функции или метода; оно не может быть заключено в скобки. Вызовы встроенных функций ограничены как для утверждений выражений.
Каждый раз, когда выполняется утверждение "defer", значение функции и параметры для вызова оцениваются как обычно и сохраняются заново, но фактическая функция не вызывается. Вместо этого отложенные функции вызываются непосредственно перед возвратом окружающей функции в обратном порядке. То есть, если окружающая функция возвращается через явное return утверждение, отложенные функции выполняются после того, как этот return утверждение установит какие-либо параметры результата, но до того, как функция вернется к своему вызывающему. Если значение отложенной функции оценивается как nil, выполнение вызывает панику при вызове функции, а не при выполнении утверждения "defer".
Например, если отложенная функция является литералом функции, а окружающая функция имеет именованные параметры результата, которые находятся в области видимости внутри литерала, отложенная функция может получить доступ к параметрам результата и изменить их, прежде чем они будут возвращены. Если отложенная функция имеет какие-либо возвращаемые значения, они отбрасываются после завершения функции.