Is it safe to keep noCopy types in a slice which might copy on re-alloc?

147 views
Skip to first unread message

Antonio Caceres Cabrera

unread,
Aug 27, 2023, 10:54:26 AM8/27/23
to golang-nuts
Go vet complains about this minimal example code

    type Foo struct {
    val atomic.Uint64
    }
   
    func main() {
    var foos = make([]Foo, 0)
    var bar Foo
    bar.val.Store(5)
    foos = append(foos, bar) // call of append copies lock value: example.com/foo.Foo contains sync/atomic.Uint64 contains sync/atomic.noCopy

    }

Because the types of the sync package are not supposed to be copied.
This is also true for sync.Map, sync.Mutex, sync.WaitGroup etc.

However, if I instead copy in a zero-value Foo and then set that, go vet does not complain, even if later appends are done:

    func main() {
    var foos = make([]Foo, 0)
    foos = append(foos, Foo{})
    foos[0].val.Store(5)
    foos = append(foos, Foo{})
    foos = append(foos, Foo{})
    // append some more
        // ...
    fmt.Println(foos)
    }

However, is this supposed to be safe, or is go vet just not catching it?
Even if copying a zero-value of such a type is safe, append() might have to re-allocate the underlying array, which includes copying the existing values, which might already have been used, as foo[0] has been in this case. This would violate the noCopy trait of the type. My question is thus:

Is it safe to keep nocopy types from the sync and sync.atomic packages in a slice, given that they might internally be copied when the slice is appended to?

Of course, copying these types is not by itself atomic or synchronized with other accesses. So let's assume that I, as the programmer, guarantee that while an append happens, this slice will only be accessed by the one appending goroutine, using other synchronization primitives, such as mutex, channels etc. or by only running these appends in the main before any other go-routine is started.

Ian Lance Taylor

unread,
Aug 27, 2023, 1:05:15 PM8/27/23
to Antonio Caceres Cabrera, golang-nuts
Doing an append on a slice of types that contain atomic types is in
general not safe and go vet is not catching it.

If your program can guarantee that while the append is happening there
is no concurrent access to the atomic types, then I think that is
safe. It's not safe in general for all nocopy types, but it's safe
for types like atomic.Uint64. It doesn't seem like a very clear way
to write a program though. It may be better to use a slice of
pointers: in your example, []*Foo. Or precount the size of the slice
to ensure that append won't copy it.

Ian

Antonio Caceres Cabrera

unread,
Aug 27, 2023, 5:28:12 PM8/27/23
to golang-nuts
Thanks for your reply, Ian.
I've decided on using a []*Foo now. Just to clarify, however: In general, copying/assigning from a struct literal as in
a[i] = Foo{}, is that always safe for these sync types? Or generally, is copying their "untouched" zero-values always safe?

Antonio

Ian Lance Taylor

unread,
Aug 28, 2023, 8:29:51 PM8/28/23
to Antonio Caceres Cabrera, golang-nuts
On Sun, Aug 27, 2023 at 2:28 PM Antonio Caceres Cabrera
<julioca...@gmail.com> wrote:
>
> Thanks for your reply, Ian.
> I've decided on using a []*Foo now. Just to clarify, however: In general, copying/assigning from a struct literal as in
> a[i] = Foo{}, is that always safe for these sync types? Or generally, is copying their "untouched" zero-values always safe?

Yes, in general it's safe to copy the untouched zero value of these types.

Ian
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/6ad297ba-19d4-4eaf-a2c2-252ca9db390cn%40googlegroups.com.
Reply all
Reply to author
Forward
0 new messages