2020/02/24

[golang]パッケージ名? ディレクトリ名?

以前、こんな記事を書いた。

hiro99ma blog: [golang]go runの引数はなんなのか
https://hiro99ma.blogspot.com/2020/02/golanggo-run.html

とりあえずmainパッケージを含んでいるディレクトリを指定しておけばいいんじゃないか、くらいで終わった。

 

そういえばimportなんかは基本的にsrc以下からのパスで書いていたはずだ。
import "github.com/hirokuma/gogo/cmd"、とか。
mainパッケージを含んでいるディレクトリを書くということであれば、cmdディレクトリの中がmainパッケージになっていて、どれかのgoファイルの中にmain()があるんだろう。

いつも「mainパッケージ」と呼んでいるが、よく考えれば正式な名前は「github.com/hirokuma/gogo/cmd/mainパッケージ」なのだろうか?

 

runの引数は実行できれば良かったのだが、importとなるとまた話が変わってくることに気付いたのだった。


ここを見よう。

How to Write Go Code - The Go Programming Language
https://golang.org/doc/code.html

読む前の注意点。

This document assumes that you are using Go 1.13 or later and the GO111MODULE environment variable is not set

1.13より古い場合はアーカイブ参照だ。
ページの最終更新日が書いてあると助かるのだが、載ってなさそうだ。
google検索からすると、2020年1月13日みたいだ。

 

翻訳するなら自動でもよさそうなので、気がついたところだけ抜粋しよう。

  • パッケージ、リポジトリ、モジュール
    • たぶん、ファイル単体を別とすれば、パッケージが最小単位
    • リポジトリは1つ以上のモジュールを含む
      • 通常、リポジトリのルートにある1つのモジュールが含まれる
      • go.modとディレクトリのことが書いてあるが、なんか分かりづらいぞ。。
    • モジュールは関連するパッケージの集合
  • A package is a collection of source files in the same directory that are compiled together.
    • パッケージの単位は同一ディレクトリなのね。
  • Each module's path not only serves as an import path prefix for its packages, but also indicates where the go command should look to download it.
    • import pathってことは、importに書いているあれはパスの方なのね。
  • An import path is a string used to import a package. A package's import path is its module path joined with its subdirectory within the module.
    • import pathは、パスといっても純粋なディレクトリじゃなくて、ディレクトリ構造+パッケージ名、なのか

 

よし、なんとなくわかった!
わかったんじゃないだろうか。。。

いろいろコードの書き方に抜け道(コンパイルエラーにならず上記の書き方にしない方法)はあるのだろうが、上記の書き方をしておけば悪くないということだ。

C言語の時は使用期間が長いこともあり、自分でなんとなくルールを決めていった。
Java言語ではディレクトリ構成も含めてきっちり決められていたので、その通りにやりつつも自由度がないなあ、と思った。
その中間くらいなのかな。

[vscode]vscodeとWSLとgolang

仕事でもvscodeを使っているが、それはLinux上で動かしている。
家で使う場合は、VirtualBox上にLinuxを立ち上げ、その環境で動かしている。

最近、何回かgolangの記事を書いたが、それらはVirtualBox + Linuxでやっていて、vscodeを使っていた。
が、golangであればWindowsでも使えるので、そっちの方が軽いだろう。

それに、Linux環境も併用したいなら、WSLを使えばよい。
というわけで、vscodeはWindowsで、それ以外の環境はWSLで作ろうとした。

 

環境はできたのだが、Linuxで動かしていたときはgoのextensionをインストールすると、goファイルを初めて開いたときにツール類のインストールが自動で行われていたものが、Windows + WSLでは行われなかったのだ。
ダイアログで"go env"ができない、みたいなことを言われるのだ。

これは・・・Windows側にgoのバイナリが入っていないからか?

 

と思ったら、単にWSL側の環境変数にgoのぱすをとおしていなかっただけだった。。。
apt installしたから自動で追加されているかと思ったのだが、add repositoryした方だったからか入っていなかった。
PATHに追加すれば、インストールもできました。

よくわからないが、gopkgsのインストールは失敗したので手動でインストールした。
OUTPUTタブに失敗したコマンドが出てくるので、助かるね。

 

WSL側で"explorer.exe ."とすると\\wslの状態で立ち上がる。
同様に、"code.exe ."で立ち上がってくれるかと思ったが、こちらは最後に起動したremote状態がWSLであれば、WSLで起動しているようだ。
vscodeを複数開くと、最初のウィンドウサイズは保存してくれているサイズになるが、2枚目以降は強制的に画面の4分の1くらいになるようなので、最初からWSL状態で立ち上がってくれるのは助かる。
コマンドラインから選択できるようであればショートカットを作っておくのだが、まあよかろう。

[vscode]Open EditorsのSow/Hideがコンテキストメニューになった

Visual Studio Codeのナビゲーション部分(と呼ぶのか?)に、ファイル一覧を表示する"Explorer"がある。
その内部に表示される項目がいくつかあるが、"Open Editors"はsettings.jsonで非表示にできるようになっていたと思う。
"explorer.openEditors.visible"だったと思う。

非表示にしたい理由は、編集するタブが増えるとOpen Editorsの項目が増えて、その下に表示されるディレクトリ表示の位置が下がってしまうからだ。
たたむこともできるのだけど、たたむくらいだったら非表示でいいか、と。

 

 

今までその設定をしていたのだが、久々にWindowsのVisual Studio Code v1.42.1を起動するとOpen Editorsが表示されていた。

どうしたらいいんじゃんろうかと触っていたら、コンテキストメニューで設定できるようになっていた。

image

以前からできていたのかもしれないが、気付かなかった。

2020/02/23

[golang]mutexもある

いかんいかん、golang気を抜くと学習を忘れてしまう。
そして、気を抜くとgolang自体忘れかねない。。。

 

goroutineをちょっとだけやったが、そのときはデータの受け渡しにchanを使った。
しかし、スライスを渡せる以上、メモリの共有もできるということだ。
ならばセマフォなりmutexなりの機構があるはずだ。


mutexは、ある。

Go でロックする - Qiita
https://qiita.com/mrasu/items/7531a5a28e9fda77aa5e#%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%AB

 

まあ、mutexはありがちだからよいとして、WaitGroupというものがあるそうだ。

WaitGroupは「Addした回数Doneされるまで待ち続ける」という命令を書けるようにした仕組みです。

セマフォっぽい使い方になるのかな?

waitgroup.goにはAdd() / Done() / Wait()があって、関連するオブジェクトをAddするとかいうものではなく、単にカウントだけの管理をしているようだ。
ちなみにDone()は、Add(-1)になってる。

 

かといって自分で好きな負の値を書いていくのは良くない気がする。
race.ReleaseMerge()は1回だけだし。
いや、そもそもrace.Enableとかやってるけど、raceってWaitGroupオブジェクトごとに持ってるんじゃなくて、importしてるからグローバルな値よね?

・・・race.Enableはconst値じゃった。

 

まだgolangのソースコードに慣れていないのだが、データの持ち方としては、こんな感じだろうか。

  • 外側にある大文字の変数名は、公開されたグローバル変数
  • 外側にある小文字の変数名は、公開されてないグローバル変数(Cでいうところのstatic変数)
  • 関数名の前に括弧して変数定義がある場合は、クラス変数っぽいやつ
  • 関数の引数
  • 関数内で宣言したスタックとかmakeした変数

 

goroutineで共有するなら、グローバル変数やmakeした変数がよかろう。
関数の引数は、元がどうやって宣言されているかによるから、なんともいえんな。


というわけで、簡単にグローバル変数を試すことにした。
mutexの話をしているはずなのに、グローバル変数を試すことになるとは。。

 

src/cmd_ex5/main.go

01: package main
02: 
03: import "ex5"
04: 
05: var MainVal int
06: 
07: func main() {
08: 	MainVal = 20
09: 	ex5.Ex5Val = 30
10: 	ex5.FuncEx5b()
11: 	MainVal = 10
12: 	ex5.Ex5Val = 55
13: 	ex5.FuncEx5b()
14: }
  

 

src/ex5/ex5b.go

01: package ex5
02: 
03: import (
04: 	"fmt"
05: )
06: 
07: var Ex5Val int
08: 
09: func FuncEx5b() {
10: 	fmt.Printf("FuncEx5b: %d\n", Ex5Val)
11: 	//fmt.Printf("MainVal: %d\n", main.MainVal) //compile error
12: }
  

 

$ go run cmd_ex5
FuncEx5b: 30
FuncEx5b: 55

 

main関数は、mainパッケージに置く。
mainパッケージに置いたグローバル変数は、mainパッケージの担当だろう。
しかし、ex5パッケージからはmain.MainValにアクセスできなかった。
逆はできるので、言語仕様だろうか。

まあ、パッケージにpublicなグローバル変数を用意するよりは、getter/setterなんかでアクセスさせた方が安全な気はするが、言語的にできないのとは違う。

ディレクトリ名をmainにしなかったせいか?
しかし、実行ファイルをいくつか作りたい場合はcmd以下にディレクトリを作ろう、というのが推奨だったと思う。
cmd/ex5/mainなんかにしてみたが、ダメだ。
main関数があるとダメなのかと思ったが、そうでもない。

 

うーん、わからん。。

2020/02/16

[golang]配列はデータまるまる値渡し

前回の続き。

前回はスライスだけだったので、今回は配列をやろう。


01: package main
02: 
03: import "fmt"
04: 
05: func gogo(bababa [5]int) {
06: 	for i := range bababa {
07: 		 bababa[i] = 10 + i
08: 	}
09: }
10: 
11: func lolo(bababa [5]int) {
12: 	i := 3
13: 	for i, bababa[i] = range bababa {
14: 		//break
15: 	}
16: }
17: 
18: func main() {
19: 	var momomo [5]int
20: 
21: 	gogo(momomo)
22: 	lolo(momomo)
23: 	for i, val := range momomo {
24: 		fmt.Printf("[%d] = %d\n", i, val)
25: 	}
26: }
  

$ go run .
[0] = 0
[1] = 0
[2] = 0
[3] = 0
[4] = 0

ほう。

ちなみに、前回のスライスで書いたバージョンはこうだ。

01: package main
02: 
03: import "fmt"
04: 
05: func gogo(bababa []int) {
06: 	for i := range bababa {
07: 		 bababa[i] = 10 + i
08: 	}
09: }
10: 
11: func lolo(bababa []int) {
12: 	i := 3
13: 	for i, bababa[i] = range bababa {
14: 		//break
15: 	}
16: }
17: 
18: func main() {
19: 	var momomo []int
20: 
21: 	momomo = make([]int, 5)
22: 	gogo(momomo)
23: 	lolo(momomo)
24: 	for i, val := range momomo {
25: 		fmt.Printf("[%d] = %d\n", i, val)
26: 	}
27: }
  

$ go run ex3
[0] = 11
[1] = 12
[2] = 10
[3] = 14
[4] = 14

前回は要素数を空欄にしていたので、指定しただけなのだが、それで大きく結果が変わった。

gogo()を呼んでいる内容が反映されていないので、コピー渡しされているのだろうか?


ちょうどよい記事があった。

Golang 関数引数のスライスはポインタで渡すべき? - Qiita
https://qiita.com/ta_ta_ta_miya/items/593ed887f58b251c4fe2

 

golanの関数引数は値渡しらしい。
これはC言語もそうなのだが、配列についてはgolangの方がより値渡しになっているな。

C言語の配列は、配列の変数名だけだとアドレスを返す。
配列の中身は、そのアドレスから始まるメモリに書かれている。
ポインタ変数の場合は、そのアドレスにはポインタが指すアドレスが書かれている。

 

golangでも配列はそのアドレスからデータが書かれているのだろう。だから、配列を「値渡し」すると、そのアドレスに書いてあるデータも含めてコピーされる。
スライスの場合はC言語のポインタ変数みたいなもので、アドレスに書かれているデータがアドレスやサイズのデータなので、それをコピーして渡す。

 

じゃあ、C言語っぽく配列を渡したかったらどうするかというと、スライスにすればいいそうだ。
まあ、この辺は慣れるしかないな。


いかんいかん、ちゃんとスライスにすればOKなのかどうかを確認していなかった。

01: package main
02: 
03: import "fmt"
04: 
05: func gogo(bababa [5]int) {
06: 	for i := range bababa {
07: 		 bababa[i] = 10 + i
08: 	}
09: }
10: 
11: func gogo2(bababa []int) {
12: 	for i := range bababa {
13: 		 bababa[i] = 10 + i
14: 	}
15: }
16: 
17: func main() {
18: 	var momomo [5]int
19: 
20: 	gogo(momomo)
21: 	for i, val := range momomo {
22: 		fmt.Printf("gogo[%d] = %d\n", i, val)
23: 	}
24: 
25: 	gogo2(momomo[:])
26: 	for i, val := range momomo {
27: 		fmt.Printf("gogo2[%d] = %d\n", i, val)
28: 	}
29: }
  

$ go run ex4
gogo[0] = 0
gogo[1] = 0
gogo[2] = 0
gogo[3] = 0
gogo[4] = 0
gogo2[0] = 10
gogo2[1] = 11
gogo2[2] = 12
gogo2[3] = 13
gogo2[4] = 14

うむ、よかろう。