> For the complete documentation index, see [llms.txt](https://docs.golang.org.ua/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.golang.org.ua/funkcii.md).

# Функции

Итак, функции. Они же процедуры. Признаюсь, я потратил некоторое время на размышления о выборе подходящего места для этой главы. По моей мысли, разобравшись с указателями,  мы именно теперь подготовлены к наиболее полному освещению этой темы. Так, камешек к камешку, выводится кладка здания нашей книги.&#x20;

## Дефиниция

Что в программировании называют функцией? Вовсе не пустой вопрос. В математике, к примеру, функцией называют в широком смысле зависимость некоторых значений от некоторых аргументов. В программировании же функции - это логически завершённые блоки кода, выполняющие определённую работу. Они используются в первую очередь с целью избежания повторения однотипного кода в теле программы. Перечень переменных и их типы, которые принимает функция называются параметрами функции. Значения, присваиваемые параметрам, называются аргументами. Имена параметров и аргументов могут не совпадать, главное - это соблюдение типа. При этом функция может вовсе не использовать аргументов и не возвращать никаких значений. Сумма таких понятий, как имя функции, перечень её параметров и возвращаемых значений называется сигнатурой функции. В связи с тем, что в некоторых языках  программирования существует так называемый полиморфизм функций, определение сигнатуры может в определённых случаях отличаться от приведенного выше. Таким образом функции - это в первую очередь элементы хорошо структурированного кода. Необходимо понимать, что большинство задач в вашей программе может быть выполнено и без применения хотя бы единственной функции. Однако, непременным свойством хорошего кода является выделение логически завершённых блоков и оформление их в виде функций. Это сродни хорошей литературе  - когда мы сперва ясно осознаём и формулируем свои мысли, а затем складываем их в абзацы и предложения, придавая тексту структуру.

Между прочим, мы уже имели возможность косвенно познакомиться по крайней мере с одной функцией! Эта функция - **main** - точка входа в каждую нашу программу.

```go
func main() {
}
```

## Особенности

Какие особенности присущи функциям в языке Go? Таких особенностей две. Первая из них - это возможность возвращения не одного, а нескольких значений, не прибегая к дополнительным инструментам, наподобие модификаторов параметров **out** в C#. Вторая особенность - это существование так называемых вариативных функций, то есть таких, которые могут принимать неопределённое число параметров. Но давайте обо всём по порядку. От простого - к сложному.

**Простейшая функция - без параметров и возвращаемого значения.**

Первое, что стоит рассмотреть, это простейшая функция, которая не имеет ни параметров, ни возвращаемых значений. Синтаксис такой функции будет состоять из ключевого слова **func,** имени функции, круглых скобок **()** и тела функции в фигурных скобках **{}.**

```
func nameOfFunc(){ // открывающая скобка в той же строке!
}
```

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

```go
package main

import (
	"fmt"
)

func printPifagorTable() {
	for i := 1; i < 10; i++ {
		for j := 1; j < 10; j++ {
			fmt.Print(i*j, "\t")
		}
		fmt.Println()
	}

}

func main() {
	printPifagorTable()

}
//Результат:
/*
1		2		3		4		5		6		7		8		9	
2		4		6		8		10	12	14	16	18	
3		6		9		12	15	18	21	24	27	
4		8		12	16	20	24	28	32	36	
5		10	15	20	25	30	35	40	45	
6		12	18	24	30	36	42	48	54	
7		14	21	28	35	42	49	56	63	
8		16	24	32	40	48	56	64	72	
9		18	27	36	45	54	63	72	81
*/
```

Нередко это бывает циклически выводимое меню:

```go
package main

import (
	"fmt"
)

func printMenu() {
	fmt.Println("Press a key to say:")
	m := map[int]string{
		1: "Hello!",
		2: "How do you do?",
		3: "Exit",
	}

	for i := 1; i <= len(m); i++ {
		fmt.Println(i, m[i])
	}

}

func main() {
	var input string
	for {
		printMenu()
		fmt.Scan(&input)
		switch input {
		case "1":
			fmt.Println("Hello!")
		case "2":
			fmt.Println("I`m great!")
		case "3":
			fmt.Println("Bye!")
		}
		if input == "3" {
			break
		}

	}
}
//Результат:
/*
Press a key to say:
1 Hello!
2 How do you do?
3 Exit
1
Hello!
Press a key to say:
1 Hello!
2 How do you do?
3 Exit
2
I`m great!
Press a key to say:
1 Hello!
2 How do you do?
3 Exit
3
Bye!
*/
```

**Функция, принимающая параметры и возвращающая значения**

Синтаксически, в круглых скобках здесь добавится один или несколько параметров. После круглых скобок последует возвращаемое значение. Наличие возвращаемого значения подразумевает введение оператора **return,** которым должны завершиться все ветви кода в теле функции.

```go
func nameOfFunc (i int, f float64) bool {
// some code here
return true
}
```

Если параметры однотипны, их можно перечислить через запятую, вот так:

```go
func nameOfFunc (a, b int, f float64) bool {
// some code here
return true
}
```

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

```go
func nameOfFunc (a, b int, f float64) (float64, error) {
// some code here
return 0.01, nil
}
```

Приведём пример. Пускай наша функция производит деление с защитой от деления на 0:

```go
package main

import (
	"errors"
	"fmt"
)

func division(x, y float64) (float64, error) {
	var err error
	if y == 0 {
		err = errors.New("division by zero error")
		return 0, err
	}

	return x / y, nil

}

func main() {
	fmt.Println(division(12, 5))
	fmt.Println(division(5, 0))
}
//Результат:
//2.4 <nil>
//0 division by zero error
```

Зачастую для обработки ошибок пишут отдельную функцию, чтобы избежать характерных для Go повторений блоков кода **if err != nil { //....**. Вот пример такой функции-обёртки:

```go
func failOnError(err error, msg string) {
  if err != nil {
    log.Fatalf("%s: %s", msg, err) // выход из программы при ошибке!
  }
}
```

Возвращаемые значения могут быть именованными. Синтаксически это выглядит так:

```go
func nameOfFunc (a, b int, f float64) (result float64, err error) {
// some code here
return result, err
}
```

Изменим соответствующим образом функцию деления:

```go
package main

import (
	"errors"
	"fmt"
)

func division(x, y float64) (result float64, err error) {
	if y == 0 {
		err = errors.New("division by zero error")
		return result, err
	}

	result = x / y

	return result, err

}

func main() {

	fmt.Println(division(12, 5))
	fmt.Println(division(5, 0))

}
//Результат:
//2.4 <nil>
//0 division by zero error
```

Использование именованных возвращаемых значений имеет определённый конкретный смысл и эффект. Будучи именованными, эти переменные изначально инициализируются значениями типа по умолчанию и сразу доступны в теле функции. Таким образом, нет необходимости объявлять дополнительные переменные. Но самый главный эффект проявляется в том случае, если нам нужно восстановиться после ошибки и всё же вернуть из функции некоторые значения. Об этом сейчас и поговорим.

## Defer

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

```go
package main

import (
	"fmt"
)

func helloFriends() {
	fmt.Println("Hello, friends!")
}

func main() {
	defer helloFriends()
	fmt.Println("Hello, world!")
}
//Результат:
//Hello, world!
//Hello, friends!
```

Таких функций может быть несколько, тогда они выполняются по принципу "всплывания" снизу вверх (LIFO - last in, first out):

```go
package main

import (
	"fmt"
)

func deferFunc(s string) {
	fmt.Println(s)
}

func main() {
	defer deferFunc("In 1")
	defer deferFunc("In 2")
	defer deferFunc("In 3")

	fmt.Println("Hello, world!")
}
//Результат:
//Hello, world!
//In 3
//In 2
//In 1
```

Основных предназначений у этого приёма программирования  - два. Первое, это "брошенное" закрытие открытых соединений:

```go
package main

import (
	"log"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:80") // open connection
	if err != nil {
		log.Fatalf("net.Dial: %v", err)
	}
	defer conn.Close() // shure to close it later
}
```

Второе же применение  - восстановление (recover) после ошибки (exception). Приведём пример восстановления после ошибки, с возвращением параметров:

```go
package main

import (
	"fmt"
)

type commonResponse struct {
	status string
	result int
	error  bool
}

func printSliceElement(n int, slice []int) (c commonResponse) {

	defer func() { //анонимная функция, есть и такое!
		if r := recover(); r != nil {
			fmt.Println("Recovered in printSliceElement func, continue: ", r)
			c.status = "Failure"
		}

	}()

	c.status = "Succsess"
	c.result = slice[n]
	c.error = false

	return c

}

func main() {

	slice := []int{0, 1, 2, 3, 4}
	c := printSliceElement(4, slice)
	fmt.Println(c)
	c = printSliceElement(5, slice)
	fmt.Println(c)

}
//Результат:
//{Succsess 4 false}
//Recovered in printSliceElement func, continue:  runtime error: index out of range [5] with length 5
//{Failure 0 false}
```

Обратите внимание: без использования именованного возвращаемого значения (**c commonResponse**) было бы невозможно возвращение значений из **defer** функции при восстановлении после ошибки. Запомните этот пример. Это - классический способ обработки исключений в языке Go. Если вы хотите, чтобы ваша программа после критической ошибки продолжала работать - это как раз то, что вам нужно.

## Анонимные функции

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

```go
func(){
// do something here
}()
```

Что же, поздравляю, вы были весьма проницательны! У нас новая "нозологическая единица". Это - анонимная функция. Круглые скобки в конце означают выполнение кода "на месте". В простейшем виде выглядеть это будет так:

```go
package main

import (
	"fmt"
)

func main() {

	func() {
		fmt.Println("Anonymous function!")
	}()
}
//Результат:
//Anonymous function!
```

Чтобы сделать функцию более функциональной (масло масляное), в скобки можно поместить параметры:

```go
package main

import (
	"fmt"
)

func main() {

	res := func(i int) int {
		return i * i
	}(2)
	fmt.Println(res)
}
//Результат:
//4
```

Можно создать переменную - функцию:

```go
package main

import (
	"fmt"
)

var f = func(i int) int {
		return i * i
	}

func main() {
	
	fmt.Println(f(2))
}
//Результат:
//4
```

Нередко анонимные функции используются как go-подпрограммы (goroutines, горутины). Но об этом следует поговорить несколько позже, в разделе о многопоточности.

## Значения-функции

В последнем примере переменная **f** - представляет собой так называемое значение-функцию. Таким образом, функции (анонимные или нет), могут быть присвоены переменным. А ещё они могут быть, к примеру, переданы в другую функцию и возвращены из неё. Если функции возвращают функции, то они выстраиваются в так называемый «стек вызовов» по типу FIFO. Посмотрим, что из себя представляет переменная **f**:

```go
package main

import (
	"fmt"
)

var f = func(i int) int {
	return i * i
}

func main() {

	fmt.Printf("%T", f)
}
//Результат:
//func(int) int
```

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

```go
package main

import (
	"fmt"
)

var f = func(i int) int {
	return i * i
}

func voidFunc() {
}

func main() {

	fmt.Printf("%T", f)
	f = voidFunc
}
//Результат:
//ошибка компиляции!
//cannot use voidFunc (type func()) as type func(int) int in assignment
```

Нулевым значением типа функции является nil. Вызов нулевой функции приводит к аварийной ситуации. Значения-функции не являются сравниваемыми.

## Рекурсия

Вероятно, это одна из самых нелюбимых и запутанных тем, относительно разбора предмета "функции". Чаще всего, от использования рекурсии можно безболезненно воздержаться, что не может не радовать. Однако, существуют классические алгоритмы, которые решаются именно применением рекурсии, и поэтому я не вижу причин не применять её, если контекст программы соответствует этому. Что есть рекурсивная функция? Это функция, которая в процессе работы вызывает сама себя. Не будем углубляться здесь в академические дебри обработки рекурсивных структур данных. Ограничимся классическим примером рекурсии - вычислением факториала:

```go
package main

import (
	"fmt"
)

func factorial(n uint) uint {
	if n == 0 {
		return 1
	}
	return n * factorial(n-1)
}

func main() {

	fmt.Println(factorial(5))
}
//Результат:
//120
```

## Замыкания

Функцию, использующую переменные, определенные вне этой функции, называют замыканием. Замыкания и рекурсия считаются основными приёмами так называемого функционального программирования. Приведём простой пример:

```go
package main

import (
	"fmt"
)

var flag bool // глобальная переменная

func setFlagOn() {
	flag = true
}

func setFlagOff() {
	flag = false
}

func main() {
	setFlagOn()
	fmt.Println(flag)
	setFlagOff()
	fmt.Println(flag)

}
//Результат:
//true
//false
```

Замыканием также является функция, возвращающая другую функцию:

```go
package main

import (
	"fmt"
	"math/rand"
	"time"
)

func makeRandom() func() int {
	min := 0
	max := 100
	return func() (ret int) {
		rand.Seed(time.Now().Unix())
		ret = rand.Intn(max-min) + min
		return
	}
}

func main() {
	random := makeRandom()
	fmt.Println(random())

}
//Результат:
//рандомное целое число в диапазоне 0...100
```

## Вариативные функции

В числе особенностей функций в Go мы упоминали так называемую вариативность. Рассмотрим это явление поближе. Вариативность функции - это возможность принимать переменное количество однотипных параметров. Синтаксически это выражается многоточием (**...**) перед указанием типа:

```go
func f (i ...int){
}
```

Количество передаваемых параметров может быть от нуля (ничего не передаём) и до некоего множества. Важно помнить, что вариативным может быть только единственный параметр, либо же последний в перечне, если параметров несколько:

```go
func f (i ...int){ // хорошо
}

func f1 (b bool, i ...int){ // хорошо
}

func f2 (i ...int, b bool){ // ошибка 
}
//syntax error: cannot use ... with non-final parameter i
```

На деле тип вариативного параметра - это срез! Убедимся в этом:

```go
package main

import (
	"fmt"
)

func f(i ...int) {
	fmt.Printf("%T\n", i) //получить тип
	fmt.Println(len(i))
}

func main() {

	f()
	f(3, 5, 7)

}
//Результат:
//[]int
//0
//[]int
//3
```

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

```go
package main

import (
	"fmt"
	"time"
)

func sleep(duration ...int) {

	var timeout int

	if len(duration) == 0 {
		fmt.Println("No input, using default parameters.")
		timeout = 1
	} else {
		timeout = duration[0]
		fmt.Println("Input:", timeout)
	}

	fmt.Println("Waiting", timeout, "sec.")

	time.Sleep(time.Second * time.Duration(timeout))

}

func main() {

	sleep()
	sleep(5)

}
//Результат:
//No input, using default parameters.
//Waiting 1 sec.
//Input: 5
//Waiting 5 sec.
```

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

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

```go
package main

import (
	"fmt"
)

//будем складывать числа
func sum(numbers ...int) int {
	var i int = 0

	for _, val := range numbers {
		i += val
	}
	return i

}

func main() {

	fmt.Println(sum())
	fmt.Println(sum(2, 3))
	fmt.Println(sum([]int{2, 3})) //ошибка!
	//cannot use []int literal (type []int) as type int in argument to sum
	fmt.Println(sum([]int{2, 3}...)) // верное решение!	

}
//Результат:
//0
//5
//5
```

На этой ноте мы в общих чертах завершим тему о функциях. В добрый путь!

#### Раунд!
