Ответы пользователя по тегу Go
  • В стандартной библиотеке go есть ли инструменты для работы с многомерными срезами/массивами?

    В стандартной библиотеке — нет. Но есть сторонние библиотеки вроде https://pkg.go.dev/gonum.org/v1/gonum/mat или https://github.com/james-bowman/sparse

    Если вы решаете задачи на алгоритмы, то вам лучше как раз без таких библиотек, которые в одну строчку делают задачу (потому что в библиотеке кто-то до вас уже написал агоритмы).

    Если же по работе нужно для решения проблемы, то берите одну из библиотек, которые я предложил выше.

    Ведь без инструментов, тут нужен iq минимум 150


    Это вам так кажется, потому что первый язык и вы только начинаете разбираться. На самом деле такие задачи без проблем решаются на чистом го.
    Ответ написан
    Комментировать
  • Почему я получаю ошибку invalid operation: err (variable of type Error) is not an interface при проверке типа переменной?

    https://go.dev/play/p/eDi3J3Zzcdg

    package main
    
    import "fmt"
    
    type MyError struct {
    	Message string
    }
    
    func (e MyError) Error() string {
    	return e.Message
    }
    
    func (e MyError) Smth() {
    }
    
    type SomeInterface interface {
    	Smth()
    }
    
    func main() {
    	var err SomeInterface
    	err = MyError{"Something went wrong"}
    
    	// Проверяем, реализует ли err интерфейс error
    	if _, ok := err.(error); ok {
    		fmt.Println("err реализует интерфейс error")
    	} else {
    		fmt.Println("err НЕ реализует интерфейс error")
    	}
    }


    У вас получилось, что тип переменной err это структура, но нужно чтобы тип был каким-нибудь интерфейсом.

    В моем примере я создал интерфейс SomeInterface и добавил вашему типу метод, чтобы он этому интерфейсу удовлетворял. Теперь данный ассершн имеет смысл.

    P.S. С пустым интерфейсом тоже работать будет https://go.dev/play/p/Yz0M1Wzopua
    Ответ написан
    Комментировать
  • Какой фреймворк для Go изучать в 2024 году?

    Если http:
    Взять какой-нибудь хороший роутер, например https://github.com/julienschmidt/httprouter
    И на его основе сделать врапперы и мидлвари под себя по необходимости.
    Если работаете в какой-то конторе, то там уже должен быть фреймворк для микросервисов, заточенный под инфраструктуру компании. Потому что если сервисы делаются все по-разному, это беда.

    Если grpc:
    Особых вариантов нет, нужно использовать protoc.
    При необходимости подключить плагины, чтобы был сваггер и гейтвей в http
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway
    github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2

    Почему не предлагаю какой-то большой конкретный фреймфорк? Если бы вы сказали, что веб-приложения будете писать, можно было бы посоветовать что-то типа gin, но вы сказали про микросервисы. С ними такая специфика, что надо как конструктор собирать себе стек под задачу, иначе будет неудобно.
    Ответ написан
    3 комментария
  • Как правильно настроить локальное окружение для веб-разработки на Go?

    Есть достаточно много утилит, которые могут следить за папкой с исходниками и пересобирать приложение.
    Например https://github.com/cosmtrek/air

    Ну и для разработки будет гораздо быстрее и удобнее запускать не в докере, билдя каждый раз контейтер, а использовать go run
    Ответ написан
    Комментировать
  • Как добавить метаданные в изображение?

    https://github.com/dsoprea/go-exif

    Есть такая библиотека, которая умеет править или создавать новые exif-блоки
    Ответ написан
    Комментировать
  • Кто нибудь применял или видел generics в серьезных Go проектах?

    Одно из самых полезных применений дженериков, которое у меня было — это написание врапперов, которые добавляют строгую типизацию в обработку чего-либо и уменьшают бойлерплейт. Например, обработка http-запросов. Обычно используется роутер и в него регистрируются хендлеры для разных путей, например:
    router := httprouter.New()
    
    router.POST("/api/products", productsHandler.Handle)
    router.POST("/api/get_free_slots", getFreeTimeSlotsHandler.Handle)
    router.POST("/api/create_visit", createVisitHandler.Handle)


    При этом, функция Handle у каждого хендлера это обычно что-то типа func(w http.ResponseWriter, r *http.Request)
    Хендлер уже внутри себя сам читает тело, парсит его из json, потом формирует ответ, итд.

    Хотелось добавить сюда типизацию и какую-то структуру, поэтому я написал
    простой враппер

    type Validatable interface {
    	Validate() error
    }
    
    func Wrap[Req Validatable, Res any](fn func(ctx context.Context, req Req) (Res, error)) httprouter.Handle {
    	handler := func(writer http.ResponseWriter, request *http.Request, _ httprouter.Params) {
    		writeError := func(statusCode int, err error) {
    			writer.WriteHeader(statusCode)
    			_, _ = writer.Write([]byte(err.Error()))
    		}
    
    		var req Req
    
    		bytes, err := io.ReadAll(request.Body)
    		if err != nil {
    			writeError(http.StatusBadRequest, err)
    			return
    		}
    
    		if err := json.Unmarshal(bytes, &req); err != nil {
    			writeError(http.StatusBadRequest, err)
    			return
    		}
    
    		if err := req.Validate(); err != nil {
    			writeError(http.StatusBadRequest, err)
    			return
    		}
    
    		resp, err := fn(request.Context(), req)
    		if err != nil {
    			writeError(http.StatusInternalServerError, err)
    			return
    		}
    
    		respBytes, err := json.Marshal(resp)
    		if err != nil {
    			writeError(http.StatusInternalServerError, err)
    			return
    		}
    		writer.WriteHeader(http.StatusOK)
    		_, _ = writer.Write(respBytes)
    	}
    
    	return handler
    }



    Он берет на себя весь бойлерплейт по парсингу и валидации запроса, формированию ответа, работы с кодами, итд. Причем, через стандартный errors.Is иногда добавлял сюда еще возможность из хэндлера указать врапперу, какой http-код ответа отдавать. Обычно все функции враппера делал под конкретную задачу в конкретном проекте (можно, например, не только json тела парсить, но и урл и хедеры, итд).

    Использование враппера выглядит так.
    Мы пишем хэндлер, используя конкретные типы

    type Handler struct {
    	userService *usersvc.Service
    }
    
    type Request struct {
    	UserID int64 `json:"user_id"`
    }
    
    func (c Request) Validate() error {
    	if c.UserID == 0 {
    		return errors.New("empty user id")
    	}
    	return nil
    }
    
    type Response struct {
    	Name string `json:"name"`
    	Age  int    `json:"age"`
    }
    
    func New(userService *usersvc.Service) *Handler {
    	return &Handler{userService: userService}
    }
    
    func (h Handler) Handle(ctx context.Context, req *Request) (*Response, error) {
    	user, err := h.userService.UserByID(ctx, req.UserID)
    	if err != nil {
    		return nil, err
    	}
    
    	return &Response{
    		Name: user.Name,
    		Age:  user.Age,
    	}, nil
    }



    Через dependency injection даем хэндлеру все источники данных, изолируем хэндлер в отдельном пакете, четко прописываем ему типы Request и Response, пишем валидацию для запроса. Открыв хэндлер, разработчик сразу видит его контракты, весь бойлерплейт во враппере, остается написать только логику.

    В итоге использование такого хэндлера+враппер выглядит так:
    getUserHandler := getuser.New(userSvc)
    
    router := httprouter.New()
    
    router.POST("/api/get_user", wrapper.Wrap(productsHandler.getUserHandler))


    Причем, тут даже не видно, что Wrap() имеет дженерик-параметры, потому что гошка самостоятельно выводит эти параметры из типов Request и Response из хэндлера и проверяет, что у Request есть метод Validate.

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

    Потому что вы должны хотя бы раз прочитать значение переменной. Во всех случаях, которые вы приводите, где ошибки нет — значение переменной где-то читается.
    Это сделано, чтобы исключить ряд багов, которые может допустить программист (например, зашедоуить переменную из скоупа выше и присвоить значение во временную переменную вместо оригинальной переменной).
    Ответ написан
    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
    Ответ написан