[go] errors: optimize Is type checks

6 views
Skip to first unread message

Chencheng Jiang (Gerrit)

unread,
Jun 23, 2026, 3:42:49 PM (4 days ago) Jun 23
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Chencheng Jiang has uploaded the change for review

Commit message

errors: optimize Is type checks

Reduce the dynamic type checks performed while walking an error tree. Instead of checking for Is before switching on Unwrap, handle the Is+Unwrap combinations directly in the type switch.

This preserves the existing order: compare comparable targets, call Is if present, then traverse Unwrap. Add tests for errors that implement both Is and Unwrap.

Also add reflectlite.TypeComparable so Is can test target comparability without constructing a reflectlite.Type interface just to call Comparable.

Add benchmarks for direct, wrapped, joined, and uncomparable-target cases.

On linux/amd64, Intel i7-14700KF, go1.27-devel:

name old time/op new time/op delta
IsDirectHit-20 2.627ns 2.451ns -6.72%
IsDirectMiss-20 3.754ns 3.018ns -19.59%
IsWrappedHit-20 19.23ns 17.54ns -8.76%
IsWrappedMiss-20 20.89ns 18.41ns -11.85%
IsJoinHit-20 27.76ns 23.80ns -14.28%
IsJoinMiss-20 33.74ns 26.40ns -21.75%
IsUncomparableTarget-20 5.657ns 5.426ns -4.07%
geomean 11.12ns 9.711ns -12.65%
Change-Id: I660499828a3d28a2ebf485e5fb7be624f2eee378

Change diff

diff --git a/src/errors/wrap.go b/src/errors/wrap.go
index e4a5ca3..aef3749 100644
--- a/src/errors/wrap.go
+++ b/src/errors/wrap.go
@@ -4,9 +4,7 @@

package errors

-import (
- "internal/reflectlite"
-)
+import "internal/reflectlite"

// Unwrap returns the result of calling the Unwrap method on err, if err's
// type contains an Unwrap method returning error.
@@ -47,7 +45,7 @@
return err == target
}

- isComparable := reflectlite.TypeOf(target).Comparable()
+ isComparable := reflectlite.TypeComparable(target)
return is(err, target, isComparable)
}

@@ -56,10 +54,33 @@
if targetComparable && err == target {
return true
}
- if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
- return true
- }
switch x := err.(type) {
+ case interface {
+ Is(error) bool
+ Unwrap() error
+ }:
+ if x.Is(target) {
+ return true
+ }
+ err = x.Unwrap()
+ if err == nil {
+ return false
+ }
+ case interface {
+ Is(error) bool
+ Unwrap() []error
+ }:
+ if x.Is(target) {
+ return true
+ }
+ for _, err := range x.Unwrap() {
+ if is(err, target, targetComparable) {
+ return true
+ }
+ }
+ return false
+ case interface{ Is(error) bool }:
+ return x.Is(target)
case interface{ Unwrap() error }:
err = x.Unwrap()
if err == nil {
diff --git a/src/errors/wrap_test.go b/src/errors/wrap_test.go
index 81c795a..6f0cfc3 100644
--- a/src/errors/wrap_test.go
+++ b/src/errors/wrap_test.go
@@ -23,6 +23,12 @@
poser := &poser{"either 1 or 3", func(err error) bool {
return err == err1 || err == err3
}}
+ poserWrap := poserWrapped{err: err1, f: func(err error) bool {
+ return err == err3
+ }}
+ poserMulti := poserMultiErr{errs: []error{err1}, f: func(err error) bool {
+ return err == err3
+ }}

testCases := []struct {
err error
@@ -42,6 +48,10 @@
{poser, err3, true},
{poser, erra, false},
{poser, errb, false},
+ {poserWrap, err1, true},
+ {poserWrap, err3, true},
+ {poserMulti, err1, true},
+ {poserMulti, err3, true},
{errorUncomparable{}, errorUncomparable{}, true},
{errorUncomparable{}, &errorUncomparable{}, false},
{&errorUncomparable{}, errorUncomparable{}, true},
@@ -92,6 +102,24 @@
return true
}

+type poserWrapped struct {
+ err error
+ f func(error) bool
+}
+
+func (p poserWrapped) Error() string { return "poserWrapped" }
+func (p poserWrapped) Is(err error) bool { return p.f(err) }
+func (p poserWrapped) Unwrap() error { return p.err }
+
+type poserMultiErr struct {
+ errs []error
+ f func(error) bool
+}
+
+func (p poserMultiErr) Error() string { return "poserMultiErr" }
+func (p poserMultiErr) Is(err error) bool { return p.f(err) }
+func (p poserMultiErr) Unwrap() []error { return p.errs }
+
func TestAs(t *testing.T) {
var errT errorT
var errP *fs.PathError
@@ -367,6 +395,75 @@
}
}

+func BenchmarkIsDirectHit(b *testing.B) {
+ err := errors.New("x")
+ for i := 0; i < b.N; i++ {
+ if !errors.Is(err, err) {
+ b.Fatal("Is failed")
+ }
+ }
+}
+
+func BenchmarkIsDirectMiss(b *testing.B) {
+ err := errors.New("x")
+ target := errors.New("y")
+ for i := 0; i < b.N; i++ {
+ if errors.Is(err, target) {
+ b.Fatal("Is succeeded")
+ }
+ }
+}
+
+func BenchmarkIsWrappedHit(b *testing.B) {
+ target := errors.New("x")
+ err := wrapped{"wrap", wrapped{"wrap", wrapped{"wrap", target}}}
+ for i := 0; i < b.N; i++ {
+ if !errors.Is(err, target) {
+ b.Fatal("Is failed")
+ }
+ }
+}
+
+func BenchmarkIsWrappedMiss(b *testing.B) {
+ target := errors.New("x")
+ err := wrapped{"wrap", wrapped{"wrap", wrapped{"wrap", errors.New("y")}}}
+ for i := 0; i < b.N; i++ {
+ if errors.Is(err, target) {
+ b.Fatal("Is succeeded")
+ }
+ }
+}
+
+func BenchmarkIsJoinHit(b *testing.B) {
+ target := errors.New("x")
+ err := multiErr{errorT{"a"}, multiErr{errorT{"b"}, target}, errorT{"c"}}
+ for i := 0; i < b.N; i++ {
+ if !errors.Is(err, target) {
+ b.Fatal("Is failed")
+ }
+ }
+}
+
+func BenchmarkIsJoinMiss(b *testing.B) {
+ target := errors.New("x")
+ err := multiErr{errorT{"a"}, multiErr{errorT{"b"}, errors.New("y")}, errorT{"c"}}
+ for i := 0; i < b.N; i++ {
+ if errors.Is(err, target) {
+ b.Fatal("Is succeeded")
+ }
+ }
+}
+
+func BenchmarkIsUncomparableTarget(b *testing.B) {
+ err := errorUncomparable{}
+ target := errorUncomparable{}
+ for i := 0; i < b.N; i++ {
+ if !errors.Is(err, target) {
+ b.Fatal("Is failed")
+ }
+ }
+}
+
func BenchmarkAs(b *testing.B) {
err := multiErr{multiErr{multiErr{errors.New("a"), errorT{"a"}}, errorT{"b"}}}
for i := 0; i < b.N; i++ {
diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go
index 88cc50d..1cacbc0 100644
--- a/src/internal/reflectlite/type.go
+++ b/src/internal/reflectlite/type.go
@@ -387,6 +387,12 @@
return toType(abi.TypeOf(i))
}

+// TypeComparable reports whether values of the dynamic type of i are comparable.
+// It is equivalent to TypeOf(i).Comparable() for non-nil i.
+func TypeComparable(i any) bool {
+ return abi.TypeOf(i).Equal != nil
+}
+
func (t rtype) Implements(u Type) bool {
if u == nil {
panic("reflect: nil type passed to Type.Implements")

Change information

Files:
  • M src/errors/wrap.go
  • M src/errors/wrap_test.go
  • M src/internal/reflectlite/type.go
Change size: M
Delta: 3 files changed, 131 insertions(+), 7 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I660499828a3d28a2ebf485e5fb7be624f2eee378
Gerrit-Change-Number: 793441
Gerrit-PatchSet: 1
Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Chencheng Jiang (Gerrit)

unread,
Jun 25, 2026, 5:40:13 PM (2 days ago) Jun 25
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Ian Lance Taylor and Russ Cox

Chencheng Jiang uploaded new patchset

Chencheng Jiang uploaded patch set #2 to this change.
Open in Gerrit

Related details

Attention is currently required from:
  • Ian Lance Taylor
  • Russ Cox
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I660499828a3d28a2ebf485e5fb7be624f2eee378
Gerrit-Change-Number: 793441
Gerrit-PatchSet: 2
Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
Gerrit-Reviewer: Russ Cox <r...@golang.org>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Russ Cox <r...@golang.org>
Gerrit-Attention: Ian Lance Taylor <ia...@golang.org>
unsatisfied_requirement
satisfied_requirement
open
diffy

Emmanuel Odeke (Gerrit)

unread,
Jun 26, 2026, 10:04:24 AM (21 hours ago) Jun 26
to Chencheng Jiang, goph...@pubsubhelper.golang.org, Russ Cox, Ian Lance Taylor, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Chencheng Jiang, Ian Lance Taylor and Russ Cox

Emmanuel Odeke voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Chencheng Jiang
  • Ian Lance Taylor
  • Russ Cox
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I660499828a3d28a2ebf485e5fb7be624f2eee378
Gerrit-Change-Number: 793441
Gerrit-PatchSet: 2
Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
Gerrit-Reviewer: Emmanuel Odeke <emma...@orijtech.com>
Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
Gerrit-Reviewer: Russ Cox <r...@golang.org>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Russ Cox <r...@golang.org>
Gerrit-Attention: Ian Lance Taylor <ia...@golang.org>
Gerrit-Attention: Chencheng Jiang <dorb...@gmail.com>
Gerrit-Comment-Date: Fri, 26 Jun 2026 14:04:16 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy

Chencheng Jiang (Gerrit)

unread,
Jun 26, 2026, 1:31:13 PM (17 hours ago) Jun 26
to goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, Emmanuel Odeke, Russ Cox, Ian Lance Taylor, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Emmanuel Odeke, Ian Lance Taylor and Russ Cox

Chencheng Jiang added 1 comment

Patchset-level comments
File-level comment, Patchset 2 (Latest):
Chencheng Jiang . resolved

@emma...@orijtech.com Can you rerun the trybot? I don't think this error is related to my commit.

Open in Gerrit

Related details

Attention is currently required from:
  • Emmanuel Odeke
  • Ian Lance Taylor
  • Russ Cox
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I660499828a3d28a2ebf485e5fb7be624f2eee378
Gerrit-Change-Number: 793441
Gerrit-PatchSet: 2
Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
Gerrit-Reviewer: Emmanuel Odeke <emma...@orijtech.com>
Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
Gerrit-Reviewer: Russ Cox <r...@golang.org>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Russ Cox <r...@golang.org>
Gerrit-Attention: Emmanuel Odeke <emma...@orijtech.com>
Gerrit-Attention: Ian Lance Taylor <ia...@golang.org>
Gerrit-Comment-Date: Fri, 26 Jun 2026 17:31:09 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy

Emmanuel Odeke (Gerrit)

unread,
Jun 26, 2026, 2:38:38 PM (16 hours ago) Jun 26
to Chencheng Jiang, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, Russ Cox, Ian Lance Taylor, Gopher Robot, golang-co...@googlegroups.com
Attention needed from Chencheng Jiang, Ian Lance Taylor and Russ Cox

Emmanuel Odeke voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Chencheng Jiang
  • Ian Lance Taylor
  • Russ Cox
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: go
Gerrit-Branch: master
Gerrit-Change-Id: I660499828a3d28a2ebf485e5fb7be624f2eee378
Gerrit-Change-Number: 793441
Gerrit-PatchSet: 2
Gerrit-Owner: Chencheng Jiang <dorb...@gmail.com>
Gerrit-Reviewer: Emmanuel Odeke <emma...@orijtech.com>
Gerrit-Reviewer: Ian Lance Taylor <ia...@golang.org>
Gerrit-Reviewer: Russ Cox <r...@golang.org>
Gerrit-CC: Gopher Robot <go...@golang.org>
Gerrit-Attention: Russ Cox <r...@golang.org>
Gerrit-Attention: Ian Lance Taylor <ia...@golang.org>
Gerrit-Attention: Chencheng Jiang <dorb...@gmail.com>
Gerrit-Comment-Date: Fri, 26 Jun 2026 18:38:33 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages