How to force compiler error if struct shallow copy?

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

How to force compiler error if struct shallow copy?

问题

我维护一个库,其中导出的结构体具有不应进行浅拷贝的字段。例如:

type Example struct {
    Val    int
    Nums   []int
}

由于Nums是一个切片类型的字段,对Example实例进行浅拷贝会复制切片头,并可能引发错误:

foo := Example{Val: 1, Nums: []int{100}}
bar := Example

bar.Nums[0] = 200

fmt.Println(foo.Nums) // [200]

我想阻止导入库的代码对结构体进行浅拷贝。可能的解决方案有:

  • 返回指针的构造函数,但无法阻止客户端对其进行解引用和浅拷贝:
pfoo := lib.NewFoo() // 返回类型为`*Foo`
foo := *pfoo // 现在变量foo的类型为`Foo`
foo2 := foo  // 我不希望这样
  • 在库中添加详尽的文档以阻止浅拷贝。我还可以声明库中所有使用Foo的方法都要求指针类型,以确保在我的代码中安全使用,但导入库的代码可能编写使用Foo值的函数。
  • Foo中添加一个Clone()方法,但这属于“文档”问题:有人可能不会阅读它。
  • go vet中添加自定义检查,但这不会导致编译器错误。
  • 重写库以避免暴露不可复制的字段。
  • 忍受可能存在的错误。

然而,是否有其他方法可以强制编译器在进行结构体浅拷贝时抛出错误?

英文:

I maintain a library where exported struct has fields that should not shallow copy. For example:

type Example struct {
    Val    int
    Nums   []int
}

As Nums is a field of type slice, a shallow copy of a Foo instance copies the slice header and allow bugs:

foo := Example{Val: 1, Nums: []int{100}}
bar := Example

bar.Nums[0] = 200

fmt.Println(foo.Nums) // [200]

I want to prevent code importing the library to shallow copy the struct. Possible solutions:

  • constructor that return a pointer, but nothing stops the client from dereference and shallow copy anyway:
pfoo := lib.NewFoo() // returns type `*Foo`
foo := *pfoo // now variable foo is type `Foo`
foo2 := foo  // I don't want this
  • add abundant documentation to the library to discourage shallow copyies. I can also declare all methods in my library that use Foos to require pointer types, to make things safe on my side, but again, importer code may write functions that use Foo values.
  • add a Clone() method to Foo, but this falls under "documentation" issue: one may not read it.
  • add a custom check to go vet but this won't result in compiler errors.
  • rewrite the library to avoid exposed non-copiable fields
  • put up with the possibility of having bugs

However is there another way to force the compiler to throw an error on struct shallow copies?

答案1

得分: 5

这个问题 runtime: add NoCopy documentation struct type? 解决了这个问题。
对该问题的评论推荐了以下解决方案:

请注意,绝对必须选择 vet 检查的代码已经可以这样做。一个包可以定义:

type noCopy struct{}
func (*noCopy) Lock() {}

然后将 noCopy noCopy 放入任何必须由 vet 标记的结构体中。

还需要一个 Unlock 方法来触发警告。以下是示例的完整解决方案:

type noCopy struct{}

func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type Example struct {
    noCopy noCopy
    Val    int
    Nums   []int
}

通过这个更改,go vet 命令会对以下代码打印警告信息:assignment copies lock value to y:Example contains noCopy

playground上运行示例

如果应用程序需要深拷贝,文档中应明确要求不应复制该值,并提供一个 Clone() 方法。

这种方法不会增加 Example 的大小,因为 struct{} 的大小为零。

标准库中的 sync.WaitGroup 类型使用了 这种方法

英文:

The issue runtime: add NoCopy documentation struct type? addresses this problem.
A comment on the issue recommends this solution:

> Note that code that absolutely must opt in to the vet check can already do so. A package can define:
>
> type noCopy struct{}
> func (*noCopy) Lock() {}
>

> and then put a noCopy noCopy into any struct that must be flagged by vet.

An Unlock method is also required to trigger the warning. Here's the complete solution for the example:

type noCopy struct{}

func (*noCopy) Lock()   {}
func (*noCopy) Unlock() {}

type Example struct {
	noCopy noCopy
	Val    int
	Nums   []int
}

With this change, the go vet command prints the warning assignment copies lock value to y:Example contains noCopy for the following code:

var x Example
y := x

Run the example on the playground.

Document the requirement that the value should not be copied. Provide a Clone() method if applications need a deep copy.

The approach does not add to the size of Example because the size of a struct{} is zero.

The standard library sync.WaitGroup type uses this approach.

huangapple
  • 本文由 发表于 2021年6月30日 01:25:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/68183168.html
匿名

发表评论

匿名网友

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

确定