Skip to content

proposal: Tracing errors with the tracer interface. #23271

@jaekwon

Description

@jaekwon

The error system is great, but sometimes it's hard to figure out where an error came from. Here's a proposal that could more or less be implemented today with a Golang source code transpiler, but would benefit from official Golang support. This is a proposal for Go1. (For Go2 I would rather propose that error be a tracer itself)

The proposal is to support the following:

  • a global interface like type terror interface {} (name can change)
  • in "pkg/errors", func Terror(e error) terror {...} as implemented below (name can change)
  • in "pkg/errors", keep the same signature in errors.New() error {...} but have it return a terror that can be run-time converted.
// New global type.
// Like `error`, a global interface.
// Trace calls `runtime.Caller` to get the file and line number.
type tracer interface {
    Trace()
}

// New global type.
// A new type of error that traces.
type terror interface {
    error
    tracer
}

// In pkg/errors.
func Terror(e error) terror {
  if t, ok := e.(terror); ok {
    return t
  }
  return newTerror(e)
}

// In pkg/errors.
// Same method signature, but actually returns a terror.
func New(...) error {..}

// Usage:
var e error = errors.New("bark")
var t1 terror := e // compile error
var t2 terror := e.(terror) // ok
var t3 terror := errors.Terror(e) // ok

Everything else can be handled by third-party libraries and code transpilers. What follows is just one example of syntax support from a transpiler with support for an assignment modifier !.

func foo() terror {
  var x terror = errors.New("bark").(terror)
  var x! terror = x // trace!
  var y error = x
  var z! error = x // compile error
  return x
}

x := foo()
x! = x // trace!
x! = <-ch // trace!

var y, z *terror
y = z
y! = z // trace! (but is this what we want?)
*y = x
*y! = x // trace!

type MyStruct struct {
  t terror
}
s := MyStruct{x}
s = MyStruct{field!:x} // trace!
s2 := s
s2! := s // compile error s.(tracer) is not ok

var i interface{}
i = x
i2 := i.(terror)
i3! := i.(terror) // trace!

func bar(t! terror) {...}
bar(x) // trace @ bar()!

The pain-point with error is that it doesn't trace. This seems like a way to inject tracing with minimal syntactic change and good performance.

The transpiler could also work without explicit !, but rather automatically every time something is assigned to a var _ terror.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions