My Go packages sometimes require setup - it may be a connection pool for a DB, the creation of a *Regexp
struct that I use throughout the package, or any number of other things.
Given that these functions/setup routines have to be called in order for my packages to function correctly, that the setup may be computationally expensive to run, and that the setups only have to be run once, I usually put them an init()
function within my package - e.g.:
//Taken from example code here: https://stackoverflow.com/a/36358573/13296826
var myDb *sql.DB
func init() {
var err error
myDb, err = sql.Open("driver-name", "database=test1")
if err != nil {
panic(err)
}
}
To set up a (hypothetical) connection pool to a DB where the params are hardcoded. Or:
var myRegexp *regexp.Regexp
func init() {
//Will panic if MustCompile() fails.
myRegexp = regexp.MustCompile("[a-zA-Z0-9]{3}")
}
To compile a regexp struct for use throughout the package.
The two problems that I can see with this approach are:
- Package-level variables (even unexported ones) are seen by many as bad practice (e.g. https://stackoverflow.com/a/50844500/13296826 & https://www.reddit.com/r/golang/comments/8d8qes/when_is_it_okay_to_use_package_level_variables/) - this viewpoint is often down to the fact that a package-level variable can lead to race conditions (although both
sql.DB
andregexp.Regexp
are safe for concurrent use). - The
init()
functions in both of the above code examples can result in an error or panic. Since theinit()
is called when the package is initialised (and not directly invoked by callingpackage.init()
), handling an error or recovering from a panic isn't possible outside of theinit()
(according to this: https://stackoverflow.com/a/30659042/13296826).
So, is there a better way to handle initialising a package-level variable that accounts for any errors/panics that may occur?