英文:
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
Foo
s to require pointer types, to make things safe on my side, but again, importer code may write functions that useFoo
values. - add a
Clone()
method toFoo
, 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
。
如果应用程序需要深拷贝,文档中应明确要求不应复制该值,并提供一个 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论