150 lines
3.8 KiB
Go
150 lines
3.8 KiB
Go
package union
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"unsafe"
|
|
|
|
"git.brut.systems/judah/xx/mem"
|
|
)
|
|
|
|
var (
|
|
ErrUninitializedAccess = errors.New("access of uninitialized union")
|
|
ErrInvalidType = errors.New("type does not exist within union")
|
|
)
|
|
|
|
// anystruct represents a struct type with any members.
|
|
//
|
|
// Note: because Go's type constraint system can't enforce
|
|
// this, anystruct is here for documentation purposes.
|
|
type anystruct any
|
|
|
|
// @note(judah): is there a way to declare the type parameters
|
|
// to allow 'type Value union.Of[...]' so users can define their
|
|
// own methods?
|
|
|
|
// Of represents a union of different types.
|
|
//
|
|
// Since members are accessed by type instead of name,
|
|
// T is expected to be a struct of types like so:
|
|
//
|
|
// type Value = union.Of[struct {
|
|
// int32
|
|
// uint32
|
|
// float32
|
|
// })
|
|
type Of[T anystruct] struct {
|
|
typ reflect.Type
|
|
mem []byte
|
|
}
|
|
|
|
func (u Of[T]) Size() uintptr {
|
|
return mem.Sizeof[T]()
|
|
}
|
|
|
|
// String returns the string representation of a union.
|
|
func (u Of[T]) String() string {
|
|
var b strings.Builder
|
|
|
|
fmt.Fprintf(&b, "union[%s] = ", reflect.TypeFor[T]().String())
|
|
if u.typ == nil {
|
|
b.WriteString("none")
|
|
} else {
|
|
b.WriteString(u.typ.String())
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// Is returns true if the given type is currently stored in the union.
|
|
func Is[E any, T anystruct](u Of[T]) bool {
|
|
// Explicit invalid check to make sure invalid types don't result in false-positives.
|
|
if u.typ == nil {
|
|
return false
|
|
}
|
|
|
|
return u.typ == reflect.TypeFor[E]()
|
|
}
|
|
|
|
// Set overwrites the backing memory of a union with the given value; initializing the union if uninitialized.
|
|
//
|
|
// Set is unsafe and will not verify if the backing memory has enough capacity to store the value.
|
|
// Use [SetSafe] for more safety checks.
|
|
func Set[V any, T anystruct](u *Of[T], value V) {
|
|
if u.mem == nil {
|
|
u.mem = make([]byte, mem.Sizeof[T]())
|
|
}
|
|
|
|
unsafe.Slice((*V)(unsafe.Pointer(&u.mem[0])), 1)[0] = value
|
|
u.typ = reflect.TypeFor[V]()
|
|
}
|
|
|
|
// SetSafe overwrites the backing memory of a union with the given value,
|
|
// returning an error if the value cannot be stored in the union.
|
|
//
|
|
// Use [Set] for fewer safety checks.
|
|
func SetSafe[V any, T anystruct](u *Of[T], value V) error {
|
|
if u.mem == nil {
|
|
u.mem = make([]byte, mem.Sizeof[T]())
|
|
}
|
|
|
|
vt := reflect.TypeFor[V]()
|
|
for _, field := range getInternalFields(*u) {
|
|
if field.Type == vt {
|
|
unsafe.Slice((*V)(unsafe.Pointer(&u.mem[0])), 1)[0] = value
|
|
u.typ = reflect.TypeFor[V]()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("%s - %w", vt, ErrInvalidType)
|
|
}
|
|
|
|
// Get returns the union's backing memory interpreted as a value of type V, panicking if the union is uninitialized.
|
|
//
|
|
// Get is unsafe and will not verify if the type exists within the union.
|
|
// Use [GetSafe] for more safety checks.
|
|
func Get[V any, T anystruct](u Of[T]) V {
|
|
if u.mem == nil {
|
|
panic(ErrUninitializedAccess)
|
|
}
|
|
|
|
return unsafe.Slice((*V)(unsafe.Pointer(&u.mem[0])), 1)[0]
|
|
}
|
|
|
|
// GetSafe returns the union's backing memory interpreted as a value of type V, returning an error if the type
|
|
// does not exist within the union or the union is uninitialized.
|
|
//
|
|
// Use [Get] for fewer safety checks.
|
|
func GetSafe[V any, T anystruct](u Of[T]) (V, error) {
|
|
if u.mem == nil {
|
|
return mem.ZeroValue[V](), ErrUninitializedAccess
|
|
}
|
|
|
|
vt := reflect.TypeFor[V]()
|
|
for _, field := range getInternalFields(u) {
|
|
if field.Type == vt {
|
|
return unsafe.Slice((*V)(unsafe.Pointer(&u.mem[0])), 1)[0], nil
|
|
}
|
|
}
|
|
|
|
return mem.ZeroValue[V](), ErrInvalidType
|
|
}
|
|
|
|
// getInternalFields returns an array of reflect.StructField belonging
|
|
// to the internal type of a union.
|
|
func getInternalFields[U Of[T], T anystruct](_ U) []reflect.StructField {
|
|
backing := reflect.TypeFor[T]()
|
|
if backing.Kind() != reflect.Struct {
|
|
return nil
|
|
}
|
|
|
|
var fields []reflect.StructField
|
|
for i := range backing.NumField() {
|
|
fields = append(fields, backing.Field(i))
|
|
}
|
|
|
|
return fields
|
|
}
|