今更golang始める人の備忘録
Me
- JavaとかC#(.NET Coreじゃないほう)とかのエンタープライズフルスタックガッチガチ系アプリ屋。Web周りやっててフロントサーバーインフラある程度は行けるが、ある程度で止まっている上に古い。
- Rails, Laravelあたりは軽く触ったことがあるが、触った程度でちゃんと仕事で触れたことはない。
- フロントの知識はRollup.jsが出てきた辺りで止まっている。今やWebpackチャンク職人しなくていいってマジ?
- 最近マネジメント周りに手を出さざるを得なくなってそっちの方ばかり手を出してたけど、プログラミングやりたい欲が噴出したので言語勉強に手を出し始めた。
Stance of scrap
- 新しく言語を学ぶ、というのが久々なので、他の言語やるときにどういう手順でやったか思い出すための備忘録
- あわよくば強いエンジニアに「こっちの方がいいですよ」という指摘もらえないかと期待している。
- 「見てて有意義」は目指さない。あたふたしながらやる。半分くらい日記帳みたいなつもりでやる。
env
- OS: Windows11 Pro 2022H2
- Go Version: 1.19.1
- Editor: VSCode + Go Plugin
A Tour of Go
最初にやるといいらしいのでとりあえず手を出してみる。
日本語版があるので、変に自分で翻訳して正しい意味を取れないよりは翻訳されたものを素直に受け取る方が吉と判断。
Webエディタでやるよりローカルで触る方が変なショートカット暴発でイラッとしなくていいなと考え、日本語版で指示されたままにgo get
で開始しようとする。
PS > go get github.com/atotto/go-tour-jp/gotour
go: go.mod file not found in current directory or any parent directory.
'go get' is no longer supported outside a module.
To build and install a command, use 'go install' with a version,
like 'go install example.com/cmd@latest'
For more information, see https://golang.org/doc/go-get-install-deprecation
or run 'go help get' or 'go help install'.
PS > go install github.com/atotto/go-tour-jp/gotour
go: 'go install' requires a version when current directory is not in a module
Try 'go install github.com/atotto/go-tour-jp/gotour@latest' to install the latest version
PS > go install github.com/atotto/go-tour-jp/gotour@latest
go: github.com/atotto/go-tour-jp/gotour@latest (in github.com/atotto/go-tour-jp@v0.0.0-20201205034358-913c5eae8455):
The go.mod file for the module providing named packages contains one or
more replace directives. It must not contain directives that would cause
it to be interpreted differently than if it were the main module.
えぇ……?
調べたところ、Version1.16でモジュール管理周りで変更があり、そこに引っ掛かっている模様。
これでバージョンを指定してモジュールのビルド&インストールができる。 ただし go.mod ファイルが replace や exclude ディレクティブを含んでいると go install が失敗するみたい。
日本語ローカライズは長らく更新がないので、おそらく対応されたとしてもずっと先だろう。
公式では現行バージョン用に該当部が書き換えられている。
go get --force
のようなオプションが存在するかどうかは調べていないが、やったとしても「他に今のバージョンでは別の書き方をします」が存在しそうなので、公式の方を利用して進める。
ここまででtourのローカル起動は「CLIでファイル読ませて実行できるよ」のようなものだと勘違いしていたが、どうも実行環境とTourのサーバーをローカルに置けるよ、というだけのものであり、目的にはそぐわない。リファレンスはちゃんと読みましょう。
A Tour of Go 寄り道:モジュール管理周り
go install
したものってどこに行ったんだと気になったので、その辺も調べようとしたら思いっきりTourに書いてあった。英語はちゃんと読みましょう。
This will place a tour binary in your GOPATH's bin directory. When you run the tour program, it will open a web browser displaying your local version of the tour.
- 基本的に実行可能バイナリなんかは
${GOPATH}/bin
以下。変えたければ$GOBIN
変えれば良さそう。 - ビルド前のコードは
${GOPATH}/pkg/mod
以下に入ってくれるっぽい。 - 依存性の管理は
go.mod
を利用し、モジュールの実態は全部${GOPATH}
に突っ込んでる。
リファレンス読んで動かしてみた限りは上記挙動っぽい。
現状グローバルでのモジュール管理っぽい挙動しか扱ってなく、go.mod
とgo.sum
の使い分けやら、go workspace
やらgo mod
やらが関連しそうだなーという感触だけど、今段階で触れると深淵に触れそうだからこのあたりで触るのやめてTourの続きを触る。
Zennでこのあたり取り扱ってくださっている方がいたので、あとで参考にするメモ。
A Tour of Go: Basics/Packages, variables, and functions.
内容そのもの以外で気になって調べた点についてメモ
page 1
Programs start running in package main.
go run
などで走らせる場合は、エントリポイントはmain
パッケージのfunc main()
でないといけない。
page 5
When two or more consecutive named function parameters share a type, you can omit the type from all but the last.
同じ型の引数を連続して記述している場合、型の宣言が省略されるよ、というもの。
このあたりもgo fmt
で修正かけてくれたりするのかな?と試しにやってみたが、別にそんなことはなかった。
あくまでシュガーシンタックス扱いで、そこまで厳密に見るようなものでもないらしい。
page 11
fmt
のフォーマット形式はパッケージのドキュメンテーションにある。
%を使う形式のことをなんと表現するかわからなかったのだけれど、printf formatみたいに言われるのが標準なのかな?とにかくその形式。
ところでUnicodeのコードポイントをrune
と表現したシンタックスにしてるのかっこよすぎんか。
A Tour of Go: Flow control statements: for, if, else, switch and defer
page 5
fmt.Sprint
:fmtでフォーマットだけする場合はこっち。標準出力ではなく文字列を返す。
page 12
defer
全般について。
「使い方はわかったけど何に使うんやコレ」と思ったのでざっくり調べてみたら、
-
panic
からのリカバー。try-finallyっぽく使う(基本的にはこれ) - ファイルのクローズ処理
- テスト実行時のクリーンアップ
あたりがメインっぽい。
じゃあなんで例外処理機構用意せずdeferで解決しようとしてるんだろう、とついでに調べたらこちらの記事にお世話になった。recover
で復帰してそれ以外は大域脱出」ってのは個人的にしっくりくる書き方なので、個人的にはgolangの例外処理機構の方が好みかなー。
A Tour of Go: More types: structs, slices, and maps.
structs, slicesはサクッと読んで軽く叩いてみて終わり。
sliceに比べて配列の使いどころがよくわからない。基本的に実態持ってるのがarrayで、sliceでその参照(と、どこのインデックスまでの範囲からどこまでをスライスするか)を持ってるというのはわかるけど、実業務でそこまでarrayの存在気にすることあるかな……となっている。
と、ここまで考えたところでappendの挙動を思い出して検証。
appendするときは可変長の配列っぽく使えるけど、配列は値でイミュータブルかつ固定なら、新しく要素が追加されたスライスってもしかして別の配列を生成して、そこに新たに参照を持ってない?これを意識せずにappendしちゃうと元のポインタ持ちつつappend後のポインタの参照先変わっちゃう?意識してないと怖いやつ?
というわけでやってみたところ、やっぱり新たにarray生成してメモリ割り当ててる臭い挙動。ですよねー。
package main
import "fmt"
func main() {
arr := [3]int{1, 2, 3}
fmt.Println(&arr[0]) //0xc0000aa078
sli := arr[:]
fmt.Println(&sli[0]) //0xc0000aa078
sli = append(sli, 4)
fmt.Println(&sli[0]) //0xc0000c8030
}
ググってみた感じやっぱりallocしてる模様。ただし、この挙動をするときはcapを超えた時のみで、超えない場合は特に問題ないらしい。そりゃそうよね。
package main
import "fmt"
func main() {
arr := [3]int{1, 2, 3}
fmt.Println(&arr[0]) //0xc0000aa078
sli := arr[:]
fmt.Println(&sli[0]) //0xc0000aa078
sli_copy := arr[:1]
fmt.Println(&sli_copy[0]) //0xc0000aa078
}
試してみてもそんな感じの挙動。
脳死で一方のsliceにもう一方のsliceをappendするときにforで回して1要素ずつappendしたら嫌な挙動しそう。slice同士でappendで連結できるので、こっちを使うのが良い。
Exercise: Sliceまで実施。
package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
result := make([][]uint8, dy)
for i := range result {
result[i] = make([]uint8, dx)
}
for y := range result {
for x := range result[y] {
result[y][x] = uint8((x ^ y * 3) / 2)
}
}
return result
}
func main() {
pic.Show(Pic)
}
とりあえず実装してお題に書いてあるがまま適当に式変えたりして挙動試してみたけど、これgoのお作法的に正しいのかわかんないなー。先に二重スライス作ってから値突っ込んでるけどxはfor分の中でmakeした方がいいんだろうか、など思って、先人がどうやってるのか検索したら画像大喜利してて笑った。