在 golang 中联合接口和类型

Unioning an interface and type in golang

我正在尝试在 Golang 中实现一些缓存功能,但我希望它们对实现 Stringer 接口的字符串和其他对象都有效。我正在尝试使用 Golang 泛型,这是我目前所拥有的:

import (
    "fmt"
)

type String interface {
    ~string | fmt.Stringer
}

然而,这给出了一个错误cannot use fmt.Stringer in union (fmt.Stringer contains methods)。有没有办法在不依赖反射或类型的情况下做到这一点 boxing/unboxing?

泛型——理论上允许使用多种类型——在编译时确定了一个具体类型。接口在 运行时 允许多种类型。您希望同时将这两者结合起来 - 不幸的是,这是不可能的。


不使用反射最接近的是使用运行时类型断言:

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

由于类型参数提议 suggests code like yours,可能会造成混淆,但最终成为 Go 1.18 中的实现限制。

它在 specs 和 Go 1.18 发行说明中提到。规格为规范参考:

Implementation restriction: A union (with more than one term) cannot contain the predeclared identifier comparable or interfaces that specify methods, or embed comparable or interfaces that specify methods.

对于 Go 1.18 版本中未包含此功能的原因,还有一些广泛的 explanation。 tl;dr 简化了联合类型集的计算(尽管在 Go 1.18 中,类型参数的方法集也没有隐式计算......)。

还要考虑,无论有没有这个限制,除了将 T 传递给使用反射的函数之外,您可能不会获得任何有用的东西。要在 ~string | fmt.Stringer 上调用方法,您仍然需要 type-assert 或 type-switch.

注意,如果这种约束的目的只是为了打印字符串值,你可以只使用fmt.Sprint,它使用了反射。

对于更广泛的情况,当参数可以采用 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.String()
    }

    panic("invalid type")
}

游乐场:https://go.dev/play/p/-wzo2KPKzWZ