[英]Generic type in a switch statement
Just started learning generics.刚开始学习generics。 I'm making a command processor and I honestly don't know how to word this so I'm just going to show an example problem:
我正在制作一个命令处理器,老实说,我不知道如何表达,所以我将展示一个示例问题:
var ErrInvalidCommand = errors.New("invalid command")
type TransactionalFn[T any] func(ctx context.Context, db T) error
func NewTransactionalCommand[T any](fn TransactionalFn[T]) *TransactionalCommand[T] {
return &TransactionalCommand[T]{
fn: fn,
}
}
type TransactionalCommand[T any] struct {
fn TransactionalFn[T]
}
func (cmd *TransactionalCommand[T]) StartTransaction() error {
return nil
}
func (cmd *TransactionalCommand[T]) Commit() error {
return nil
}
func (cmd *TransactionalCommand[T]) Rollback() error {
return nil
}
type CMD interface{}
type CommandManager struct{}
func (m *CommandManager) Handle(ctx context.Context, cmd CMD) error {
switch t := cmd.(type) {
case *TransactionalCommand[any]:
return m.handleTransactionalCommand(ctx, t)
default:
fmt.Printf("%T\n", cmd)
return ErrInvalidCommand
}
}
func (m *CommandManager) handleTransactionalCommand(ctx context.Context, cmd *TransactionalCommand[any]) error {
if err := cmd.StartTransaction(); err != nil {
return err
}
if err := cmd.fn(ctx, nil); err != nil {
if err := cmd.Rollback(); err != nil {
return err
}
}
if err := cmd.Commit(); err != nil {
return err
}
return nil
}
// tests
type db struct{}
func (*db) Do() {
fmt.Println("doing stuff")
}
func TestCMD(t *testing.T) {
ctx := context.Background()
fn := func(ctx context.Context, db *db) error {
fmt.Println("test cmd")
db.Do()
return nil
}
tFn := bus.NewTransactionalCommand(fn)
mng := &bus.CommandManager{}
err := mng.Handle(ctx, tFn)
if err != nil {
t.Fatal(err)
}
}
mng.handle
returns ErrInvalidCommand
so the test fails because cmd
is *TransactionalCommand[*db]
and not *TransactionalCommand[any]
mng.handle
返回ErrInvalidCommand
因此测试失败,因为cmd
是*TransactionalCommand[*db]
而不是*TransactionalCommand[any]
Let me give another, more abstract example:让我再举一个更抽象的例子:
type A[T any] struct{}
func (*A[T]) DoA() { fmt.Println("do A") }
type B[T any] struct{}
func (*B[T]) DoB() { fmt.Println("do B") }
func Handle(s interface{}) {
switch x := s.(type) {
case *A[any]:
x.DoA()
case *B[any]:
x.DoB()
default:
fmt.Printf("%T\n", s)
}
}
func TestFuncSwitch(t *testing.T) {
i := &A[int]{}
Handle(i) // expected to print "do A"
}
Why doesn't this switch statement case *A[any]
match *A[int]
?为什么这个 switch 语句 case
*A[any]
不匹配*A[int]
? How to make CommandManager.Handle(...)
accept generic Commands?如何使
CommandManager.Handle(...)
接受通用命令?
*A[any]
does not match *A[int]
because any
is a static type, not a wildcard. *A[any]
不匹配*A[int]
因为any
是 static 类型,而不是通配符。 Therefore instantiating a generic struct with different types yields different types .因此,实例化具有不同类型的通用结构会产生不同的类型。
In order to correctly match a generic struct in a type switch, you must instantiate it with a type parameter:为了正确匹配类型开关中的泛型结构,您必须使用类型参数对其进行实例化:
func Handle[T any](s interface{}) {
switch x := any(s).(type) {
case *A[T]:
x.DoA()
case *B[T]:
x.DoB()
default:
panic("no match")
}
}
Though in absence of other function arguments to infer T
, you will have to call Handle
with explicit instantiation.尽管在没有其他 function arguments 来推断
T
的情况下,您将不得不使用显式实例化调用Handle
。 T
won't be inferred from the struct alone. T
不会单独从结构中推断出来。
func main() {
i := &A[int]{}
Handle[int](i) // expected to print "do A"
}
Playground: https://go.dev/play/p/2e5E9LSWPmk游乐场: https://go.dev/play/p/2e5E9LSWPmk
However when Handle
is actually a method, as in your database code, this has the drawback of choosing the type parameter when instantiating the receiver.但是,当
Handle
实际上是一个方法时,如在您的数据库代码中,这具有在实例化接收器时选择类型参数的缺点。
In order to improve the code here you can make Handle
a top-level function:为了改进这里的代码,您可以将
Handle
设置为顶级 function:
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
}
}
Then you have the problem of how to supply the argument db T
to the command function.然后你就有了如何将参数
db T
提供给命令 function 的问题。 For this, you might:为此,您可以:
simply pass an additional *db
argument to Handle
and handleTransactionalCommand
, which also helps with type parameter inference.只需将额外的
*db
参数传递给Handle
和handleTransactionalCommand
,这也有助于类型参数推断。 Call as Handle(ctx, &db{}, tFn)
.调用为
Handle(ctx, &db{}, tFn)
。 Playground: https://go.dev/play/p/6WESb86KN5D游乐场: https://go.dev/play/p/6WESb86KN5D
pass an instance of CommandManager
(like solution above but *db
is wrapped).传递
CommandManager
的实例(如上面的解决方案,但*db
已包装)。 Much more verbose, as it requires explicit instantiation everywhere.更加冗长,因为它需要在任何地方进行显式实例化。 Playground: https://go.dev/play/p/SpXczsUM5aW
游乐场: https://go.dev/play/p/SpXczsUM5aW
use a parametrized interface instead (like below).改用参数化接口(如下所示)。 So you don't even have to type-switch.
因此,您甚至不必进行类型切换。 Playground: https://go.dev/play/p/EgULEIL6AV5
游乐场: https://go.dev/play/p/EgULEIL6AV5
type CMD[T any] interface {
Exec(ctx context.Context, db T) error
}
Why does the generic type switch fail to compile?为什么泛型类型开关无法编译?
This is in fact the result of an intentional decision of the Go team.这实际上是 Go 团队有意决定的结果。 It turned out that allowing type switches on parametrized types can cause confusion
事实证明,允许在参数化类型上进行类型切换会导致混淆
In an earlier version of this design, we permitted using type assertions and type switches on variables whose type was a type parameter, or whose type was based on a type parameter.
在此设计的早期版本中,我们允许在类型为类型参数或类型基于类型参数的变量上使用类型断言和类型切换。 We removed this facility because it is always possible to convert a value of any type to the empty interface type, and then use a type assertion or type switch on that.
我们删除了这个工具,因为总是可以将任何类型的值转换为空接口类型,然后在其上使用类型断言或类型开关。 Also, it was sometimes confusing that in a constraint with a type set that uses approximation elements, a type assertion or type switch would use the actual type argument, not the underlying type of the type argument (the difference is explained in the section on identifying the matched predeclared type)
此外,有时令人困惑的是,在具有使用近似元素的类型集的约束中,类型断言或类型切换将使用实际的类型参数,而不是类型参数的基础类型(差异在关于识别的部分中解释匹配的预声明类型)
From the Type Parameters Proposal来自类型参数提案
Let me turn the emphasized statement into code.让我把强调的语句变成代码。 If the type constraint uses type approximation (note the tildes)...
如果类型约束使用类型近似(注意波浪线)...
func PrintStringOrInt[T ~string | ~int](v T)
...and if there also was a custom type with int
as the underlying type... ...如果还有一个以
int
作为基础类型的自定义类型...
type Seconds int
...and if PrintOrString()
is called with a Seconds
parameter... ...如果
PrintOrString()
使用Seconds
参数调用...
PrintStringOrInt(Seconds(42))
...then the switch
block would not enter the int case
but go right into the default case
, because Seconds
is not an int
. ...然后
switch
块不会进入int case
,而是 go 进入default case
,因为Seconds
不是int
。 Developers might expect that case int:
matches the type Seconds
as well.开发人员可能期望
case int:
也匹配Seconds
类型。
To allow a case
statement to match both Seconds
and int
would require a new syntax, like, for example,要允许
case
语句同时匹配Seconds
和int
将需要新的语法,例如,
case ~int:
As of this writing, the discussion is still open, and maybe it will result in an entirely new option for switching on a type parameter (such as, switch type T
).在撰写本文时,讨论仍处于开放状态,也许它会带来一个全新的选项来打开类型参数(例如,
switch type T
)。
More details, please refer to proposal: spec: generics: type switch on parametric types更多细节请参考提案:spec: generics: type switch on parametric types
Trick: convert the type into 'any'技巧:将类型转换为 'any'
Luckily, we do not need to wait for this proposal to get implemented in a future release.幸运的是,我们不需要等待该提案在未来的版本中实施。 There is a super simple workaround available right now.
现在有一个超级简单的解决方法。
Instead of switching on v.(type)
, switch on any(v).(type)
.不要打开
v.(type)
,而是打开any(v).(type)
。
switch any(v).(type) {
...
This trick converts v
into an empty interface{}
(aka any
), for which the switch
happily does the type matching.这个技巧将
v
转换为一个空的interface{}
(又名any
), switch
很乐意为此进行类型匹配。
Source: A tip and a trick when working with generics资料来源: 使用 generics 时的提示和技巧
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.