Generic result type in Go2
Go2 is going to support generics. Recently I decided to check how such generic, chainable result type (useful in sequential operation workflows) might look like.
Here is an example of the API I wanted to achieve:
Start(func() (int, error) { return 6, nil }).
Then(devide).
Then(multiply).
Then(increment)
If any of the steps from the process (device
, multiply
, increment
) end with error, the transformation should stop and last error should be preserved.
Before defining the generic Result
type, let’s define ResultError
first so errors from the Result
struct can be nicely print via json.MarshalIndent
:
In all code snippets below, I use syntax compatible with go2goplay
type ResultError struct {
error
}
func (re ResultError) MarshalJSON() ([]byte, error) {
return json.Marshal(re.Error())
}
Then generic Result
type (with exportable fields so they can be marshaled with json.MarshalIndent
) supporting Then
method and String
helper (for pretty prints):
type Result[T any] struct {
Value T
Err *ResultError
}
func (r Result[T]) String() string {
s, _ := json.MarshalIndent(r, "", " ")
return string(s)
}
func (r Result[T]) Then(fn func(T) (T, error)) Result[T] {
if r.Err != nil {
return r
}
v, err := fn(r.Value)
if err != nil {
return Result[T]{Err: &ResultError{err}}
}
return Result[T]{Value: v}
}
and the first step of the process / Result
constructor might look as follows:
func Start[T any](fn func() (T, error)) Result[T] {
v, err := fn()
if err != nil {
return Result[T]{Err: &ResultError{err}}
}
return Result[T]{Value: v}
}
Consequently the whole operation might be written as:
var r = Start(func() (int, error) { return 6, nil }).
Then(devide).
Then(multiply).
Then(increment)
fmt.Println(r)
Full Example can be run go2goplay.