Description
Author background
I am an experienced Go programmer who programmed in Go for over 10 years. I have also used many other languages such as PHP, JavaScript, ObjectPascal (Delphi), as well as loads of others which I am not so proficient or have not been using them for decades.
Related proposals
This is a proposal about Go's Error Handling. I have seen many proposals on this topic, for example: #56165 (try
statement), #37141 (pass
statement), most of them focus on simplifying the infamous go boilerplate:
if err != nil {
return err //or do something else, the "handler"
}
This proposal is different in that it is a schema about default error handling, which I think may not only simplify coding, but also improve code quality in the long run.
Proposal
A new keyword trap
should be added in order to define a "default" error handler for all error-generating statements, inside a func. It could be used in any of the following forms:
trap &error
trap func(error)
trap
func Func1() (err error) {
trap &err
f, _ := os.Open("some file")
defer f.Close()
...
}
func Func2() (ok bool) {
trap func(err error) {
fmt.Println("ERROR:", err)
ok = false
}
ok = true
taskWhichReturnsErr()
}
func Func3() {
trap //or, trap nil
f, _ := os.Create()
defer f.Close()
//...
}
The essence of this statement is that it automatically catches error, in the following two cases:
- if any statement below it generated an unhandled error (i.e. panics)
- if any statement below it returns an error, as the only or last return value, which is deliberately ignored by "_", i.e.
f, _ := os.Open(...)
, or implicitly, by not assigning to any variable, i.e.f.Close()
.
In the Func1()
example, trapped error is assigned to the err
variable, in Func2()
, passed to the handler, and in Func3()
panic-up to the caller. It is worth pointing out that the last case, direct panic-up is especially useful, because of the trap facility is designed to automatically check for errors that is ignored by the programmer, e.g. does not check the return of f.Close(), os.Rename(), db.Exec()...
Compatibility
This feature is supposed to be 100% compatible with Go1, because it ONLY works in case the programmer does NOT handle error. Consider the following case:
func Func1() {
trap
f, err := os.Open()
_ = err //even if there are errors, it will NOT be trapped!
defer f.Close() //if Close() generated error, it WILL be trapped,
//unless the error is explicitly assigned to variable other than "_"
}
Orthogonality
This feature may be implemented by the compiler using mechanism similar to "defer". It may be used multiple times, and be mixed with "defer".
Further Improvement
Although not mandatory, I think the trapped err should by default include a stacktrace to make it useful, just like errors.WithStack() in the errors package. I propose the following interface:
TracedError interface{
Err() error //the underlying/direct error
Error() string
Stack() []string //returns stack information of the error
}
Another reason that the error should be naturally stack-traced is that the panic/recover
mechanism accept/returns an interface{}
, not error
. The trap mechanism may be implemented as:
func Func() {
defer func(v interface{}) {
switch e := recover().(type) {
case nil:
case *error:
*e = trace(fmt.Errorf("%v", v)) //change "any" into error, add trace info
case func(error):
e(trace(fmt.Errorf("%v", v)))
default: //compiler report error
}
}
}
The above sample shows one difference/incompatibility between defer and trap: trap accept an argument, while defer not.
Alternatively, another keyword might be used: trace
to indicated that the trapped error should have stack information, like so:
func Func1() {
trap func(err) {...} //this err does not have stack information
...
}
func Func2() (err error) {
trace &err //this err has stack information
}
Personally, I think trapped error should always include stack information.
Costs
This proposal may not make Go easier or hearder to learn, and the cost is minimal, for the sake of the language specification.
As to the impact of the compiler or the tool chain, I think it indeed may have some work to do, especially on the tool chain, sorry I don't know which tool should be modified to add such feature, but I would expect VS Code (the language server?) should warn the programmer if it sees code such as f.Close()
without assigning
its return value:
error returned by f.Close() is silently ignored, consider add "trap", or check error values explicitly.