Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go method chaining and error handling

I want to create a method chaining API in Go. In all examples I can find the chained operations seem always to succeed which I can't guarantee. I therefore try to extend these to add the error return value.

If I do it like this

package main

import "fmt"

type Chain struct {
}

func (v *Chain)funA() (*Chain, error ) {
    fmt.Println("A")
    return v, nil
}
func (v *Chain)funB() (*Chain, error) {
    fmt.Println("B")
    return v, nil
}
func (v *Chain)funC() (*Chain, error) {
    fmt.Println("C")
    return v, nil
}

func main() {
    fmt.Println("Hello, playground")
    c := Chain{}
    d, err := c.funA().funB().funC() // line 24
}

The compiler tells me chain-err-test.go:24: multiple-value c.funA() in single-value context and won't compile. Is there a good way so funcA, funcB and funcC can report an error and stop that chain?

like image 435
johannes Avatar asked Dec 04 '14 15:12

johannes


3 Answers

Is there a good way so funcA, funcB and funcC can report an error and stop that chain?

Unfortunately, no, there is no good solution to your problem. Workarounds are sufficiently complex (adding in error channels, etc) that the cost exceeds the gain.

Method chaining isn't an idiom in Go (at least not for methods that can possibly error). This isn't because there is anything particularly wrong with method chains, but a consequence of the idiom of returning errors instead of panicking. The other answers are workarounds, but none are idiomatic.

Can I ask, is it not idiomatic to chain methods in Go because of the consequence of returning error as we do in Go, or is it more generally a consequence of having multiple method returns?

Good question, but it's not because Go supports multiple returns. Python supports multiple returns, and Java can too via a Tuple<T1, T2> class; method chains are common in both languages. The reason these languages can get away with it is because they idiomatically communicate errors via exceptions. Exceptions stop the method chain immediately and jump to the relevant exception handler. This is the behavior the Go developers were specifically trying to avoid by choosing to return errors instead.

like image 105
weberc2 Avatar answered Sep 20 '22 17:09

weberc2


You can try like that: https://play.golang.org/p/dVn_DGWt1p_H

package main

import (
    "errors"
    "fmt"
)

type Chain struct {
    err error
}

func (v *Chain) funA() *Chain {
    if v.err != nil {
        return v
    }
    fmt.Println("A")
    return v
}
func (v *Chain) funB() *Chain {
    if v.err != nil {
        return v
    }
    v.err = errors.New("error at funB")
    fmt.Println("B")
    return v
}
func (v *Chain) funC() *Chain {
    if v.err != nil {
        return v
    }
    fmt.Println("C")
    return v
}

func main() {
    c := Chain{}
    d := c.funA().funB().funC() 
    fmt.Println(d.err)
}
like image 23
Kiura Avatar answered Sep 20 '22 17:09

Kiura


If you have control over the code and the function signature is identical you can write something like:

func ChainCall(fns ...func() (*Chain, error)) (err error) {
    for _, fn := range fns {
        if _, err = fn(); err != nil {
            break
        }
    }
    return
}

playground

like image 32
OneOfOne Avatar answered Sep 19 '22 17:09

OneOfOne