Exit with error code in go?
Asked Answered
T

6

98

What's the idiomatic way to exit a program with some error code?

The documentation for Exit says "The program terminates immediately; deferred functions are not run.", and log.Fatal just calls Exit. For things that aren't heinous errors, terminating the program without running deferred functions seems extreme.

Am I supposed to pass around some state that indicate that there's been an error, and then call Exit(1) at some point where I know that I can exit safely, with all deferred functions having been run?

Tracietracing answered 23/9, 2013 at 16:9 Comment(2)
What about having a global variable state which is clean by default and set to dirty on non-fatal error. And before your main() exits, you can check for that variable. Not perfectly nice, but it might be the easiest solution in some cases. (I am glad comments can't be donwvoted :))Donegal
Yeah, that's basically what I ended up doing. I find it inelegant because I have to avoid deferring anything in main (because I still call Exit(1) to set the return code, and don't want to kill my deferred fn), so I stuck what used to be my main (which was only three lines, one of which is a defer) into a function. I'm hoping someone will have a better way. So far, one person has replied with os.Exit, and then deleted their reply when I commented out that I quote the os.Exit docs in my post, and now there's another answer that points me to os.Exit.Tracietracing
A
128

I do something along these lines in most of my real main packages, so that the return err convention is adopted as soon as possible, and has a proper termination:

func main() {
    if err := run(); err != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", err)
        os.Exit(1)
    }
}

func run() error {
    err := something()
    if err != nil {
        return err
    }
    // etc
}
Alvey answered 23/9, 2013 at 22:30 Comment(4)
What's the best method to test this code?Andeee
Run the binary in a functional test.Alvey
_, _ = fmt.Fprintf(…) to explicitly ignore the error returned by Fprintf.Forgiving
Will the above be valid for Linux as well as DOS?Assail
A
11

In Python I commonly use a pattern, which being converted to Go looks like this:

func run() int {
    // here goes
    // the code

    return 1
}

func main() {
    os.Exit(run())
}
Arp answered 24/9, 2013 at 15:44 Comment(1)
I consider this the best solution. You can return an error from the run() method and have main() deal with it without how it sees fit.Judicatory
M
6

I think the most clear way to do it is to set the exitCode at the top of main, then defer closing as the next step. That lets you change exitCode anywhere in main, and it's last value will be exited with:

package main

import (
    "fmt"
    "os"
)

func main() {
    exitCode := 0
    defer func() { os.Exit(exitCode) }()

    // Do whatever, including deferring more functions

    defer func() {
        fmt.Printf("Do some cleanup\n")
    }()

    func() {
        fmt.Printf("Do some work\n")
    }()

    // But let's say something went wrong
    exitCode = 1

    // Do even more work/cleanup if you want

    // At the end, os.Exit will be called with the last value of exitCode
}

Output:

Do some work
Do some cleanup

Program exited: status 1.

Go Playgroundhttps://play.golang.org/p/AMUR4m_A9Dw

Note that an important disadvantage of this is that you don't exit the process as soon as you set the error code.

Mylan answered 30/1, 2019 at 7:47 Comment(2)
Leaving this up, but after writing more Go code I think https://mcmap.net/q/216881/-exit-with-error-code-in-go (answer by Gustavo Niemeyer) is clearer and easier to use in basically all cases. Keeping track of function calls is a lot easier than keeping track of defersMylan
A downside of this approach is that if your code panics, it will never be printed out, because before panic propagates to the top, you exit the program in defer.Inlet
A
3

As mentioned by fas, you have func Exit(exitcode int) from the os package.

However, if you need the defered function to be applied, you always can use the defer keyword like this:

http://play.golang.org/p/U-hAS88Ug4

You perform all your operation, affect a error variable and at the very end, when everything is cleaned up, you can exit safely.

Otherwise, you could also use panic/recover: http://play.golang.org/p/903e76GnQ-

When you have an error, you panic, end you cleanup where you catch (recover) it.

Apostatize answered 23/9, 2013 at 17:37 Comment(1)
I think I understand what you mean in the first approach, but the example is a little confusing to me. Why defer fct1() and fct2()? This mean that they will be executed in reverse order! It seems you intend something more like this, or not?Papke
W
1

Yes, actually. The os package provides this.

package main

import "os"

func main() {
    os.Exit(1)
}

http://golang.org/pkg/os/#Exit

Edit: so it looks like you know of Exit. This article gives an overview of Panic which will let deferred functions run before returning. Using this in conjunction with an exit may be what you're looking for. http://blog.golang.org/defer-panic-and-recover

Woodwaxen answered 23/9, 2013 at 17:17 Comment(3)
I am pretty sure @Tracietracing knows about it.Donegal
Maybe I misread or he edited but I thought he was talking about something else.Woodwaxen
I think this is a good answer as it is pretty straightforward :)Chantry
B
1

Another good way I follow is:

if err != nil {
    // log.Fatal will print the error message and will internally call System.exit(1) so the program will terminate
    log.Fatal("fatal error message")
}
Baggy answered 19/1, 2023 at 4:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.