Задать вопрос
Ответы пользователя по тегу Go
  • Почему присвоение значения переменной не считается использованием переменной?

    Потому что вы должны хотя бы раз прочитать значение переменной. Во всех случаях, которые вы приводите, где ошибки нет — значение переменной где-то читается.
    Это сделано, чтобы исключить ряд багов, которые может допустить программист (например, зашедоуить переменную из скоупа выше и присвоить значение во временную переменную вместо оригинальной переменной).
    Ответ написан
    3 комментария
  • Почему так работают интерфесы в Го?

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

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

    Кстати, не рекомендую называть интерфейсы ISomething, это не принято в Го.
    Ответ написан
    Комментировать
  • Как исправить ошибку "cannot download, $GOPATH must not be set to $GOROOT"?

    1. Убедиться, что го установлен не в ~/go и что $GOPATH не смотрит на место его установки
    2. Убедиться, что код проекта находится не в ~/go или вложенных в него папках и не в $GOPATH и вложенных в него папках.
    3. Убедиться, что зависимости в проекте управляются через модули (что сделан go mod init)
    Ответ написан
    Комментировать
  • Golang нужно делать реконнект к дб или поднимать новое соединение?

    Правильная практика — это использовать пул готовых установленных соединений к базе и раскидывать запросы между ними. Почти все библиотеки для работы с БД делают именно так.

    https://go.dev/doc/database/manage-connections
    Ответ написан
    2 комментария
  • Можно ли в структуре указать тип данных отличный от того что лежит в базе данных?

    Хорошей практикой является принимать данные из БД в структуру, которая соответствует по типам тому, что лежит в БД.

    Если вам нужно конвертировать потом эти данные, конвертируйте их в другую структуру, которая расположена на слое бизнес-логики, например.
    Ответ написан
    2 комментария
  • Как под капотом реализованы интерфейсы в go?

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

    При вызове метода гошка на рантайме по таблице находит место в памяти, где этот метод хранится и вызывает его. Причем, первый вызов будет подольше, а следующие вызовы уже быстрее из-за кэширования.
    Ответ написан
    Комментировать
  • Как правильно обрабатывать ошибку в go?

    Для таких случаев в пакете errors есть методы Is и As

    Is проверяет, что указанная ошибка соответствует возвращенной (включая возможность вложенности ошибок)

    As может заполнять структуру ошибки при соответствии типа (тоже включая возможную вложенность)

    https://go.dev/play/p/3j9O079sj97

    Развернуть код
    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func main() {
    	err := someFunc()
    	{
    		keyExistsErr := &KeyExistError{}
    		if errors.As(err, keyExistsErr) {
    			fmt.Println("Мы получили ttl:", keyExistsErr.ttl)
    		}
    	}
    }
    
    type KeyExistError struct {
    	ttl string
    }
    
    func (e KeyExistError) Error() string {
    	return fmt.Sprint("Отправить код нельзя раньше чем через ", e.ttl, " сек.")
    }
    
    func someFunc() error {
    	return KeyExistError{
    		ttl: "4",
    	}
    }


    Причем, оно будет работать даже если обернуть ошибку через %w или через errors.Wrap, итд...
    https://go.dev/play/p/x2b_AZKI43t

    Развернуть код

    package main
    
    import (
    	"errors"
    	"fmt"
    )
    
    func main() {
    	err := someFunc()
    	{
    		keyExistsErr := &KeyExistError{}
    		if errors.As(err, keyExistsErr) {
    			fmt.Println("Мы получили ttl:", keyExistsErr.ttl)
    		}
    	}
    }
    
    type KeyExistError struct {
    	ttl string
    }
    
    func (e KeyExistError) Error() string {
    	return fmt.Sprint("Отправить код нельзя раньше чем через ", e.ttl, " сек.")
    }
    
    func someFunc() error {
    	err := someOtherFunc()
    	if err != nil {
    		return fmt.Errorf("error calling someOtherFunc: %w", err)
    	}
    	return nil
    }
    
    func someOtherFunc() error {
    	return KeyExistError{
    		ttl: "4",
    	}
    }

    Ответ написан
  • Как найти утечку памяти?

    В го автоматическое управление памятью, поэтому утечек памяти в классическом смысле быть не может (если не использовать пакет unsafe). Но могут быть утечки горутин (когда вы запускаете горутины, но они не завершаются, а блокируются на каком-то io или мьютексе/канале/...)

    Такое легко ищется с помощью pprof. Добавьте в свою программу веб-интерфейс pprof-а

    Для этого запустите любой http-сервер из стандартной библиотеки и добавьте импорт import _ "net/http/pprof"

    Например так:
    import (
      "net/http"
      _ "net/http/pprof"
    )
    
    ...
    
    func main() {
      ...
      http.ListenAndServe("localhost:8080", nil)
    }


    После этого при запуске программы у вас должен открываться веб-интерфейс pprof-а по адресу 127.0.0.1:8080/debug/pprof

    Дождитесь, когда накопятся утечки и откройте страницу 127.0.0.1:8080/debug/pprof/goroutine?debug=1

    На ней будет список всех работающих горутин и их количество. Найдите группу с самым большим количеством, она и утекает. По стеку посмотрите, где горутина блокируется, тогда поймете, почему они накапливаются.
    Ответ написан
    3 комментария
  • Предложения по оптимизации названий пакетов в Golang?

    Если используете глобальные пакеты (не советую, кстати, лучше dependency injection использовать), то зачем вам там второй уровень вложенности?
    Выводите функции в этих пакетах в корень и тогда будет:
    logger.Setup("log.log")
    database.Connect()
    settings.Setup("options.json")
    Ответ написан
    2 комментария
  • Как решить проблему X does not implement Y при работе с интерфесами?

    По-хорошему, у вас репозиторий не должен никогда возвращать интерфейс. В Го принято возвращать конкретный тип, а не интерфейс. https://github.com/golang/go/wiki/CodeReviewCommen...

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

    Если сказать по-другому: в domain-пакетах у вас не должно быть импортов каких-то структур из пакета БД, только наоборот — из пакета БД импортить типы из domain.
    Ответ написан
    Комментировать
  • Как установить пакет в Golang?

    Инструкция у пакета супер-устаревшая. В Го уже давно стандартом считается использование модулей.
    Проекты теперь нельзя класть в GOPATH, нужно использовать другие папки.
    Перед началом работы над проектом вам нужно инициализировать модули через go mod init имя_проекта, именем обычно бывает путь к репозиторию проекта, например: go mod init github.com/myuser/someproject

    После этого можно устанавливать нужные вам зависимости через go get github.com/go-sql-driver/mysql@latest. Вместо latest можно указывать необходимую вам версию пакета.
    Ответ написан
    Комментировать
  • Как лучше всего замапить запрос в структуру?

    Для слоя базы данных лучше всего иметь отдельные структуры, из которых потом данные перекладывать в слой бизнес-логики. Причем, лучше всего чтобы перекладыванием занимался пакет, ответственный за базу данных.
    Для слоя АПИ тоже лучше свои отдельные структуры, в них перекладывать должен слой АПИ.
    Слой бизнес-логики (модель) должен быть чистым от любых транспортных имплементаций и не импортить никакие пакеты базы или АПИ. Это наоборот, база и АПИ должны импортить в себя структуры бизнес-слоя, чтобы их возвращать и принимать.
    Короче, это я кратко про букву D в слове SOLID.
    Ответ написан
    2 комментария
  • Как добавить к концу строки \r?

    Так как либа для записи требует интерфейс io.Writer, вы легко можете создать свой прокси-райтер, который будет при встрече каждого \n добавлять еще и \r

    В самой примитивной реализации это будет выглядеть так:
    package main
    
    import (
    	"bytes"
    	"io"
    	"os"
    
    	"github.com/olekukonko/tablewriter"
    )
    
    func main() {
    	data := [][]string{
    		{"1/1/2014", "Domain name", "2233", "$10.98"},
    		{"1/1/2014", "January Hosting", "2233", "$54.95"},
    		{"1/4/2014", "February Hosting", "2233", "$51.00"},
    		{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
    	}
    
    	w := New(os.Stdout)
    
    	table := tablewriter.NewWriter(w)
    	table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
    	table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
    	table.SetBorder(false)                                // Set Border to false
    	table.AppendBulk(data)                                // Add Bulk Data
    	table.Render()
    }
    
    type ClrfAdder struct {
    	writer io.Writer
    }
    
    func New(w io.Writer) *ClrfAdder {
    	return &ClrfAdder{
    		writer: w,
    	}
    }
    
    func (c ClrfAdder) Write(p []byte) (n int, err error) {
    	replaced := bytes.ReplaceAll(p, []byte("\n"), []byte("\r\n"))
    	n, err = c.writer.Write(replaced)
    	if err != nil {
    		if n > len(p) {
    			n = len(p)
    		}
    		return n, err
    	}
    	return len(p), nil
    }


    Но можно использовать готовые стрим-реплейсеры, например https://github.com/icholy/replace
    Ответ написан
  • Как сделать болле корректно даный цикл?

    Вполне нормальное у вас решение. Разве что можно сделать так:
    func divide(x ...float64) float64 {
      if len(x) == 0 {
        return 0
      }
      if len(x) == 1 {
        return x[0]
      }
      a := x[0]
      for _, v := range x[1:] {
        a /= v
      }
      return a
    }
    Ответ написан
    1 комментарий
  • Можно ли изменять неимпортируемые поля структуры в других пакетах программы?

    Не понял, в чем проблема. Есть у вас пакет cache, внутри тип Cache и функция New

    Так как функция New экспортируемая, вызываете ее из любого пакета и создаете себе экземпляр кэша. Потом этот экземпляр через dependency injection кладете во все нужные вам экземпляры сервисов.
    Ответ написан
    Комментировать
  • Какую технологию выбрать для live трансляции go?

    Проще всего взять за основу ffmpeg, он очень гибкий есть куча примеров, как на его основе делать трансляции через протокол HLS.

    Go тут по сути будет использоваться как оркестратор для запуска ffmpeg и предоставления доступа к hls-кускам видео и плейлистам.

    В свое время ковырял эту связку, в целом так программу для трансляций можно собрать за день.
    Ответ написан
    2 комментария
  • Отправка загруженной фото в боте -> пользователю?

    Есть минимум два способа
    1. Выгрузить фото в файл или s3 и дать на него урл
    2. Выгрузить фото в качестве сырых данных в файл или в память и отправить эти данные другому пользователю через sendPhoto


    Если правильно помню, просто переслать файл-айди не получится, потому что у Телеги файл-айди разный для разных пользователей и ботов.
    Ответ написан
    Комментировать
  • Как использовать GOOS в exec.Command?

    Способ из инета костыльный, по-хорошему нужно делать так:

    cmd := exec.Command("go", "build", ".")
    cmd.Env = os.Environ()
    cmd.Env = append(cmd.Env, "GOOS=windows")
    Ответ написан
    3 комментария
  • Как выйти из go функции, которая в функции?

    Должен подойти такой вариант
    func start() {
    	file, _ := os.ReadFile("./data.txt")
    
    	for _, i := range strings.Split(string(file), "\n") {
    		select {
    		case <-stop:
    			return
    		default:
    			http.Get(Url + i)
    		}
    	}
    }


    На каждой итерации цикла проверяем, было ли сообщение в канал, если не было, обрабатываем очередной урл. Минус в том, что проверяем мы только между обработками урлов. Если обработка одного урла это пара секунд, то все норм.

    Более сложный вариант

    package main
    
    import (
    	"context"
    	"errors"
    	"io/ioutil"
    	"net/http"
    	"strings"
    )
    
    func start() {
    	file, _ := os.ReadFile("./data.txt")
    
    	ctx, cancel := context.WithCancel(context.Background())
    	defer cancel() // контекст обязательно надо отменять в конце
    
    	go func() {
    		select {
    
    		// Отменяем контекст при получении стоп-сигнала
    		case <-stop:
    			cancel()
    
    		// чтобы горутина завершилась по отмене контекста даже если stop не будет
    		case <-ctx.Done():
    		}
    	}()
    
    	for _, i := range strings.Split(string(file), "\n") {
    		err := processUrl(ctx, Url+i)
    		if err != nil {
    			if errors.Is(err, context.Canceled) {
    				return
    			}
    			// обработать ошибку
    		}
    	}
    }
    
    func processUrl(ctx context.Context, finalUrl string) error {
    	req, err := http.NewRequestWithContext(ctx, "GET", finalUrl, nil)
    	if err != nil {
    		return err
    	}
    	res, err := http.DefaultClient.Do(req)
    	if err != nil {
    		return err
    	}
    	defer res.Body.Close() // обязательно, иначе будет утечка
    
    	// обработать ответ
    
    	return nil
    }



    Здесь уже выход будет мгновенный, потому что http-запрос отменится по контексту хоть на середине.
    Ответ написан