Can atomic.Value store a zero interface{}?

ghz 8months ago ⋅ 86 views
package atomicvalue

import "sync/atomic"

type AtomicValue[T any] struct {
    v atomic.Value
}

func (av *AtomicValue[T]) Load() T {
    if v := av.v.Load(); v != nil {
        return v.(T)
    } else {
        var v T
        return v
    }
}

func (av *AtomicValue[T]) Store(value T) {
    av.v.Store(value)
}

func (av *AtomicValue[T]) CompareAndSwap(old, new T) bool {
    return av.v.CompareAndSwap(old, new)
}

func (av *AtomicValue[T]) Swap(new T) (old T) {
    old = av.Load()
    av.Store(new)
    return old
}

I wrap the atomic.Value to a generic type. I handled the zero value in Load() function, but I can't store nil.

type MyInterface interface{
    One()
    Two()
    Three()
}

var value atomicvalue.AtomicValue[MyInterface]
bar:=&MyStruct{} // MyStruct implements MyInterface
value.store(bar) // ok
value.store(nil) // panic there

When I store a nil, there will be a panic:

panic: sync/atomic: store of nil value into Value

Answers

The panic occurs because the atomic.Value does not allow storing nil values. In your AtomicValue implementation, you're trying to handle the zero value case in the Load function, but storing nil directly is not permitted by the atomic.Value.

To handle nil values gracefully in your AtomicValue, you can use a sentinel value to represent nil. Here's how you can modify your AtomicValue implementation to handle nil:

package atomicvalue

import "sync/atomic"

type AtomicValue[T any] struct {
    v atomic.Value
}

func (av *AtomicValue[T]) Load() T {
    if v := av.v.Load(); v != nil {
        return v.(T)
    }
    // Handle nil case by returning a default value or sentinel value
    return zeroValueForTypeT() // Or return your predefined sentinel value
}

func (av *AtomicValue[T]) Store(value T) {
    // If value is nil, store a sentinel value instead
    if value == nil {
        value = zeroValueForTypeT() // Or use your predefined sentinel value
    }
    av.v.Store(value)
}

func (av *AtomicValue[T]) CompareAndSwap(old, new T) bool {
    return av.v.CompareAndSwap(old, new)
}

func (av *AtomicValue[T]) Swap(new T) (old T) {
    old = av.Load()
    av.Store(new)
    return old
}

// Define a function to return zero value or sentinel value for type T
func zeroValueForTypeT() T {
    // Return appropriate zero value or sentinel value for type T
}

In this modified implementation, when storing a nil value, you replace it with a sentinel value that represents nil. Similarly, in the Load function, you handle the case where the stored value is the sentinel value and return nil accordingly.

Ensure that zeroValueForTypeT() returns an appropriate zero value or sentinel value for the type T. This approach allows you to gracefully handle nil values without causing a panic.