Attribute equality

July 23, 2026

func (Attr) Equal

func (a Attr) Equal(b Attr) bool

Equal reports whether a and b have equal keys and values.

That’s a pretty obvious, and opaque statement. How is equality determined? We have to look at the source to determine:

func (a Attr) Equal(b Attr) bool {
	return a.Key == b.Key && a.Value.Equal(b.Value)
}

Okay, so it returns true if the keys are equal (that’s simple—they’re just strings) AND if the values are equal. But what does THAT mean?

Back to the source:

func (v Value) Equal(w Value) bool {
	k1 := v.Kind()
	k2 := w.Kind()
	if k1 != k2 {
		return false
	}
	switch k1 {
	case KindInt64, KindUint64, KindBool, KindDuration:
		return v.num == w.num
	case KindString:
		return v.str() == w.str()
	case KindFloat64:
		return v.float() == w.float()
	case KindTime:
		return v.time().Equal(w.time())
	case KindAny, KindLogValuer:
		return v.any == w.any // may panic if non-comparable
	case KindGroup:
		return slices.EqualFunc(v.group(), w.group(), Attr.Equal)
	default:
		panic(fmt.Sprintf("bad kind: %s", k1))
	}
}

Now that is informative. It does a type-wise comparison first, then a direct value comparison. This has some interesting implications:

  • Custom types compare differently.
	type myBool bool
	a := slog.Any("a", myBool(true))
	b := slog.Any("a", true)
	fmt.Println(a.Equal(b)) // Prints: false

Integers, even across Go types, compare directly:

	a := slog.Any("a", int32(0))
	b := slog.Any("a", int64(0))
	fmt.Println(a.Equal(b)) // Prints: true

But the most interesting implication, at least to me, is that non-compariable values panic:

	a := slog.Any("a", []int{1, 2, 3})
	b := slog.Any("a", []int{1, 2, 3})
	fmt.Println(a.Equal(b))

result:

panic: runtime error: comparing uncomparable type []int

goroutine 1 [running]:
log/slog.Value.Equal({{}, 0x4bb780?, {0x4bb780?, 0x1497ff76a048?}}, {{}, 0x10101cde30f7108?, {0x4bb780?, 0x1497ff76a060?}})
	/usr/local/go-faketime/src/log/slog/value.go:437 +0x3cd
log/slog.Attr.Equal(...)
	/usr/local/go-faketime/src/log/slog/attr.go:99
main.main()
	/tmp/sandbox393141892/prog.go:13 +0x1d9

I suppose this makes sense, since equality is literally undefined for non-comparable values, so neither true nor false makes sense. But it’s also surprising, becuase it means you can’t test for equality on unknown slog attributes or values, without the risk of a panic.


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


slog.GroupAttrs

Last time we looked at slog.Group, which accepts the variadic ...any as arguments. But there’s also a version that accepts slog.Attr values: func GroupAttrs func GroupAttrs(key string, attrs ...Attr) Attr GroupAttrs returns an Attr for a Group Value consisting of the given Attrs. GroupAttrs is a more efficient version of Group that accepts only Attr values. But notice there’s no mixed form of this, like there is for the standard logging functions.


Values

TIL Logger.LogAttrs is a thing! But what is that thing?? Yesterday I mentioned that using an slog.Attr can be marginally more efficient than using naked key/value pairs in a log call. While true, that glosses over what is likely to be a much more impactful performance consideration in certain applications… Attrs and Values … The value part of an Attr is a type called Value. Like an [any], a Value can hold any Go value, but it can represent typical values, including all numbers and strings, without an allocation.


Attrs

We’ve been talking about key/value pairs. The log/slog package has a name for these: Attr (short for “attribute”). And there’s more than one way to build an attribute: Attrs and Values An Attr is a key-value pair. The Logger output methods accept Attrs as well as alternating keys and values. The statement slog.Info("hello", slog.Int("count", 3)) behaves the same as slog.Info("hello", "count", 3) There are convenience constructors for Attr such as Int, String, and Bool for common types, as well as the function Any for constructing Attrs of any type.

Get daily content like this in your inbox!

Subscribe