Go 泛型:为什么 switch case *A[any] 不匹配 *A[int]

刚开始学习泛型。我正在制作一个命令处理器,老实说我不知道如何表达这个所以我只是想展示一个示例问题:


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)

    }

}


为什么这个 switch 语句大小写不*A[any]匹配*A[int]?如何CommandManager.Handle(...)接受通用命令?


30秒到达战场
浏览 134回答 2
2回答

墨色风雨

为什么泛型类型开关编译失败?这其实是 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高兴地为它进行类型匹配。

拉莫斯之舞

*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/EgULEIL6AV5type CMD[T any] interface {    Exec(ctx context.Context, db T) error}
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go