2020/05/15

[golang]go getとgo.mod

go getするとき、pkgに落ちることもあれば、srcに落ちることもある。
ルールがよくわからんなー、と思っていたのだが、もしかしてgo.modによるのか?

[golang]selectとchan

golangで、chanの受信をしているコードがあった。
普通に?1行で受信しているのではなく、selectのcaseで受信していたのだ。
しかも複数chan!
受信って、バッファがあるときはどうだったか忘れたけど、ブロッキングになってるんじゃなかったっけ?


そうでもないらしい。

Go言語でチャネルとselect - Qiita
https://qiita.com/najeira/items/71a0bcd079c9066347b4#select

selectだからというわけでもないだろうが、selectシステムコールのように複数の待ち受けができるらしい。

もしselectがなかったら、どうするんだろう? 非同期はgoroutineで対処するものなのだろうから、どうしようもないのかな。。。

2020/05/04

[golang]手軽そうなkey value storeタイプのDB

golangの勉強をしているが、ちょっとしたデータ保存をしたいと思った。
大げさに言えばデータの永続化だが、プロセスを終了させてもデータが残っているようにしたいだけだ。

 

SQLiteを考えたが、大して検索などしないならkey value storeタイプでもよいだろう。
Cでlmdbを使っていたので、その辺で探してみた。

 

なんとなく手軽そうなので、bboltを使ってみよう。
boltdb/boltというリポジトリがオリジナルだったようだが、そちらはarchiveになっていた。


ちょっとチュートリアルをやって終わろうと思ったが・・・説明が長い!
DBだし、lmdbをベースにしているとなるとマルチプロセスやらマルチスレッドやらgoroutineやらあるので、ちゃんと説明がいるのだろう(読んでないけど)。

 

クロージャーとかいうものを使っているらしい。
他の言語でも聞いたことがある単語だが、まあスルーさせてもらおう。

よくわかってないが、bucketというものを作ってアクセスするようだ。Put/GetがBucketにしかないしね。
lmdbのmdb_dbi_open()で指定するnameのようなものだろうか。

01: package main
02: 
03: import (
04:     "log"
05: 
06:     "go.etcd.io/bbolt"
07: )
08: 
09: func main() {
10:     db, err := bbolt.Open("./db.bbolt", 0666, nil)
11:     if err != nil {
12:         log.Fatalf("%v\n", err)
13:     }
14:     defer db.Close()
15: 
16:     err = db.Update(func(tx *bbolt.Tx) error {
17:         b, err := tx.CreateBucket([]byte("MyBucket"))
18:         if err != nil {
19:             if err == bbolt.ErrBucketExists {
20:                 b = tx.Bucket([]byte("MyBucket"))
21:             } else {
22:                 log.Fatalf("create bucket: %v\n", err)
23:             }
24:         }
25:         err = b.Put([]byte("answer"), []byte("42"))
26:         return err
27:     })
28:     if err != nil {
29:         log.Fatalf("update: %v\n", err)
30:     }
31:     err = db.View(func(tx *bbolt.Tx) error {
32:         b := tx.Bucket([]byte("MyBucket"))
33:         v := b.Get([]byte("answer"))
34:         log.Printf("The answer is: %s\n", v)
35:         return nil
36:     })
37:     if err != nil {
38:         log.Fatalf("view: %v\n", err)
39:     }
40: }

 

まあ、サンプルが動いただけなので、これでいいのかどうかはよくわからん。
追々だ、追々。

追記:
今回の使い方だったら、CreateBucketIfNotExists()の方がよさそうだ。

 

最初、これをWindows10のWSLで動かそうとしたのだが、db.Update()の方は通るもののdb.View()の方で落ちてしまった。DBファイルが存在すると動く。
Linuxの方だと問題なく動くので、これはWSLでlmdbを動かしたときと同じくファイルシステムの問題だろう。
WSL2に期待しております。

[golang]named return values

golangでは戻り値とする変数名に予め名前を決めておくことができる。

Named return values
https://tour.golang.org/basics/7

named returnではなく、named return valuesなので、return valuesがnamedなのだ。

 

01: package main
02: 
03: import "fmt"
04: 
05: func namedReturn(param int) (a int, b string) {
06:     if param == 0 {
07:         a = 10
08:         b = "hello"
09:         return
10:     } else {
11:         return 99, "world"
12:     }
13: }
14: 
15: func main() {
16:     p, q := namedReturn(0)
17:     fmt.Printf("p=%d, q=%s\n", p, q)
18:     p, q = namedReturn(1)
19:     fmt.Printf("p=%d, q=%s\n", p, q)
20: }

かといって、名前を付けていたからといってreturnが空っぽ(naked returnというそうだ)でなければ指定した値を返すこともできる。

この辺は、namedにしたらnamedだけ、みたいなルールの方が良かったんじゃないかと思うが、まあ自分で気をつければいいか。

2020/05/03

[golang]go.modのreplaceを使ってlocal pathにする

ルールが分かればあきらめが付くはず、というわけで、golangのモジュールを理解しようと努めている。
いつか決定版の記事を書きたいものだが、それまでは試行錯誤が続く・・・。

 

golangで実装する場合、必ずgitなりなんなりを使うとは限らない。
どちらかといえば、使わない場合の方が多いかもしれない。
しかし、golangはおそらくバージョン管理システムを使う前提になっているので、そこで混乱してしまうのだろう(仮説)。

 

というわけで、これだ。

When should I use the replace directive?
https://github.com/golang/go/wiki/Modules#when-should-i-use-the-replace-directive

Can I work entirely outside of VCS on my local filesystem?
https://github.com/golang/go/wiki/Modules#can-i-work-entirely-outside-of-vcs-on-my-local-filesystem

Version Control Systemを使ってないローカルファイルシステムでもできますか?というところか。
こういうFAQがあるからVCSを使う前提なのだろうと感じているのだ。Googleはなんでもネット上においてもらう戦略なんだけど抜け道はあるよ、ということなのかしらね。

 

replace自体はそんなに難しいものではなく、左側になっているものを右側とみなしてgoコンパイラというかgoコマンドが処理する、というところだろう。
左側にはimportに書いたものを、右側には置き換えたい先を書く。基本的にネットにあるものを取得する前提なので、ローカルパスを指定する場合は、

  • /で始まるパス(絶対パス)
  • ./で始まるパス(カレントディレクトリからの相対パス)
  • ../で始まるパス(上位ディレクトリからの相対パス)

のいずれかになる。

 


ここまでは、まあいいだろう。

いずれGitHubにアップする予定で、2つのpackageを作っていたとしよう。
github.com/hirokuma/helloとgithub.com/hirokuma/world。
helloはworld側の関数を呼び出して文字列を取得し、それをfmt.Printf()するだけということにする。
world側はwordingとlistというpackageを内部で持っていて、wordingの方はlistから文字列を取得するようにしている。

 

これをGitHubにアップする前にビルドしたい。
そうすると、最終的にこうなった。

 

image

hello/go.mod

01: module github.com/hirokuma/hello
02: 
03: go 1.14
04: 
05: require github.com/hirokuma/world/wording v0.0.0
06: require github.com/hirokuma/world/list v0.0.0
07: 
08: replace github.com/hirokuma/world/wording => ../world/wording
09: replace github.com/hirokuma/world/list => ../world/list
  

 

  • helloからは直接参照していないworld/listについてもgo.modに書かないといけない
  • world/listにもreplaceがいる
  • world/wordingとworld/listにそれぞれgo.modがいる

replaceのwildcard指定については要望にありそうだが、v1.4には入っていないし、今後も不明だ。

 

じゃあ、いっそのことimportの方を相対パスで書けばいいやん、と思ったのだが、それはダメだった。
"non-local package"はエラーらしい。
packageのあるディレクトリにあるファイルはダメだとか、そんな記事があったけど確かめてはいない。

Relative import paths(これの2番目の方)
https://golang.org/cmd/go/#hdr-Relative_import_paths

>To avoid ambiguity, Go programs cannot use relative import paths within a work space.
などといっているので、work space(作業ディレクトリ?)を全然別にしてしまえばよいのかもしれん。

が、なんか面倒なので、gitのsubmoduleにして全部平たくしてしまう方がいいのか。

 

ちなみに、go.modにrequireを書いていない場合、go buildすると勝手に追加された。

01: module github.com/hirokuma/hello
02: 
03: go 1.14
04: 
05: replace (
06:     github.com/hirokuma/world/list => ../world/list
07:     github.com/hirokuma/world/wording => ../world/wording
08: )
09: 
10: require (
11:     github.com/hirokuma/world/list v0.0.0-00010101000000-000000000000 // indirect
12:     github.com/hirokuma/world/wording v0.0.0-00010101000000-000000000000
13: )

勝手に追加しないで、別ファイルにしてくれると非常に助かるのだけどね! せめて更新を許可したときだけ上書きとかにしてほしいんだけどね!