Description
#21161 has seen a lot of proposals regarding error handling. Based on the discussions there and my experience writing go, I'd like to propose what I call a collect
statement. The root of this idea is that the complaints leveled against Go's error handling being repetitive/verbose are really complaints that checking for nil
in go code is repetitive/verbose.
I could see it being beneficial to have a way to "collect" to a variable - that is, try a series of statements until a non-nil
value is encountered. I've proposed syntax and control flow here, but I'm not particularly married to either. My proposed style isn't without reason, though. I feel that it fits with go's philisophy of being able to read code linearly, and having to explicitly announce intent in code.
A collect
statement would be of the form collect [IDENT] [BLOCK STMT]
, where ident must me an in-scope variable of a nil
-able type. Within a collect
statement, a special variable _!
is available as an alias for the variable being collected to. _!
cannot be used anywhere but as an assignment, same as _
. Whenever _!
is assigned to, an implicit nil
check is performed, and if _!
is not nil, the block ceases execution.
Theoretically, this would look something like this:
func TryComplexOperation() (*Result, error) {
var result *Result
var err error
collect err {
intermediate1, _! := Step1()
intermediate2, _! := Step2(intermediate1, "something")
// assign to result from the outer scope
result, _! = Step3(intermediate2, 12)
}
return result, err
}
which is equivalent to this code, which will compile (provided all necessary functions etc. are defined, of course)
func TryComplexOperation() (*Result, error) {
var result *Result
var err error
{
var intermediate1 SomeType
intermediate1, err = Step1()
if err != nil { goto collectEnd }
var intermediate2 SomeOtherType
intermediate2, err = Step2(intermediate1, "something")
if err != nil { goto collectEnd }
result, err = Step3(intermediate2, 12)
// if err != nil { goto collectEnd }, but since we're at the end already we can omit this
}
collectEnd:
return result, err
}
Aside from error handling, I think a collect
statement has some other value additions.
For example, imagine you'd like to try to retrieve a value in a series of ways
- retrieve from a local shared cache
- retrieve from a global shared cache
- instantiate a new object
With collect
you could write that as
// try several approaches for acquiring a value
func GetSomething() (s *Something) {
collect s {
_! = getLocalCache()
_! = getGlobalCache()
_! = new(Something)
}
return s
}
Also consider the scenario where you have several subsets of a larger operation, which can be annotated separately to give better error contxt
func TryComplexOperation() (*Result, error) {
var (
ioErr error
bytes1, bytes2 []byte
)
collect ioErr {
bytes1, _! = GetBytes()
bytes2, _! = GetOtherBytes()
}
if ioErr != nil {
return nil, fmt.Errorf("read error: %s", ioErr)
}
var (
parseErr error
target1 SomeStruct
target2 SomeStruct2
)
collect parseErr {
_! = json.Unmarshal(bytes1, &target1)
_! = json.Unmarshal(bytes2, &target2)
}
if parseErr != nil {
return nil, fmt.Errorf("parse error: %s", err)
}
return &Result{target1, target2}, nil
}
New syntax features required:
- keyword
collect
(I'd need to see how often this ident occurs in wild go code, I haven't run a scan on my GOROOT or GOPATH yet) - special ident
_!
(I've implemented this in the parser to see if it would be possible, and it's not terribly difficult to make this match as an ident without allowing users to declare it as a name for a variable, etc)
I think something like this would provide developers with a clean and concise way to express code that may encounter nil values, while keeping the control flow nice and linear.
I'd really like to see this discussed, even if it ends up not being adopted, because as I've developed the concept I've increasingly fallen in love with the idea. Because conceptually equivalent code can be created today (as demonstrated above), I think the impact on the language would be minimal except for at the syntactic level.