You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I am not certain this idea is at the proposal stage, but I would like to get a gauge on community interest.
I propose adding a new top level function to the errors package named Match. The function signature of Match would be Match[T any](v T, err error) MatchClause[T], which would allow the caller to frequently pass a function or method invocation inline, like so errors.Match(os.Open("test")). Most of what I find interesting about this idea is contained in MatchClause[T]. The MatchClause struct would expose to methods:
Cases(cases ...Case) (T, error)
CasesT(cases ...CaseT[T]) (T, error)
If the error provided to Match is nil, then Cases or CasesT would return immediately. Otherwise, it would iterate over the provided cases until it either reaches the end, at which point it returns the original error, or a case matches.
In my opinion, this functionality becomes most useful when there are one or more errors.As cases. It also allows for more streamlined variable assignment (in my eyes).
These new functions/types could then be used like so:
package errors
// Case represents a test case for an error.typeCaseinterface {
// Test takes an error and returns a bool indicating if the error matches the case,// and an error to replace the original error with if it does.Test(error) (bool, error)
}
// CaseT represents a test case for an error.typeCaseT[Tany] interface {
// Test takes an error and returns a bool indicating if the error matches the case,// a value of type T if it does, and an error to replace the original error with if it does.Test(error) (bool, T, error)
}
typecaseFuncfunc(error) (bool, error)
func (mcaseFunc) Test(errerror) (bool, error) {
returnm(err)
}
typecaseTFunc[Tany] func(error) (bool, T, error)
func (mcaseTFunc[T]) Test(errerror) (bool, T, error) {
returnm(err)
}
// CaseIs returns a Case that checks if the actual error is target, utilizing// [errors.Is]. If it is, the callback is invoked with the actual error.funcCaseIs(targeterror, callbackfunc(error) error) Case {
returncaseFunc(func(errerror) (bool, error) {
ifIs(err, target) {
returntrue, callback(err)
}
returnfalse, nil
})
}
// CaseAs returns a Case that checks if the actual error is of type T, utilizing// [errors.As]. If it is, the callback is invoked with the target of [errors.As].funcCaseAs[Terror](callbackfunc(T) error) Case {
returncaseFunc(func(errerror) (bool, error) {
vartargetTifAs(err, &target) {
returntrue, callback(target)
}
returnfalse, nil
})
}
// CaseDefault returns a Case that always matches and returns the result of the callback.// Note: If provided, this should be the last Case provided as an argument to// MatchClause.Cases, as any cases after this will never be tested.funcCaseDefault(callbackfunc(error) error) Case {
returncaseFunc(func(errerror) (bool, error) {
returntrue, callback(err)
})
}
// CaseIsT returns a CaseT that checks if the actual error is target, utilizing// [errors.Is]. If it is, the callback is invoked with the actual error.funcCaseIsT[Tany](targeterror, callbackfunc(error) (T, error)) CaseT[T] {
returncaseTFunc[T](func(errerror) (bool, T, error) {
varvTifIs(err, target) {
v, err=callback(err)
returntrue, v, err
}
returnfalse, v, err
})
}
// CaseAsT returns a CaseT that checks if the actual error is of type U, utilizing// [errors.As]. If it is, the callback is invoked with the target of [errors.As].funcCaseAsT[Terror, Uany](callbackfunc(T) (U, error)) CaseT[U] {
returncaseTFunc[U](func(errerror) (bool, U, error) {
var (
vUtargetT
)
ifAs(err, &target) {
v, err=callback(target)
returntrue, v, err
}
returnfalse, v, err
})
}
// CaseDefaultT returns a CaseT that always matches and returns the result of the callback.// Note: If provided, this should be the last CaseT provided as an argument to// MatchClause.CasesT, as any cases after this will never be tested.funcCaseDefaultT[Tany](callbackfunc(error) (T, error)) CaseT[T] {
returncaseTFunc[T](func(errerror) (bool, T, error) {
v, err:=callback(err)
returntrue, v, err
})
}
// Match is a function that takes a value and an error and returns a MatchClause[T].// This allows for inline/fluent error handling.funcMatch[Tany](vT, errerror) MatchClause[T] {
returnMatchClause[T]{v: v, err: err}
}
typeMatchClause[Tany] struct {
vTerrerror
}
// Cases takes a variadic number of Case and returns a value and an error,// after testing each case in the order they were provided. If no case matches, the original// error is returned.func (mMatchClause[T]) Cases(cases...Case) (T, error) {
ifm.err==nil {
returnm.v, nil
}
for_, mapper:=rangecases {
ifok, newErr:=mapper.Test(m.err); ok {
returnm.v, newErr
}
}
returnm.v, m.err
}
// CasesT takes a variadic number of CaseT and returns a value and an error,// after testing each case in the order they were provided. If no case matches, the original// error is returned.func (mMatchClause[T]) CasesT(cases...CaseT[T]) (T, error) {
ifm.err==nil {
returnm.v, nil
}
for_, mapper:=rangecases {
ifok, v, newErr:=mapper.Test(m.err); ok {
returnv, newErr
}
}
returnm.v, m.err
}
```
</details>
The text was updated successfully, but these errors were encountered:
So this is really just a matter of personal preference. I just wonder if something like this idea could get us closer to improved error handling down the road
Your second example is fewer lines of code, fewer concepts, and fewer tokens, and has the virtue of being supported already. For these reasons I much prefer it.
Proposal Details
I am not certain this idea is at the proposal stage, but I would like to get a gauge on community interest.
I propose adding a new top level function to the errors package named
Match
. The function signature ofMatch
would beMatch[T any](v T, err error) MatchClause[T]
, which would allow the caller to frequently pass a function or method invocation inline, like soerrors.Match(os.Open("test"))
. Most of what I find interesting about this idea is contained inMatchClause[T]
. TheMatchClause
struct would expose to methods:Cases(cases ...Case) (T, error)
CasesT(cases ...CaseT[T]) (T, error)
If the error provided to
Match
is nil, thenCases
orCasesT
would return immediately. Otherwise, it would iterate over the provided cases until it either reaches the end, at which point it returns the original error, or a case matches.In my opinion, this functionality becomes most useful when there are one or more
errors.As
cases. It also allows for more streamlined variable assignment (in my eyes).These new functions/types could then be used like so:
Example use case
Example implementation
The text was updated successfully, but these errors were encountered: