Reputation:
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:
pfoo := lib.NewFoo() // returns type `*Foo`
foo := *pfoo // now variable foo is type `Foo`
foo2 := foo // I don't want this
Foo
s to require pointer types, to make things safe on my side, but again, importer code may write functions that use Foo
values.Clone()
method to Foo
, but this falls under "documentation" issue: one may not read it.go vet
but this won't result in compiler errors.However is there another way to force the compiler to throw an error on struct shallow copies?
Upvotes: 0
Views: 511
Reputation: 121089
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.
Upvotes: 5