user15824378
user15824378

Reputation:

How to force compiler error if struct shallow copy?

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

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

Upvotes: 0

Views: 511

Answers (1)

Thundercat
Thundercat

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

Related Questions