Я пытаюсь реализовать некоторые функции кэширования в Golang, но я хочу, чтобы они были действительны как для строк, так и для других объектов, реализующих интерфейс Stringer
. Я пытаюсь сделать это, используя дженерики Golang, и это то, что у меня есть до сих пор:
import (
"fmt"
)
type String interface {
~string | fmt.Stringer
}
Однако это дает ошибку cannot use fmt.Stringer in union (fmt.Stringer contains methods)
. Есть ли способ сделать это, не полагаясь на отражение или тип бокса/распаковки?
@Para Я не думаю, что это сработает, потому что для этого потребуется, чтобы все, что реализует String
, наследовалось от string
и реализовывало fmt.Stringer
.
Обобщения, которые теоретически позволяют использовать множество типов, останавливаются на одном конкретном типе во время компиляция. Интерфейсы позволяют использовать несколько типов в время выполнения. Вы хотите совместить и то, и другое одновременно — к сожалению, это невозможно.
Самое близкое, что вы можете получить без использования отражения, - это использовать утверждение типа времени выполнения:
func StringLike(v any) string {
if s, ok := v.(string); ok {
return s
}
if s, ok := v.(fmt.Stringer); ok {
return s.String()
}
panic("non string invalid type")
}
https://go.dev/play/p/p4QHuT6R8yO
Это решение, к которому я тоже пришел. Я надеялся, что дженерики Golang предложат эту функциональность; возможно, они будут в более поздней версии.
Путаница может быть оправдана, потому что предложение параметров типа предлагает код, подобный вашему, однако, оказалось ограничением реализации в Go 1.18.
Он упоминается в характеристики и в примечаниях к выпуску Go 1.18. Спецификации являются нормативной ссылкой:
Implementation restriction: A union (with more than one term) cannot contain the predeclared identifier
comparable
or interfaces that specify methods, or embedcomparable
or interfaces that specify methods.
Существует также несколько обширный объяснение, почему это не было включено в выпуск Go 1.18. tl;dr упрощает вычисление наборов типов объединения (хотя в Go 1.18 наборы методов параметров типов также не вычисляются неявно...).
Учтите также, что с этим ограничением или без него вы, скорее всего, не получите ничего полезного, кроме передачи T
функциям, использующим отражение. Чтобы вызвать методы на ~string | fmt.Stringer
, вам все равно нужно ввести подтверждение или переключение типа.
Обратите внимание, что если целью такого ограничения является просто печать строкового значения, вы можете просто использовать fmt.Sprint
, который использует отражение.
В более широком случае утверждение типа или переключатель, как в ответе colm.anseo, отлично работает, когда аргумент может принимать точные типы, такие как string
(без ~
) и fmt.Stringer
. Для приближений вроде ~string
вы не можете исчерпывающе обрабатывать все возможные термины, потому что эти наборы типов практически бесконечны. Итак, вы вернулись к размышлению. Лучшей реализацией может быть:
func StringLike(v any) string {
// switch exact types first
switch s := v.(type) {
case fmt.Stringer:
return s.String()
case string:
return s
}
// handle the remaining type set of ~string
if r := reflect.ValueOf(v); r.Kind() == reflect.String {
return r.Convert(reflect.TypeOf("")).Interface().(string)
}
panic("invalid type")
}
Детская площадка: https://go.dev/play/p/rRD7QSNMkcz
Это отличное объяснение проблемы. С моей точки зрения, цель была где-то на полпути между предложенным вами исправлением и размышлением. Я хотел сгенерировать строку из любого типа, который реализует fmt.Stringer
или саму строку, сохраняя при этом строгую проверку типов во время компиляции, но похоже, что это невозможно с go1.18. В любом случае, спасибо за подробный ответ.
@ Woody1193, возможно, они снимут это ограничение в будущем. Комментарий, на который я дал ссылку из системы отслеживания проблем Go, намекает на то, что реализация союзов с методами выполнима. Разрешение типов тильды в переключателях также поможет, и это также может быть разрешено в будущем. В Go 1.18 все консервативно, чтобы избежать слишком больших проблем в случае критических недостатков дизайна. Альтернативой безопасности типов может быть объявление stringLike
неэкспортируемым и иметь экспортированные методы доступа String[S ~string](v S)
и Stringer(s fmt.Stringer)
, которые вызывают stringLike
Верно, и это хороший момент
интерфейс типа String { fmt.Stringer /n ~string }