Go语言 如何按泛型类型参数的值进行复制

xkrw2x1b  于 2023-04-27  发布在  Go
关注(0)|答案(1)|浏览(94)

在泛型函数中,如何按值复制具有泛型类型的参数?
下面是一个简单的例子:

type MyInterface interface { // Interface use for type constraint
    SetID(string)
}

type Implem struct { // Implements MyInterface
    ID string
}

func (i *Implem) SetID(id string) {
    i.ID = id
}

func doStuff[T MyInterface](input T) T {
    res := input // Here I want to make a copy of input, so the next line modify res but not input
    res.SetID("newID")
    return res
}

func main() {
    implem := &Implem{ID: "oldID"}
    res := doStuff(implem)
    fmt.Println(implem) // Should be "oldID"
    fmt.Println(res)    // Should be "newID"
}

我尝试了几种方法,比如将res声明为*T并将input设置为它的值,或者使用类型Assert,但SetID仍然修改input。
按值复制input的最佳方法是什么?

eimct9ow

eimct9ow1#

在一般情况下,浅层复制类型参数的工作方式与用于示例化它的任何类型参数相同。如果您使用非指针类型示例化T,则赋值将复制其值。如果您使用指针类型示例化T,则赋值仍将复制其值,但该值是内存地址。
在您编写的代码中,类型约束MyInterface可以由指针和非指针类型满足。
假设,你可以声明一个类型如下:

type Foo struct{}

func (i Implem) SetID(id string) {
}

对值接收者的字段的任何赋值都是无效的,但Foo将是有效的类型参数。
也许你想做的是将类型参数限制为指针类型。这样你就可以捕获基类型并解引用以浅复制值。

// restrict to pointer types
type MyInterface[T any] interface {
    *T
    SetID(string)
}

// capture the base type T
// the argument 'input' is a pointer type due to MyInterface
func doStuff[T any, U MyInterface[T]](input U) U {
    t := *input        // dereferencing copies the value
    res := U(&t)       // take address of t and convert to U
    res.SetID("newID") // you can now call SetID on value of type U
    return res
}

这个输出

&{oldID}
&{newID}

Playground:https://go.dev/play/p/U8Ssq3_YPVi
作为替代方案,您可以向结构体添加一个额外的方法,以返回它的副本。然后在泛型函数中,您可以向具有该方法的匿名接口键入assert:

// shallow copy and return same type
func (i *Implem) Copy() *Implem {
    c := *i
    return &c
}

func doStuff[T MyInterface](input T) T {
    // type assert to anonymous interface with method that returns T
    t, ok := any(input).(interface{ Copy() T })
    if !ok {
        // handle failure
    }
    res := t.Copy()
    res.SetID("newID")
    return res
}

Playground:https://go.dev/play/p/K7MOApHdEmM
在我看来,使用匿名接口更方便,可读性更强,因为类型参数T是以一种不言自明的方式使用的。当然,你可以定义一个命名接口:

type Copier[T any] interface {
    Copy() T
}

并对使用T示例化的接口Assert:

t, ok := any(input).(Copier[T])

Playground:https://go.dev/play/p/Berisu6Qz-P

相关问题