Переглянути джерело

types/views: add SliceMapKey[T]

views.Slice are meant to be immutable, and if used as such it
is at times desirable to use them as a key in a map. For non-viewed
slices it was kinda doable by creating a custom key struct but views.Slice
didn't allow for the same so add a method to create that struct here.

Updates tailscale/corp#17122

Signed-off-by: Maisem Ali <[email protected]>
Maisem Ali 2 роки тому
батько
коміт
b752bde280
2 змінених файлів з 70 додано та 0 видалено
  1. 33 0
      types/views/views.go
  2. 37 0
      types/views/views_test.go

+ 33 - 0
types/views/views.go

@@ -39,6 +39,9 @@ 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.ж)
@@ -168,6 +171,22 @@ func (v SliceView[T, V]) SliceTo(i int) SliceView[T, V] { return SliceView[T, V]
 // 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.ж {
@@ -190,6 +209,20 @@ type Slice[T any] struct {
 	ж []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] {

+ 37 - 0
types/views/views_test.go

@@ -166,3 +166,40 @@ func TestSliceEqual(t *testing.T) {
 		t.Error("got a[:2] == a[:1]")
 	}
 }
+
+// TestSliceMapKey tests that the MapKey method returns the same key for slices
+// with the same underlying slice and different keys for different slices or
+// with same underlying slice but different bounds.
+func TestSliceMapKey(t *testing.T) {
+	underlying := []string{"foo", "bar"}
+	nilSlice := SliceOf[string](nil)
+	empty := SliceOf([]string{})
+	u1 := SliceOf(underlying)
+	u2 := SliceOf(underlying)
+	u3 := SliceOf([]string{"foo", "bar"}) // different underlying slice
+
+	sub1 := u1.Slice(0, 1)
+	sub2 := u1.Slice(1, 2)
+	sub3 := u1.Slice(0, 2)
+
+	wantSame := []Slice[string]{u1, u2, sub3}
+	for i := 1; i < len(wantSame); i++ {
+		s0, si := wantSame[0], wantSame[i]
+		k0 := s0.MapKey()
+		ki := si.MapKey()
+		if ki != k0 {
+			t.Fatalf("wantSame[%d, %+v, %q) != wantSame[0, %+v, %q)", i, ki, si.AsSlice(), k0, s0.AsSlice())
+		}
+	}
+
+	wantDiff := []Slice[string]{nilSlice, empty, sub1, sub2, sub3, u3}
+	for i := 0; i < len(wantDiff); i++ {
+		for j := i + 1; j < len(wantDiff); j++ {
+			si, sj := wantDiff[i], wantDiff[j]
+			ki, kj := wantDiff[i].MapKey(), wantDiff[j].MapKey()
+			if ki == kj {
+				t.Fatalf("wantDiff[%d, %+v, %q] == wantDiff[%d, %+v, %q] ", i, ki, si.AsSlice(), j, kj, sj.AsSlice())
+			}
+		}
+	}
+}