5

In golang, when I import a module, its init() gets executed (before main() I assume?), it is possible some error is generated in this function. How can I capture these errors and handle them in my own code?

NeoWang
  • 17,361
  • 24
  • 78
  • 126

3 Answers3

6

Errors in Go are return values, as you may know. As init() does not return anything, the only alternative is to panic() in init if anything goes wrong.

A package that panics on init is arguably not very well designed, although there may be valid use cases for this.

Given the circumstances, recover() is not an option, because init is run before main. So if you can't edit the package in question, then you're out of luck.

This is one of the reasons why panic and recover should be used sparingly, only in situations where literally "panicking" makes sense.

@twotwotwo contributed the following quote from "effective Go" that describes this (for the init case):

if the library truly cannot set itself up, it might be reasonable to panic, so to speak

So: if your init function needs to report an error, then ask yourself if that code really belongs in init or would be better kept somewhere else. If it really has to be init, consider setting an error flag inside of the package and document that any client must check that error.

thwd
  • 23,956
  • 8
  • 74
  • 108
  • 1
    [Effective Go](https://golang.org/doc/effective_go.html#panic) agrees on panicking: "if the library truly cannot set itself up, it might be reasonable to panic, so to speak." – twotwotwo Jun 05 '15 at 06:48
  • Thank you, I have included your contribution in my answer. – thwd Jun 05 '15 at 06:54
5

Yes, package init() functions run before the main() function, see Package initialization in the language specification.

And no, you can't handle errors occurred in package init() functions. Even if you could, that would mean a package your program depends on failed to initialize and you wouldn't know what to expect from it.

Package init() functions have no return values and they can't panic in a meaningful way that was meant to recover from. If an init() function panics, the program terminates.

Since init() functions are not called by you (e.g. from the main() function), you can't recover from there. It is the responsibility of the package itself to handle errors during its initialization, not the users of the package.

One option to signal error happening during an init() is to store the error state in a variable (e.g. exported, or unexported but queryable by an exported function). But this should be used only if it is reasonable to continue, and this is also the task/responsibility of the package itself (to store/report the error) and not the users of the package. You can't do this without the cooperation of the package (you can't "catch" unhandled/unreported errors and panics).

icza
  • 389,944
  • 63
  • 907
  • 827
  • In my case, the module I imported expects some configuration file at a certain path, if the file is not present, it panics. I want to either capture the error (not possible) and record a log, or provide a fallback configuration file elsewhere. Can I change the conf file variable, and re-call the init() func? – NeoWang Jun 05 '15 at 07:43
  • @NeoWang You can't do anything before the package `init()` functions that your program depends on complete. You could alter the `init()` function of the external package. But if the package was written this way and you can't or don't want to modify the package source files, only thing you can do is make sure the expected configuration file exists before starting the application. – icza Jun 05 '15 at 07:54
  • @NeoWang if it was me, I'd either request an upstream change or fork the package to allow for something like `err := thirdPartyPackage.Config(filename/or/reader)` or at minimum to have an environment variable to override the location. Depending on what the third party package is/does, having an API with a statically located config file that needs to be present (and presumably also have "correct" contents) for startup of someone elses program is a bad design. – Dave C Jun 05 '15 at 15:32
1

Not directly, but you could using something like this:

package mypkg

var InitErr error
var Foo MyFoo

func init() {
    Foo, InitErr = makeInitialisation()
    // ...
}

And then in your main:

package main

import "foo/bar/mypkg"

func main() {
    if (mypkg.InitErr != nil) {
        panic(err)
    }
    // ...
}
Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • 3
    I understand your idea but there's a bug: `:=` will declare a local `InitErr` in init. – thwd Jun 05 '15 at 06:46
  • 1
    IMO, if you need this then you'd be much better off moving to explicitly controlled initialization. E.g. `err := mypkg.Init()` or `x, err := mypkg.OpenSomething()` (kinda like `sql.Open`) or whatever. – Dave C Jun 05 '15 at 15:27