Я использую пакет Zerolog, и у меня есть несколько типов с теми же методами. https://github.com/rs/zerolog/blob/master/event.go#L752https://github.com/rs/zerolog/blob/master/context.go#L393
Я пытаюсь написать функцию, которая может вызывать метод «Интерфейс» для обоих типов:
package main
import (
"github.com/google/uuid"
"github.com/rs/zerolog"
"os"
)
func main() {
logger := setKeyVal(zerolog.New(os.Stdout).With(), "test_global_uuid", uuid.New().String()).Logger()
setKeyVal(logger.Info(), "msg_key", 123).Msg("Test log message")
}
func setKeyVal[T *zerolog.Event | zerolog.Context](zLog T, key string, val interface{}) T {
return zLog.Interface(key, val)
}
Но я получаю ошибку:
zLog.Interface undefined (type T has no field or method Interface)
Этот код работает, если я создаю две функции без дженериков:
package main
import (
"github.com/google/uuid"
"github.com/rs/zerolog"
"os"
)
func main() {
logger := setKeyValContext(zerolog.New(os.Stdout).With(), "test_global_uuid", uuid.New().String()).Logger()
setKeyValEvent(logger.Info(), "msg_key", 123).Msg("Test log message")
}
func setKeyValEvent(zLog *zerolog.Event, key string, val interface{}) *zerolog.Event {
return zLog.Interface(key, val)
}
func setKeyValContext(zLog zerolog.Context, key string, val interface{}) zerolog.Context {
return zLog.Interface(key, val)
}
Как я могу использовать одну и ту же функцию для разных типов?
🤔 А знаете ли вы, что...
Одной из фишек Go является многопоточность через горутины (goroutines) и каналы (channels).
Используя универсальную функцию в go, вы можете вызвать любой метод, объявленный в ограничении типа, и применить любой оператор, который поддерживается каждым типом в ограничении (например, вы можете написать func [T int|float] F(x, y T) T { return x + y }
). Но вы не можете вызвать метод или получить доступ к полю структуры, даже если каждый тип в ограничении имеет этот метод или поле структуры.
В вашем случае решение простое — просто добавьте в ограничение метод, который вы вызываете.
Вот минимальное воспроизведение вашей проблемы с исправлением. Ссылка на игровую площадку
package main
import "fmt"
type A struct {
}
func (a A) F() A {
fmt.Println("A.F()")
return a
}
type B struct {
}
func (b B) F() B {
fmt.Println("B.F()")
return b
}
func method[T interface {
F() T // add the method you want to call here!
A | B
}](x T) T {
return x.F()
}
func main() {
fmt.Printf("%#v %#v\n", method(A{}), method(B{}))
}
Редактировать: Хотя этот ответ работает, ответ @PaulHankin гораздо более элегантен. Соответственно скорректированный пример может быть Run on Playground
Я просто оставлю свой ответ в силе, потому что иногда другой подход может помочь лучше понять проблему.
Здесь вы упускаете один момент: вы ожидаете не конкретных типов, а на самом деле интерфейса, который выглядит так
type interfacer[T any] interface {
Interface(k string, v any) T
}
Другими словами: вы ожидаете, что T будет типом, имеющим функцию Interface(k string, v any)
, которая возвращает значение исходного типа.
Насколько мне известно, Go не имеет возможности вывести это из вашего кода во время компиляции, поэтому вам нужно выразить это явно: Run on Playground
package main
import (
"os"
"github.com/google/uuid"
"github.com/rs/zerolog"
)
type interfacer[T any] interface {
Interface(k string, v any) T
}
func setKeyVal[T interfacer[T]](zLog T, key string, val interface{}) T {
return zLog.Interface(key, val)
}
func main() {
logger := setKeyVal(zerolog.New(os.Stdout).With(), "test_global_uuid", uuid.New().String()).Logger()
logger.Info().Msg("Works with context")
setKeyVal(logger.Info(), "msg_key", 123).Msg("Works with event")
}
Самый простой способ — ограничить параметр типа тем, что вам нужно. Единственное, что вам нужно от типа T
вашего zlog
параметра, это возможность вызывать Interface(key string, i interface{}) T
для него, поэтому ограничить его с помощью interface { Interface(key string, i interface{}) T }
— это все, что вам нужно ( Go Playground):
package main
import (
"os"
"github.com/google/uuid"
"github.com/rs/zerolog"
)
func main() {
logger := setKeyVal(zerolog.New(os.Stdout).With(), "test_global_uuid", uuid.New().String()).Logger()
setKeyVal(logger.Info(), "msg_key", 123).Msg("Test log message")
}
func setKeyVal[T interface {
Interface(key string, i interface{}) T
}](zLog T, key string, val interface{}) T {
return zLog.Interface(key, val)
}
Указывать *zerolog.Event | zerolog.Context
нет никакой пользы.
По сути, это то, что делает Маркус В. Мальберг, но не определяет interfacer
интерфейс.