How to make a copy by value of a generic type parameter

huangapple go评论88阅读模式
英文:

How to make a copy by value of a generic type parameter

问题

在通用函数中,如何按值复制具有通用类型的参数?

这是一个最简示例:

type MyInterface interface { // 用于类型约束的接口
	SetID(string)
}

type Implem struct { // 实现 MyInterface
	ID string
}

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

func doStuff[T MyInterface](input T) T {
	res := input // 在这里,我想要复制 input,所以下一行修改 res 而不是 input
	res.SetID("newID")
	return res
}

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

我尝试了几种方法,比如将 res 声明为 *T 并将 input 设置为其值,或者使用类型断言,但是 SetID 仍然修改了 input。

最佳的复制 input 的方法是什么?

英文:

In a generic function, how can I copy by value a parameter with a generic type ?

Here is a minimal example :

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"
}

I tried several things, like declaring res as a *T and setting input as its value, or playing with type assertions, but SetID still modify input.

What is the best way to copy input by value ?

答案1

得分: 0

在一般情况下,浅拷贝类型参数的工作方式与用于实例化它的任何类型参数相同。如果你用非指针类型实例化T,赋值操作会复制其值。如果你用指针类型实例化T,赋值操作仍然会复制其值,但该值是一个内存地址。

在你编写的代码中,类型约束MyInterface可以满足指针类型和非指针类型。

假设你可以声明一个类型如下:

type Foo struct{}

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

对值接收器的任何字段赋值都是无效的,但Foo将是一个有效的类型参数。

也许你想要做的是限制类型参数为指针类型。这样你就可以捕获基本类型并解引用以浅拷贝该值。

// 限制为指针类型
type MyInterface[T any] interface {
	*T
	SetID(string)
}

// 捕获基本类型 T
// 参数 'input' 是一个指针类型,因为 MyInterface
func doStuff[T any, U MyInterface[T]](input U) U {
	t := *input        // 解引用复制值
	res := U(&t)       // 取 t 的地址并转换为 U
	res.SetID("newID") // 现在你可以在类型为 U 的值上调用 SetID
	return res
}

这将输出

&{oldID}
&{newID}

Playground: https://go.dev/play/p/U8Ssq3_YPVi

作为另一种选择,你可以在结构体中添加一个额外的方法,返回其副本。然后在泛型函数中,你可以将其类型断言为具有该方法的匿名接口:

// 浅拷贝并返回相同类型
func (i *Implem) Copy() *Implem {
	c := *i
	return &c
}

func doStuff[T MyInterface](input T) T {
    // 类型断言为具有返回类型 T 的方法的匿名接口
	t, ok := any(input).(interface{ Copy() T })
	if !ok {
		// 处理失败
	}
	res := t.Copy()
	res.SetID("newID")
	return res
}

Playground: https://go.dev/play/p/K7MOApHdEmM

在我看来,使用匿名接口会更方便和可读,因为类型参数T以一种自解释的方式使用。当然,你也可以定义一个命名接口:

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

并断言为使用T实例化的该接口:

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

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

英文:

In the general case, shallow copying type parameters works the same as with whatever type argument was used to instantiate it. If you instantiate T with a non-pointer type, an assignment copies its value. If you instantiate T with a pointer type, an assignment still copies its value, but that value is a memory address.

In the code you wrote, the type constraint MyInterface could be satisfied by both pointer and non-pointer types.

Hypothetically, you could declare a type like:

type Foo struct{}

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

Any assignment on fields of a value receiver would be ineffective, but Foo would be a valid type argument.

Perhaps what you want to do is to restrict the type parameter to pointer types. This way you can capture the base type and dereference to shallow copy the value.

// 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
}

And this outputs

&{oldID}
&{newID}

Playground: https://go.dev/play/p/U8Ssq3_YPVi

<hr>

As an alternative, you can add an additional method to your struct that returns a copy of it. Then within the generic function, you type assert to an anonymous interface that has that method:

// shallow copy and return same type
func (i *Implem) Copy() *Implem {
	c := *i
	return &amp;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(&quot;newID&quot;)
	return res
}

Playground: https://go.dev/play/p/K7MOApHdEmM

Using an anonymous interface in my opinion makes it more convenient and readable, as the type parameter T is used in a self-explanatory way. You can of course define a named interface:

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

and assert to that interface instantiated with T:

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

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

huangapple
  • 本文由 发表于 2023年4月19日 18:13:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76053298.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定