• В стандартной библиотеке 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)
    Ответ написан
    Комментировать
  • Как можно повторить на своем сайте 3D-анимацию с чужого сайта?

    1. Разобраться как работает WebGL, как работают вертексные буферы, как работают шейдеры.
    2. Написать на WebGL вертексный шейдер, который интерполирует позицию между координатами из двух буферов. Таким образом можно будет передать в шейдер два буфера и параметр плавного перехода между ними (число между 0 и 1).
    3. Добавить в шейдер управление цветом (на указанном сайте чем точка дальше от камеры, тем она ближе к серому по цвету).
    4. Добавить в шейдер еще один числовой параметр для случайного отклонения точек от своих координат для эффекта "разваливания" модели при действиях мыши.
    5. Собрать весь пайплайн рендеринга, рендеря точки как спрайты с текстурой или же генерируя текстуру кружка во фрагментном шейдере (тут надо посмотреть, что из этого будет быстрее работать).
    6. Написать программу, которая распределит N точек случайным образом внутри нескольких предоставленных моделях и сохранит данные всех расположений точек в файлы, которые будет потом грузить веб-приложение. Можно использовать Blender и геоноды там, у них есть уже готовая функция заполнения модели точками. Главное потом сохранить в таком формате, чтобы было удобно читать в вебе.
    7. Собрать все компоненты вместе, чтобы при скролле и движениях мыши отправлялись нужные параметры в шейдер (две текущие модельки, бленд между ними, случайное отклонение точек, углы камеры)
    Ответ написан
    Комментировать
  • Рисование полигонами поверх полигонов?

    Это можно сделать в любом редакторе, который умеет в ретопологию, например Blender. Включается магнитиком вверху.

    https://www.youtube.com/watch?v=OY7FlJ8xTz0
    Ответ написан
    3 комментария
  • Как настроить пробелы в автоформатировании при арифметических операциях?

    Автоформатирование тут делает не VSCode, а утилита gofmt, VSCode просто ее запускает. Возможности изменить стиль форматирования у gofmt нет, потому что Го по своей идеологии форсит единый стандарт форматирования кода.
    Ответ написан
    Комментировать
  • 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.
    Ответ написан
    Комментировать