Функции и несколько возвращаемых значений
Функции и несколько возвращаемых значений
Функции в Go - это не просто подпрограммы. Это первоклассные объекты с уникальными возможностями, включая множественный возврат значений.
Основы функций
Простая функция
func greet(name string) string {
return "Привет, " + name + "!"
}
// Использование
message := greet("Мир")
fmt.Println(message) // Привет, Мир!
Несколько параметров
func add(x int, y int) int {
return x + y
}
// Или короче, если типы одинаковые
func multiply(x, y int) int {
return x * y
}
Множественный возврат
Это killer-feature Go - функция может возвращать несколько значений:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль")
}
return a / b, nil
}
// Использование
result, err := divide(10, 2)
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // 5
Игнорирование значений
// Нужен только результат
result, _ := divide(10, 2)
// Нужна только ошибка
_, err := divide(10, 0)
Именованные возвращаемые значения
func getCoordinates() (x, y int) {
x = 10
y = 20
return // naked return
}
// Более читаемый вариант
func calculateStats(numbers []int) (min, max, avg int) {
if len(numbers) == 0 {
return // возвращает нулевые значения
}
min, max = numbers[0], numbers[0]
sum := 0
for _, n := range numbers {
if n < min {
min = n
}
if n > max {
max = n
}
sum += n
}
avg = sum / len(numbers)
return min, max, avg
}
Вариативные функции
Функции с переменным числом аргументов:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// Использование
fmt.Println(sum()) // 0
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// Передача слайса
numbers := []int{1, 2, 3}
fmt.Println(sum(numbers...)) // 6
Defer - отложенное выполнение
Базовое использование
func readFile(filename string) ([]byte, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close() // Выполнится при выходе из функции
// Читаем файл
return io.ReadAll(file)
}
Порядок выполнения defer
func deferOrder() {
defer fmt.Println("первый")
defer fmt.Println("второй")
defer fmt.Println("третий")
}
// Вывод:
// третий
// второй
// первый
Defer и аргументы
func deferArgs() {
i := 0
defer fmt.Println("defer:", i) // i захватывается сразу
i++
// Выведет: defer: 0
}
Функции как значения
В Go функции - это first-class citizens:
// Функция как переменная
var operation func(int, int) int
operation = add
fmt.Println(operation(5, 3)) // 8
operation = multiply
fmt.Println(operation(5, 3)) // 15
// Функция как параметр
func apply(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
result := apply(add, 10, 5) // 15
Анонимные функции и замыкания
Анонимные функции
// Немедленный вызов
func() {
fmt.Println("Анонимная функция")
}()
// Присваивание переменной
double := func(x int) int {
return x * 2
}
fmt.Println(double(5)) // 10
Замыкания (closures)
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
// Использование
c1 := counter()
c2 := counter()
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
fmt.Println(c2()) // 1
fmt.Println(c1()) // 3
Практические примеры
HTTP handler с замыканием
func makeHandler(prefix string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s: %s", prefix, r.URL.Path)
}
}
// Использование
http.HandleFunc("/api", makeHandler("API"))
http.HandleFunc("/web", makeHandler("WEB"))
Retry с экспоненциальной задержкой
func retryWithBackoff(fn func() error, maxRetries int) error {
var err error
for i := 0; i < maxRetries; i++ {
if err = fn(); err == nil {
return nil
}
if i < maxRetries-1 {
delay := time.Duration(math.Pow(2, float64(i))) * time.Second
time.Sleep(delay)
fmt.Printf("Попытка %d не удалась, жду %v\n", i+1, delay)
}
}
return fmt.Errorf("не удалось после %d попыток: %w", maxRetries, err)
}
Middleware паттерн
type Middleware func(http.HandlerFunc) http.HandlerFunc
func withLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Запрос: %s %s", r.Method, r.URL.Path)
next(w, r)
}
}
func withAuth(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next(w, r)
}
}
// Композиция
handler := withLogging(withAuth(myHandler))
Методы (методы структур)
Хотя это не совсем функции, методы тесно связаны с ними:
type Rectangle struct {
width, height float64
}
// Метод с value receiver
func (r Rectangle) Area() float64 {
return r.width * r.height
}
// Метод с pointer receiver
func (r *Rectangle) Scale(factor float64) {
r.width *= factor
r.height *= factor
}
// Использование
rect := Rectangle{10, 5}
fmt.Println(rect.Area()) // 50
rect.Scale(2)
fmt.Println(rect.Area()) // 200
Escape analysis: стек или куча
Компилятор сам решает, где разместить локальную переменную. Если значение не переживёт кадр функции - оно живёт на стеке (бесплатное освобождение при return). Если уезжает наружу (возврат указателя, замыкание, отправка в канал) - попадает в кучу, и потом подметает GC.
Посмотреть решения компилятора: go build -gcflags="-m".
Лучшие практики
<ComparisonTable data={{ headers: ["Практика", "Плохо", "Хорошо"], rows: [ ["Обработка ошибок", "result := must(divide(10, 0))", "result, err := divide(10, 0)\nif err != nil { ... }"], ["Именование", "func calc(x, y int) int", "func calculateSum(x, y int) int"], ["Defer для ресурсов", "file.Close() // в конце функции", "defer file.Close() // сразу после Open"], ["Возврат ошибок", "return errors.New("error")", "return fmt.Errorf("не удалось обработать %s: %w", file, err)"], ["Параметры", "func process(s1, s2, s3 string)", "func process(config ProcessConfig)"] ] }} />
Полезные паттерны
Functional options
type Server struct {
port int
timeout time.Duration
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func WithTimeout(timeout time.Duration) Option {
return func(s *Server) {
s.timeout = timeout
}
}
func NewServer(opts ...Option) *Server {
s := &Server{
port: 8080, // значения по умолчанию
timeout: 30 * time.Second,
}
for _, opt := range opts {
opt(s)
}
return s
}
// Использование
server := NewServer(
WithPort(9000),
WithTimeout(60 * time.Second),
)
Итоги
- Функции могут возвращать несколько значений
- Defer для гарантированного выполнения кода
- Функции - первоклассные объекты
- Замыкания захватывают внешние переменные
- Методы расширяют типы
В следующем уроке изучим слайсы - динамические массивы Go!
Типичная ошибка
Смешивать «что делаем» и «как печатаем»: пусть функция возвращает значение, а вывод - отдельно.