在Golang中将一个接口与一个类型合并

6

我正在尝试在Golang中实现一些缓存功能,但我希望它们对字符串和其他实现了Stringer接口的对象都有效。我正在使用Golang泛型进行尝试,目前为止我已经有了以下代码:

import (
    "fmt"
)

type String interface {
    ~string | fmt.Stringer
}

然而,这样会出现错误:无法在union中使用fmt.Stringer(fmt.Stringer包含方法)。有没有一种方法可以做到这一点,而不依赖于反射或类型装箱/拆箱?


类型字符串接口 { fmt.Stringer /n ~字符串 } - Para
2
@Para 我认为那样做不会起作用,因为这需要任何实现 String 的东西既继承自 string 又实现 fmt.Stringer - Woody1193
2个回答

5
混淆可能是有道理的,因为类型参数建议 像你的代码一样,但最终在 Go 1.18 中成为实现限制。
它在规范和Go 1.18版本说明中提到。 规范是规范性参考:

实现限制:联合(具有多个术语)不能包含预定义的标识符 comparable 或指定方法的接口,或嵌入 comparable 或指定方法的接口。

还有一个相当广泛的解释,说明为什么这不包括在Go 1.18中。 tl;dr 是简化联合类型集的计算(虽然在 Go 1.18 中,类型参数的方法集也不会隐式计算...)。
考虑到即使没有这个限制,你可能也不会获得任何有用的东西,除了将 T 传递给使用反射的函数。要在 ~string | fmt.Stringer 上调用方法,仍然需要进行类型断言或类型切换。
请注意,如果此约束的目的仅是打印字符串值,则可以使用 fmt.Sprint,它使用反射。
对于更广泛的情况,在参数可以采用不带 ~ 的精确类型如 stringfmt.Stringer 时,像 colm.anseo 的答案中那样进行类型断言或切换就可以正常工作。对于像 ~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


这是一个很好的问题解释。从我的角度来看,目标在你提出的修复和反射之间。我想要从任何实现了fmt.Stringer或字符串本身的类型生成一个字符串,同时保持强大的编译时类型检查,但似乎在go1.18中这是不可能的。无论如何,感谢您详细的答案。 - Woody1193
1
@Woody1193 未来可能会取消这个限制。我在 Go 问题跟踪器中链接的评论暗示了实现带有方法的联合是可行的。在 switch 中允许波浪线类型也会有所帮助,这也可能在未来被允许。在 Go 1.18 中,一切都很保守,以避免在出现关键设计缺陷的情况下造成太多混乱。对于类型安全性的替代方案可以是声明 stringLike 为未导出,并具有导出的访问器 String[S ~string](v S)Stringer(s fmt.Stringer),它们调用 stringLike - blackgreen
没错,这是一个很好的观点。 - Woody1193
1
提醒:如果你知道一个 reflect.Value 是字符串类型,那么可以直接使用 r.String() 方法获取字符串。 - colm.anseo

2

泛型在理论上可以使用多种类型,但是在编译时只能确定一种具体类型。接口可以在运行时支持多种类型。你想同时结合这两个特性,但很遗憾这是不可能的。


如果不使用反射,最接近的方式将是使用运行时类型断言:

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泛型能够提供这种功能;也许它们会在以后的版本中实现。 - Woody1193

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接