2 回答
TA贡献1853条经验 获得超6个赞
为什么泛型类型开关编译失败?
这其实是 Go 团队有意决定的结果。事实证明,允许参数化类型上的类型切换会导致混淆
在此设计的早期版本中,我们允许在类型为类型参数或类型基于类型参数的变量上使用类型断言和类型开关。我们删除了此功能,因为始终可以将任何类型的值转换为空接口类型,然后对其使用类型断言或类型切换。此外,有时令人困惑的是,在使用近似元素的类型集的约束中,类型断言或类型切换将使用实际类型参数,而不是类型参数的基础类型(差异在识别部分中解释)匹配的预声明类型)
来自类型参数提案
让我把强调的语句变成代码。如果类型约束使用类型近似(注意波浪号)...
func PrintStringOrInt[T ~string | ~int](v T)
...如果还有一个自定义类型int
作为基础类型...
type Seconds int
...如果PrintOrString()
使用Seconds
参数调用...
PrintStringOrInt(Seconds(42))
...那么switch
块不会进入,int case
而是直接进入default case
,因为Seconds
不是int
。开发人员可能希望它也case int:
与类型相匹配Seconds
。
要允许一个case
语句同时匹配Seconds
和int
将需要一个新的语法,例如,
case ~int:
在撰写本文时,讨论仍在进行中,也许会产生一个全新的选项来打开类型参数(例如switch type T
)。
更多细节请参考提案:spec: generics: type switch on parametric types
技巧:将类型转换为“任何”
幸运的是,我们不需要等待这个提议在未来的版本中得到实施。现在有一个超级简单的解决方法。
不是打开v.(type)
,而是打开any(v).(type)
。
switch any(v).(type) { ...
这个技巧转换v
成一个空的interface{}
(又名any
),很switch
高兴地为它进行类型匹配。
TA贡献1820条经验 获得超10个赞
*A[any]不匹配*A[int],因为any是静态类型,不是通配符。因此,用不同类型实例化一个泛型结构会产生不同的类型。
为了在类型转换中正确匹配泛型结构,您必须使用类型参数实例化它:
func Handle[T any](s interface{}) {
switch x := s.(type) {
case *A[T]:
x.DoA()
case *B[T]:
x.DoB()
default:
panic("no match")
}
}
尽管在没有其他函数参数的情况下 infer T,您将不得不调用Handle显式实例化。T不会仅从结构中推断出来。
func main() {
i := &A[int]{}
Handle[int](i) // expected to print "do A"
}
游乐场:https ://go.dev/play/p/2e5E9LSWPmk
但是,当Handle实际上是一种方法时,就像在您的数据库代码中一样,这具有在实例化接收器时选择类型参数的缺点。
为了改进这里的代码,您可以创建Handle一个顶级函数:
func Handle[T any](ctx context.Context, cmd CMD) error {
switch t := cmd.(type) {
case *TransactionalCommand[T]:
return handleTransactionalCommand(ctx, t)
default:
fmt.Printf("%T\n", cmd)
return ErrInvalidCommand
}
}
那么你就会遇到如何向db T命令函数提供参数的问题。为此,您可能会:
只需将一个附加*db参数传递给Handleand handleTransactionalCommand,这也有助于类型参数推断。调用为Handle(ctx, &db{}, tFn)。游乐场:https ://go.dev/play/p/6WESb86KN5D
传递一个实例CommandManager(如上面的解决方案但*db被包装)。更冗长,因为它需要在任何地方显式实例化。游乐场:https ://go.dev/play/p/SpXczsUM5aW
改用参数化接口(如下所示)。因此,您甚至不必进行类型切换。游乐场:https ://go.dev/play/p/EgULEIL6AV5
type CMD[T any] interface {
Exec(ctx context.Context, db T) error
}
- 2 回答
- 0 关注
- 133 浏览
添加回答
举报