Заметки по Go

Введение

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

Вопросы

Почему программа отработала не правильно, и как это исправить.

package main

import (
	"fmt"
)

type Person struct {
	FirstName string
	LastName  string
}

func (p Person) Married(husband Person) {
	p.LastName = husband.LastName
}

func main() {
	eva := Person{"Eva", "First"}
	adam := Person{"Adam", "Second"}
	eva.Married(adam)

	fmt.Println(eva)
}

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

Для ожидаемого поведения нужно изменить объявление функции: вместо

func (p Person) Married(husband Person)

написать

func (p *Person) Married(husband Person)

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

Почему программа работает не правильно, и как это исправить.

package main

import "fmt"

func main() {
	var actions []func()
	for _, dir := range [...]string{"one", "two", "three", "four"} {
		actions = append(actions, func() {
			fmt.Println(dir)
		})
	}

	for _, f := range actions {
		f()
	}
}

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

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

actions = append(actions, func() {
	fmt.Println(dir)
})

вставить

a := dir
actions = append(actions, func() {
	fmt.Println(a)
})

Таким образом при каждой итерауции будет инициализироваться новая область памяти, в которой будет значение dir.

Почему программа работает, не как ожидается и как это исправить:

package main

import (
	"fmt"
)
 
func showNumber(num int) {
	fmt.Println(num)
}

func main() {
	iterations := 10

	for i := 0; i <= iterations; i++ {
		go showNumber(i)
	}

	fmt.Println("Goodbye!")
}

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

Самый простой вариант добавить time.Sleep для паузы при завершении. Более правильный вариант это использование пакета sync.WaitGroup.

Код будет следующий:

package main

import (
	"fmt"
	"sync"
)


 
func showNumber(num int) {
	fmt.Println(num)
}

func main() {
	var wg sync.WaitGroup
	iterations := 10

	for i := 0; i <= iterations; i++ {
		wg.Add(1)
		go showNumber(i)
	}
	
	wg.Wait()
	fmt.Println("Goodbye!")
}

Мы создаем группу ожидающих горутин и ждем когда каждая из них пришлет сигнал о завершении.

Почему не выводится строка “do is ok”:

package main

import (
	"fmt"
)

type MyError struct {
	Reason string
}

func (e *MyError) Error() string {
	return e.Reason
}

func do() error {
	var err *MyError
	fmt.Println(err == nil)
	return err
}

func main() {
	if err := do(); err == nil {
		fmt.Println("do is ok")
	}
}

Интерфейс в Go состоит из 2-х элементов тип и значение. Функция do будет возвращать интерфейс не с типом nil, а с типом *MyError, т.е. (*MyError, nil)``, что не является nil, так как значение интерфейса равно nilтолько в том случае, если оба элемента равныnil, т. е. (nil, nil). Следовательно (*MyError, nil) != (nil, nil)```.

Полезные ссылки

  1. Pass by pointer vs pass by value in Go
  2. sync.WaitGroup
  3. Why is my nil error value not equal to nil
 
comments powered by Disqus