Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Built-in helper for "Must" pattern in Go

Tags:

go

Is there a more built-in wrapper to make a function that returns (X, error) successfully execute or abort, like regexp.MustCompile?

I'm talking about something like this, but more "built-in".

like image 527
Alex B Avatar asked Sep 15 '12 04:09

Alex B


3 Answers

There is not. The best you'll get is something like this:

func Must(fn func() (interface{}, error)) interface{} {
    v, err := fn()
    if err != nil {
        log.Fatalln(err)
    }
    return v
}

Then to use it:

Must(func() (interface{}, error) {
    return template.ParseGlob(pattern)
}).(*template.Template)

Assuming that template.ParseGlob(pattern) is the call you wanted to wrap.

Go does not have parametric polymorphism, so this kind of code will end up requiring type assertions to restore the original type and so (in my opinion) is more effort than it's worth. The tidiest, idiomatic error handling you'll get for long chains of potential failure is simply to check for an error, and return it. Defer your cleanup handlers:

func MyFunc() (err error) {
    a, err := blah1()
    if err != nil {
        return
    }
    defer a.Close()
    b, err := blah2(a)
    if err != nil {
        return
    }
    defer b.Close()
    // ad nauseam
}

Long and tedious, but at least it's explicit and easy to follow. Here are two modules I wrote that are crying out for parametric polymorphism that might give you some ideas for dealing without it:

  • bitbucket.org/anacrolix/dms/futures
  • bitbucket.org/anacrolix/dms/cache
like image 155
Matt Joiner Avatar answered Nov 17 '22 13:11

Matt Joiner


I don't think a built-in mechanism would make sense since you could very well handle a non-nil error in various ways, as does the examples in the template package itself: see "text/template/examplefiles_test.go", illustrating 2 different usage of 'err':

// Here starts the example proper.
    // T0.tmpl is the first name matched, so it becomes the starting template,
    // the value returned by ParseGlob.
    tmpl := template.Must(template.ParseGlob(pattern))

    err := tmpl.Execute(os.Stdout, nil)
    if err != nil {
        log.Fatalf("template execution: %s", err)
    }
    // Output:
    // T0 invokes T1: (T1 invokes T2: (This is T2))

In the particular case of the helper function (*Template) Must(), transforming an error into an exception (panic) isn't always the right course for all go programs (as debated in this thread), and to cover all the possible way to handle an error would mean to create a lot of "built-in" mechanisms.

like image 24
VonC Avatar answered Nov 17 '22 14:11

VonC


Since Go 1.18 we can define typed Must instead of interface{}:

func Must[T any](obj T, err error) T {
    if err != nil {
        panic(err)
    }
    return obj
}

How to use: https://go.dev/play/p/ajQAjfro0HG

func success() (int, error) {
    return 0, nil
}

func fail() (int, error) {
    return -1, fmt.Errorf("Failed")
}

func main() {
    n1 := Must(success())
    fmt.Println(n1)
    var n2 int = Must(fail())
    fmt.Println(n2)
}

Must fails inside main, when fail() returns non-nil error

You can even define Mustn for more than 1 return parameter, e.g.

func Must2[T1 any, T2 any](obj1 T1, obj2 T2, err error) (T1, T2) {
    if err != nil {
        panic(err)
    }
    return obj1, obj2
}
like image 1
Pak Uula Avatar answered Nov 17 '22 14:11

Pak Uula