グローバル変数に入れたと思ったらローカル変数に入っていてランタイムエラーが起きることがあります。
グローバル変数の扱いと定義・代入の動作が妙に厳しくて多少工夫が必要だったので対策方法のメモ。
まずサンプルとしてdbをグローバルにして関数内で呼び出すコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package main import ( "github.com/jinzhu/gorm" _ "github.com/mattn/go-sqlite3" ) type Test struct { gorm.Model } var ( db *gorm.DB ) func dbexe() { db.Create(&Test{}) //panic: runtime error: invalid memory address or nil pointer dereference } func main() { db, err := gorm.Open("sqlite3", "test.db") if err != nil { panic("failed to connect database") } defer db.Close() db.AutoMigrate(&Test{}) dbexe() } |
これは db, err := gorm.Open
でローカル変数db
に代入されるためです。
複数返値は1つでも未定義変数があれば:=
で代入と新規定義ができますが、グローバル変数は定義済みとして認識してくれません。
グローバル変数を明示的に指せればいいんですがそういったものはなさそうです。
この対策として3つの方法を試しました。
対策1
どちらも定義済み変数にしてしまえば代入でグローバル化できます。
1 2 3 4 5 6 |
var ( db *gorm.DB err error ) ... db, err = gorm.Open("sqlite3", "test.db") |
ただerr
のようなよく使う変数を使いまわすのはよくなさそう。
対策2
単純にローカル変数で受け取ってグローバルに渡す方法です。
1 2 |
_db, err := gorm.Open("sqlite3", "test.db") db = _db |
今回の場合ポインタの代入なので一行増えるだけです。
対策3
1つの型定義として変数を使う。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
type DB struct { db *gorm.DB } func (d *DB) init() { var err error d.db, err = gorm.Open("sqlite3", "test.db") if err != nil { panic("failed to connect database") } d.db.AutoMigrate(&Test{}) } func (d *DB) close() { d.db.Close() } func (d *DB) dbexe() { d.db.Create(&Test{}) } func main() { log.Print("start") db := &DB{} db.init() db.dbexe() db.close() } |
一番正統派な気がする。
この場合でも:=
を使った直接の代入はできないので注意。