mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-30 05:25:35 +00:00
20562a4fb9
This adds support for container-like types such as Container[T] that don't explicitly specify a view type for T. Instead, a package implementing a container type should also implement and export a ContainerView[T, V] type and a ContainerViewOf(*Container[T]) ContainerView[T, V] function, which returns a view for the specified container, inferring the element view type V from the element type T. Updates #12736 Signed-off-by: Nick Khyl <nickk@tailscale.com>
609 lines
17 KiB
Go
609 lines
17 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// Package views provides read-only accessors for commonly used
|
|
// value types.
|
|
package views
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"maps"
|
|
"reflect"
|
|
"slices"
|
|
|
|
"go4.org/mem"
|
|
)
|
|
|
|
func unmarshalSliceFromJSON[T any](b []byte, x *[]T) error {
|
|
if *x != nil {
|
|
return errors.New("already initialized")
|
|
}
|
|
if len(b) == 0 {
|
|
return nil
|
|
}
|
|
return json.Unmarshal(b, x)
|
|
}
|
|
|
|
// ByteSlice is a read-only accessor for types that are backed by a []byte.
|
|
type ByteSlice[T ~[]byte] struct {
|
|
// ж is the underlying mutable value, named with a hard-to-type
|
|
// character that looks pointy like a pointer.
|
|
// It is named distinctively to make you think of how dangerous it is to escape
|
|
// to callers. You must not let callers be able to mutate it.
|
|
ж T
|
|
}
|
|
|
|
// ByteSliceOf returns a ByteSlice for the provided slice.
|
|
func ByteSliceOf[T ~[]byte](x T) ByteSlice[T] {
|
|
return ByteSlice[T]{x}
|
|
}
|
|
|
|
// MapKey returns a unique key for a slice, based on its address and length.
|
|
func (v ByteSlice[T]) MapKey() SliceMapKey[byte] { return mapKey(v.ж) }
|
|
|
|
// Len returns the length of the slice.
|
|
func (v ByteSlice[T]) Len() int {
|
|
return len(v.ж)
|
|
}
|
|
|
|
// IsNil reports whether the underlying slice is nil.
|
|
func (v ByteSlice[T]) IsNil() bool {
|
|
return v.ж == nil
|
|
}
|
|
|
|
// Mem returns a read-only view of the underlying slice.
|
|
func (v ByteSlice[T]) Mem() mem.RO {
|
|
return mem.B(v.ж)
|
|
}
|
|
|
|
// Equal reports whether the underlying slice is equal to b.
|
|
func (v ByteSlice[T]) Equal(b T) bool {
|
|
return bytes.Equal(v.ж, b)
|
|
}
|
|
|
|
// EqualView reports whether the underlying slice is equal to b.
|
|
func (v ByteSlice[T]) EqualView(b ByteSlice[T]) bool {
|
|
return bytes.Equal(v.ж, b.ж)
|
|
}
|
|
|
|
// AsSlice returns a copy of the underlying slice.
|
|
func (v ByteSlice[T]) AsSlice() T {
|
|
return v.AppendTo(v.ж[:0:0])
|
|
}
|
|
|
|
// AppendTo appends the underlying slice values to dst.
|
|
func (v ByteSlice[T]) AppendTo(dst T) T {
|
|
return append(dst, v.ж...)
|
|
}
|
|
|
|
// At returns the byte at index `i` of the slice.
|
|
func (v ByteSlice[T]) At(i int) byte { return v.ж[i] }
|
|
|
|
// SliceFrom returns v[i:].
|
|
func (v ByteSlice[T]) SliceFrom(i int) ByteSlice[T] { return ByteSlice[T]{v.ж[i:]} }
|
|
|
|
// SliceTo returns v[:i]
|
|
func (v ByteSlice[T]) SliceTo(i int) ByteSlice[T] { return ByteSlice[T]{v.ж[:i]} }
|
|
|
|
// Slice returns v[i:j]
|
|
func (v ByteSlice[T]) Slice(i, j int) ByteSlice[T] { return ByteSlice[T]{v.ж[i:j]} }
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (v ByteSlice[T]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (v *ByteSlice[T]) UnmarshalJSON(b []byte) error {
|
|
if v.ж != nil {
|
|
return errors.New("already initialized")
|
|
}
|
|
return json.Unmarshal(b, &v.ж)
|
|
}
|
|
|
|
// StructView represents the corresponding StructView of a Viewable. The concrete types are
|
|
// typically generated by tailscale.com/cmd/viewer.
|
|
type StructView[T any] interface {
|
|
// Valid reports whether the underlying Viewable is nil.
|
|
Valid() bool
|
|
// AsStruct returns a deep-copy of the underlying value.
|
|
// It returns nil, if Valid() is false.
|
|
AsStruct() T
|
|
}
|
|
|
|
// Cloner is any type that has a Clone function returning a deep-clone of the receiver.
|
|
type Cloner[T any] interface {
|
|
// Clone returns a deep-clone of the receiver.
|
|
// It returns nil, when the receiver is nil.
|
|
Clone() T
|
|
}
|
|
|
|
// ViewCloner is any type that has had View and Clone funcs generated using
|
|
// tailscale.com/cmd/viewer.
|
|
type ViewCloner[T any, V StructView[T]] interface {
|
|
// View returns a read-only view of Viewable.
|
|
// If Viewable is nil, View().Valid() reports false.
|
|
View() V
|
|
// Clone returns a deep-clone of Viewable.
|
|
// It returns nil, when Viewable is nil.
|
|
Clone() T
|
|
}
|
|
|
|
// SliceOfViews returns a ViewSlice for x.
|
|
func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
|
|
return SliceView[T, V]{x}
|
|
}
|
|
|
|
// SliceView wraps []T to provide accessors which return an immutable view V of
|
|
// T. It is used to provide the equivalent of SliceOf([]V) without having to
|
|
// allocate []V from []T.
|
|
type SliceView[T ViewCloner[T, V], V StructView[T]] struct {
|
|
// ж is the underlying mutable value, named with a hard-to-type
|
|
// character that looks pointy like a pointer.
|
|
// It is named distinctively to make you think of how dangerous it is to escape
|
|
// to callers. You must not let callers be able to mutate it.
|
|
ж []T
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (v SliceView[T, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (v *SliceView[T, V]) UnmarshalJSON(b []byte) error { return unmarshalSliceFromJSON(b, &v.ж) }
|
|
|
|
// IsNil reports whether the underlying slice is nil.
|
|
func (v SliceView[T, V]) IsNil() bool { return v.ж == nil }
|
|
|
|
// Len returns the length of the slice.
|
|
func (v SliceView[T, V]) Len() int { return len(v.ж) }
|
|
|
|
// At returns a View of the element at index `i` of the slice.
|
|
func (v SliceView[T, V]) At(i int) V { return v.ж[i].View() }
|
|
|
|
// SliceFrom returns v[i:].
|
|
func (v SliceView[T, V]) SliceFrom(i int) SliceView[T, V] { return SliceView[T, V]{v.ж[i:]} }
|
|
|
|
// SliceTo returns v[:i]
|
|
func (v SliceView[T, V]) SliceTo(i int) SliceView[T, V] { return SliceView[T, V]{v.ж[:i]} }
|
|
|
|
// Slice returns v[i:j]
|
|
func (v SliceView[T, V]) Slice(i, j int) SliceView[T, V] { return SliceView[T, V]{v.ж[i:j]} }
|
|
|
|
// SliceMapKey represents a comparable unique key for a slice, based on its
|
|
// address and length. It can be used to key maps by slices but should only be
|
|
// used when the underlying slice is immutable.
|
|
//
|
|
// Empty and nil slices have different keys.
|
|
type SliceMapKey[T any] struct {
|
|
// t is the address of the first element, or nil if the slice is nil or
|
|
// empty.
|
|
t *T
|
|
// n is the length of the slice, or -1 if the slice is nil.
|
|
n int
|
|
}
|
|
|
|
// MapKey returns a unique key for a slice, based on its address and length.
|
|
func (v SliceView[T, V]) MapKey() SliceMapKey[T] { return mapKey(v.ж) }
|
|
|
|
// AppendTo appends the underlying slice values to dst.
|
|
func (v SliceView[T, V]) AppendTo(dst []V) []V {
|
|
for _, x := range v.ж {
|
|
dst = append(dst, x.View())
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// AsSlice returns a copy of underlying slice.
|
|
func (v SliceView[T, V]) AsSlice() []V {
|
|
return v.AppendTo(nil)
|
|
}
|
|
|
|
// Slice is a read-only accessor for a slice.
|
|
type Slice[T any] struct {
|
|
// ж is the underlying mutable value, named with a hard-to-type
|
|
// character that looks pointy like a pointer.
|
|
// It is named distinctively to make you think of how dangerous it is to escape
|
|
// to callers. You must not let callers be able to mutate it.
|
|
ж []T
|
|
}
|
|
|
|
// MapKey returns a unique key for a slice, based on its address and length.
|
|
func (v Slice[T]) MapKey() SliceMapKey[T] { return mapKey(v.ж) }
|
|
|
|
// mapKey returns a unique key for a slice, based on its address and length.
|
|
func mapKey[T any](x []T) SliceMapKey[T] {
|
|
if x == nil {
|
|
return SliceMapKey[T]{nil, -1}
|
|
}
|
|
if len(x) == 0 {
|
|
return SliceMapKey[T]{nil, 0}
|
|
}
|
|
return SliceMapKey[T]{&x[0], len(x)}
|
|
}
|
|
|
|
// SliceOf returns a Slice for the provided slice for immutable values.
|
|
// It is the caller's responsibility to make sure V is immutable.
|
|
func SliceOf[T any](x []T) Slice[T] {
|
|
return Slice[T]{x}
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (v Slice[T]) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(v.ж)
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
func (v *Slice[T]) UnmarshalJSON(b []byte) error {
|
|
return unmarshalSliceFromJSON(b, &v.ж)
|
|
}
|
|
|
|
// IsNil reports whether the underlying slice is nil.
|
|
func (v Slice[T]) IsNil() bool { return v.ж == nil }
|
|
|
|
// Len returns the length of the slice.
|
|
func (v Slice[T]) Len() int { return len(v.ж) }
|
|
|
|
// At returns the element at index `i` of the slice.
|
|
func (v Slice[T]) At(i int) T { return v.ж[i] }
|
|
|
|
// SliceFrom returns v[i:].
|
|
func (v Slice[T]) SliceFrom(i int) Slice[T] { return Slice[T]{v.ж[i:]} }
|
|
|
|
// SliceTo returns v[:i]
|
|
func (v Slice[T]) SliceTo(i int) Slice[T] { return Slice[T]{v.ж[:i]} }
|
|
|
|
// Slice returns v[i:j]
|
|
func (v Slice[T]) Slice(i, j int) Slice[T] { return Slice[T]{v.ж[i:j]} }
|
|
|
|
// AppendTo appends the underlying slice values to dst.
|
|
func (v Slice[T]) AppendTo(dst []T) []T {
|
|
return append(dst, v.ж...)
|
|
}
|
|
|
|
// AsSlice returns a copy of underlying slice.
|
|
func (v Slice[T]) AsSlice() []T {
|
|
return v.AppendTo(v.ж[:0:0])
|
|
}
|
|
|
|
// IndexFunc returns the first index of an element in v satisfying f(e),
|
|
// or -1 if none do.
|
|
//
|
|
// As it runs in O(n) time, use with care.
|
|
func (v Slice[T]) IndexFunc(f func(T) bool) int {
|
|
for i := range v.Len() {
|
|
if f(v.At(i)) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// ContainsFunc reports whether any element in v satisfies f(e).
|
|
//
|
|
// As it runs in O(n) time, use with care.
|
|
func (v Slice[T]) ContainsFunc(f func(T) bool) bool {
|
|
return slices.ContainsFunc(v.ж, f)
|
|
}
|
|
|
|
// AppendStrings appends the string representation of each element in v to dst.
|
|
func AppendStrings[T fmt.Stringer](dst []string, v Slice[T]) []string {
|
|
for _, x := range v.ж {
|
|
dst = append(dst, x.String())
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// SliceContains reports whether v contains element e.
|
|
//
|
|
// As it runs in O(n) time, use with care.
|
|
func SliceContains[T comparable](v Slice[T], e T) bool {
|
|
return slices.Contains(v.ж, e)
|
|
}
|
|
|
|
// SliceEqual is like the standard library's slices.Equal, but for two views.
|
|
func SliceEqual[T comparable](a, b Slice[T]) bool {
|
|
return slices.Equal(a.ж, b.ж)
|
|
}
|
|
|
|
// SliceEqualAnyOrder reports whether a and b contain the same elements, regardless of order.
|
|
// The underlying slices for a and b can be nil.
|
|
func SliceEqualAnyOrder[T comparable](a, b Slice[T]) bool {
|
|
if a.Len() != b.Len() {
|
|
return false
|
|
}
|
|
|
|
var diffStart int // beginning index where a and b differ
|
|
for n := a.Len(); diffStart < n; diffStart++ {
|
|
if a.At(diffStart) != b.At(diffStart) {
|
|
break
|
|
}
|
|
}
|
|
if diffStart == a.Len() {
|
|
return true
|
|
}
|
|
|
|
// count the occurrences of remaining values and compare
|
|
valueCount := make(map[T]int)
|
|
for i, n := diffStart, a.Len(); i < n; i++ {
|
|
valueCount[a.At(i)]++
|
|
valueCount[b.At(i)]--
|
|
}
|
|
for _, count := range valueCount {
|
|
if count != 0 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// MapSlice is a view over a map whose values are slices.
|
|
type MapSlice[K comparable, V any] struct {
|
|
// ж is the underlying mutable value, named with a hard-to-type
|
|
// character that looks pointy like a pointer.
|
|
// It is named distinctively to make you think of how dangerous it is to escape
|
|
// to callers. You must not let callers be able to mutate it.
|
|
ж map[K][]V
|
|
}
|
|
|
|
// MapSliceOf returns a MapSlice for the provided map. It is the caller's
|
|
// responsibility to make sure V is immutable.
|
|
func MapSliceOf[K comparable, V any](m map[K][]V) MapSlice[K, V] {
|
|
return MapSlice[K, V]{m}
|
|
}
|
|
|
|
// Contains reports whether k has an entry in the map.
|
|
func (m MapSlice[K, V]) Contains(k K) bool {
|
|
_, ok := m.ж[k]
|
|
return ok
|
|
}
|
|
|
|
// IsNil reports whether the underlying map is nil.
|
|
func (m MapSlice[K, V]) IsNil() bool {
|
|
return m.ж == nil
|
|
}
|
|
|
|
// Len returns the number of elements in the map.
|
|
func (m MapSlice[K, V]) Len() int { return len(m.ж) }
|
|
|
|
// Get returns the element with key k.
|
|
func (m MapSlice[K, V]) Get(k K) Slice[V] {
|
|
return SliceOf(m.ж[k])
|
|
}
|
|
|
|
// GetOk returns the element with key k and a bool representing whether the key
|
|
// is in map.
|
|
func (m MapSlice[K, V]) GetOk(k K) (Slice[V], bool) {
|
|
v, ok := m.ж[k]
|
|
return SliceOf(v), ok
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (m MapSlice[K, V]) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(m.ж)
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
// It should only be called on an uninitialized Map.
|
|
func (m *MapSlice[K, V]) UnmarshalJSON(b []byte) error {
|
|
if m.ж != nil {
|
|
return errors.New("already initialized")
|
|
}
|
|
return json.Unmarshal(b, &m.ж)
|
|
}
|
|
|
|
// Range calls f for every k,v pair in the underlying map.
|
|
// It stops iteration immediately if f returns false.
|
|
func (m MapSlice[K, V]) Range(f MapRangeFn[K, Slice[V]]) {
|
|
for k, v := range m.ж {
|
|
if !f(k, SliceOf(v)) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// AsMap returns a shallow-clone of the underlying map.
|
|
//
|
|
// If V is a pointer type, it is the caller's responsibility to make sure the
|
|
// values are immutable. The map and slices are cloned, but the values are not.
|
|
func (m MapSlice[K, V]) AsMap() map[K][]V {
|
|
if m.ж == nil {
|
|
return nil
|
|
}
|
|
out := maps.Clone(m.ж)
|
|
for k, v := range out {
|
|
out[k] = slices.Clone(v)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Map provides a read-only view of a map. It is the caller's responsibility to
|
|
// make sure V is immutable.
|
|
type Map[K comparable, V any] struct {
|
|
// ж is the underlying mutable value, named with a hard-to-type
|
|
// character that looks pointy like a pointer.
|
|
// It is named distinctively to make you think of how dangerous it is to escape
|
|
// to callers. You must not let callers be able to mutate it.
|
|
ж map[K]V
|
|
}
|
|
|
|
// MapOf returns a view over m. It is the caller's responsibility to make sure V
|
|
// is immutable.
|
|
func MapOf[K comparable, V any](m map[K]V) Map[K, V] {
|
|
return Map[K, V]{m}
|
|
}
|
|
|
|
// Has reports whether k has an entry in the map.
|
|
// Deprecated: use Contains instead.
|
|
func (m Map[K, V]) Has(k K) bool {
|
|
return m.Contains(k)
|
|
}
|
|
|
|
// Contains reports whether k has an entry in the map.
|
|
func (m Map[K, V]) Contains(k K) bool {
|
|
_, ok := m.ж[k]
|
|
return ok
|
|
}
|
|
|
|
// IsNil reports whether the underlying map is nil.
|
|
func (m Map[K, V]) IsNil() bool {
|
|
return m.ж == nil
|
|
}
|
|
|
|
// Len returns the number of elements in the map.
|
|
func (m Map[K, V]) Len() int { return len(m.ж) }
|
|
|
|
// Get returns the element with key k.
|
|
func (m Map[K, V]) Get(k K) V {
|
|
return m.ж[k]
|
|
}
|
|
|
|
// GetOk returns the element with key k and a bool representing whether the key
|
|
// is in map.
|
|
func (m Map[K, V]) GetOk(k K) (V, bool) {
|
|
v, ok := m.ж[k]
|
|
return v, ok
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler.
|
|
func (m Map[K, V]) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(m.ж)
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler.
|
|
// It should only be called on an uninitialized Map.
|
|
func (m *Map[K, V]) UnmarshalJSON(b []byte) error {
|
|
if m.ж != nil {
|
|
return errors.New("already initialized")
|
|
}
|
|
return json.Unmarshal(b, &m.ж)
|
|
}
|
|
|
|
// AsMap returns a shallow-clone of the underlying map.
|
|
// If V is a pointer type, it is the caller's responsibility to make sure
|
|
// the values are immutable.
|
|
func (m Map[K, V]) AsMap() map[K]V {
|
|
if m.ж == nil {
|
|
return nil
|
|
}
|
|
return maps.Clone(m.ж)
|
|
}
|
|
|
|
// MapRangeFn is the func called from a Map.Range call.
|
|
// Implementations should return false to stop range.
|
|
type MapRangeFn[K comparable, V any] func(k K, v V) (cont bool)
|
|
|
|
// Range calls f for every k,v pair in the underlying map.
|
|
// It stops iteration immediately if f returns false.
|
|
func (m Map[K, V]) Range(f MapRangeFn[K, V]) {
|
|
for k, v := range m.ж {
|
|
if !f(k, v) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// MapFnOf returns a MapFn for m.
|
|
func MapFnOf[K comparable, T any, V any](m map[K]T, f func(T) V) MapFn[K, T, V] {
|
|
return MapFn[K, T, V]{
|
|
ж: m,
|
|
wrapv: f,
|
|
}
|
|
}
|
|
|
|
// MapFn is like Map but with a func to convert values from T to V.
|
|
// It is used to provide map of slices and views.
|
|
type MapFn[K comparable, T any, V any] struct {
|
|
// ж is the underlying mutable value, named with a hard-to-type
|
|
// character that looks pointy like a pointer.
|
|
// It is named distinctively to make you think of how dangerous it is to escape
|
|
// to callers. You must not let callers be able to mutate it.
|
|
ж map[K]T
|
|
wrapv func(T) V
|
|
}
|
|
|
|
// Has reports whether k has an entry in the map.
|
|
// Deprecated: use Contains instead.
|
|
func (m MapFn[K, T, V]) Has(k K) bool {
|
|
return m.Contains(k)
|
|
}
|
|
|
|
// Contains reports whether k has an entry in the map.
|
|
func (m MapFn[K, T, V]) Contains(k K) bool {
|
|
_, ok := m.ж[k]
|
|
return ok
|
|
}
|
|
|
|
// Get returns the element with key k.
|
|
func (m MapFn[K, T, V]) Get(k K) V {
|
|
return m.wrapv(m.ж[k])
|
|
}
|
|
|
|
// IsNil reports whether the underlying map is nil.
|
|
func (m MapFn[K, T, V]) IsNil() bool {
|
|
return m.ж == nil
|
|
}
|
|
|
|
// Len returns the number of elements in the map.
|
|
func (m MapFn[K, T, V]) Len() int { return len(m.ж) }
|
|
|
|
// GetOk returns the element with key k and a bool representing whether the key
|
|
// is in map.
|
|
func (m MapFn[K, T, V]) GetOk(k K) (V, bool) {
|
|
v, ok := m.ж[k]
|
|
return m.wrapv(v), ok
|
|
}
|
|
|
|
// Range calls f for every k,v pair in the underlying map.
|
|
// It stops iteration immediately if f returns false.
|
|
func (m MapFn[K, T, V]) Range(f MapRangeFn[K, V]) {
|
|
for k, v := range m.ж {
|
|
if !f(k, m.wrapv(v)) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// ContainsPointers reports whether T contains any pointers,
|
|
// either explicitly or implicitly.
|
|
// It has special handling for some types that contain pointers
|
|
// that we know are free from memory aliasing/mutation concerns.
|
|
func ContainsPointers[T any]() bool {
|
|
return containsPointers(reflect.TypeFor[T]())
|
|
}
|
|
|
|
func containsPointers(typ reflect.Type) bool {
|
|
switch typ.Kind() {
|
|
case reflect.Pointer, reflect.UnsafePointer:
|
|
return true
|
|
case reflect.Chan, reflect.Map, reflect.Slice:
|
|
return true
|
|
case reflect.Array:
|
|
return containsPointers(typ.Elem())
|
|
case reflect.Interface, reflect.Func:
|
|
return true // err on the safe side.
|
|
case reflect.Struct:
|
|
if isWellKnownImmutableStruct(typ) {
|
|
return false
|
|
}
|
|
for i := range typ.NumField() {
|
|
if containsPointers(typ.Field(i).Type) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isWellKnownImmutableStruct(typ reflect.Type) bool {
|
|
switch typ.String() {
|
|
case "time.Time":
|
|
// time.Time contains a pointer that does not need copying
|
|
return true
|
|
case "netip.Addr", "netip.Prefix", "netip.AddrPort":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|