Go言語メモ

TSとかやってる人がGo言語に入門したよ

入門する前のGoの感想
-
=>
とか??
とか記号使わないの!? - ジェネリクス
[]
なのか... まじか - スペースのセマンティクスが強そう
- 全体的にPythonみたいだな

気になったことを書いていく

Makefile
GoってMakefileを使うんだ
独自のTask Runnerあると思ってた
ゼロ値
滅茶苦茶納得した
JavaScriptだとundefined
が出てくるような場面でゼロ値が出てくる、という仕組みはとても納得できる
複素数型
いるか???
あってもいいとは思う

変数宣言
var x int = 30
var x int // zero value
var (
a int
b = 20
c int = 30
d, e = 40, "Hello"
)
自由度が高い
使い分けの基準が載ってた
- 変数をゼロ値にしたいなら
var x int
- リテラルのデフォルト値が欲しい型と異なるときには
var x <type> = ...
と書く。x := <type>(...)
は避けたほうがいい - リテラルが欲しい型とおなじになるなら
x := ...
を使う。短いし
ただし、x, y := ... , ...
みたいなのは、カンマokイディオムについてのみ使うべき
x := ...
のとき宣言と代入の構文が同じのは気になる
unused variable
未使用変数→エラー
結構思い切ったけど不便ではなさそう

:=
の推論について
package main
import "fmt"
func main() {
a := 30
var b int32 = 3
fmt.Println(a / b) // invalid operation: a / b (mismatched types int and int32)
}
これはエラーが出る
aがintと推論されるので、int32であるbとは型が違う
暗黙的な型変換禁止!!!
fn main() {
let a = 30;
let b: i32 = 3;
println!("{}", a / b); // 10
}
Rustでは大体同じように書いてもエラーが出ない

RustとGoでは随分違いそう
Go
a := 30
と書いた時点でint
と推論
Rust
let a = 30;
だけでは型が確定せず、4行目でi32の型と一緒に使われることによってi32と確定する。
Rustのほうがリテラルをuntyped(型無し)として扱うのが上手そう

配列とスライス
Goではあまり配列を直接使うことは少ないらしい
配列では長さまで型に組み込まれているので使い勝手が悪い
スライスが他言語の配列の雰囲気で使えるらしい
[]int{}
はちょっとわかりにくい
Map
GoではMapはHashMap
for文で回すと順番がランダムになる←順番に並ぶことを前提としたコードを防ぐため
いちいちランダムにしてるなら乱数生成のオーバーヘッドがありそうな気もする
Set
Goに標準でSetなんてものはないので、Mapにキーの重複が許されないことを利用する
package main
import "fmt"
func main() {
m := map[int]bool{}
nums := []int{1, 3, 4, 5, 5, 7, 5, 1}
for _, num := range nums {
m[num] = true
}
fmt.Println(m) // map[1:true 3:true 4:true 5:true 7:true]
if m[4] {
fmt.Println("4は存在する")
}
if !m[10] {
fmt.Println("10は存在しない")
}
}
存在しないキー(=つまり集合に存在しない要素)にアクセスするとエラーではなく、値型のzero valueが返される。つまりこの場合はbool
のzero value, false
が返される。これを利用して簡潔に存在判定を記述できると。
やっぱりzero value便利
ちなみに、パフォーマンスを極めるならmap[int]bool
ではなく、map[int]struct{}
を使うらしい
package main
import "fmt"
func main() {
m := map[int]struct{}{}
nums := []int{1, 3, 4, 5, 5, 7, 5, 1}
for _, num := range nums {
m[num] = struct{}{}
}
fmt.Println(m) // map[1:{} 3:{} 4:{} 5:{} 7:{}]
if _, ok := m[4]; ok {
fmt.Println("4は存在する")
}
if _, ok := m[10]; !ok {
fmt.Println("10は存在しない")
}
}
見栄えはあまりよろしくない
結局はこれ使え

今はMapはHashMapではなくSwiss Tableというもので実装されているらしい

for文
whileは全てforに置き換えられている
確かに2つのキーワードは必要はないね
Simple is best
goto ... ?
Go To Statement Considered Harmful ー Edgar Dijkstra
ブロック単位でのジャンプしかできない模様
gotoを使ったらわかりやすいコードも稀にあるらしい、稀に

関数
概ね普通、func f(a, b int)
は良いね
blank return
書籍曰く、
Go言語の重大な欠陥
らしいです。
package main
import "fmt"
func calc(a int, b int) (r1 int, r2 int) {
r1 = a + b
r2 = a - b
return
}
func main() {
fmt.Println(calc(3, 1))
}
これがコンパイル通るのか...
reviveというlinterでは禁止されているらしい。
使ってはいけません、と
型
関数の型はfunc(<type>) <type>
defer
自動実行されるクリーンアップ関数
JSのusing
やPythonのwith
みたいなもの
関数を出るときに必ず(関数がpanicしても)実行される
JS, pythonではブロックを出るときに実行される
package main
import (
"log"
"os"
)
func main() {
f, err := os.Open("sample.txt")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// process f ...
// fは自動で閉じられる
}

Goは値渡し
関数の引数は値として渡されて、元の値には変更を与えない
ただし、マップやスライスはポインタとして実装されているため、関数内での変更が反映される
関数内でもとの値を変更するにはポインタとして値を渡す→ポインタはミュータブルの印
ポインタを取得するには&a
、ポインタの値を参照するには*a
とかく。
ポインタ型は*int
のように書く。Rustでは&i32
なので異なる。
どうやらC言語の流れを汲んでそう
この宣言の書き方にはとまどうことがあるかと思う。しかし,この宣言「 int *p 」を,「 p の左に * を付けたら int 型になる」と読むと,なるほどと理解できる。実際,int 型のポインタの左に * を付けたら int 型の変数になるからだ。

ポインタ
リテラルに直接&をつけることはできない
値を変更するにはポインタに再代入するのではなく、ポインタの指す先に再代入する。
package main
import (
"fmt"
)
func failedUpdate(px *int) {
a := 20
px = &a
}
func update(px *int) {
*px = 20
}
func main() {
x := 10
failedUpdate(&x)
fmt.Println(x) // 10
update(&x)
fmt.Println(x) // 20
}
マップやスライスポインタ
既存の要素の更新は反映されるが、要素を追加した場合にはその変更は反映されない
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3}
s2 := s1
fmt.Println(s1, s2) // [1 2 3] [1 2 3]
s2[0] = 0
fmt.Println(s1, s2) // [0 2 3] [0 2 3]
s2 = append(s2, 5)
fmt.Println(s1, s2) // [0 2 3] [0 2 3 5]
fmt.Println(len(s1), len(s2)) // 3 4
}
この場合、s2
のlenが増えても、s1
のlenは増えない。つまり、配列のうちのs1
が見ている範囲は反映されない

メソッド
ポインタ型レシーバーと値型レシーバ
使い分けについて以下引用
- メソッドがレシーバを変更するならポインタレシーバを使わなければならない
- メソッドが
nil
を扱うする必要があるなら、ポインタレシーバを使わなければならない- メソッドがレシーバを変更しないなら、値レシーバを使うことができる
ポインタレシーバはnil
を扱うことができる。
ポインタレシーバへのメソッド
func (c *Counter) Increment() { ... }
var c Counter
c.Increment()
Increment
メソッドはCounterのポインタ型を取るが、&c
とする必要はない。暗黙的にc
のポインタへと変換される

型宣言
type
は型に別名をつけるのではなく、別の新しい型を作る
type Score int
// untyped literalは代入可能
var i int = 30
var s Score = 10
// s = i <- Error [ cannot use i (variable of type int) as Score value in assignment ]
s = Score(50)
// 相互変換は可能
si := int(s) + i
is := s + Score(i) + s
Branded Typeというような工夫がいらない