Skip to content

Instantly share code, notes, and snippets.

@simics-ja
Last active June 23, 2021 00:46
Show Gist options
  • Select an option

  • Save simics-ja/50cb2298fdae570723075e09b59299d7 to your computer and use it in GitHub Desktop.

Select an option

Save simics-ja/50cb2298fdae570723075e09b59299d7 to your computer and use it in GitHub Desktop.
[Golang 備忘録] 自分用リファレンス #Golang

ウ・ル・ト・ラ・ソウッ!\ハーイ!/

Printf

値の挿入は%vを使う

fmt.Printf("ウ・ル・ト・ラ・%v! \%v/", "ソウッ!", "ハーイ!")

4文字幅でパディング&アライメントするときは、%4vのように正の値なら左側に空白を置いでパディングされ、%-4vのように負の値なら空白を右側に置くようにパディングされる

浮動小数点の表示については

third := 100.0 / 3.0
fmt.Printf("%.4v\n", third)	// 33.33
fmt.Printf("%.4f\n", third)	// 33.3333
fmt.Printf("%8.4f\n", third)	//  33.3333
fmt.Printf("%08.4f\n", third)	// 033.3333

宣言

変数宣言

定数ならconstキーワード

var a = 1
var b = 2
var (
  a = 1
  b = 2
)
var a, b = 1, 2

varを省略形式にすることもできる

ただし、パッケージスコープ(packageやimportを宣言してるところ)での変数宣言には使えない

a := 1
b := 2

変数の初期化をしなかったときは、ゼロで初期化される

var price =float64
fmt.Println(price) // 0

浮動小数点

小数点付き10進数は必ずfloat64になる 単精度浮動小数点を使うときは明示的にfloat32で宣言する

var a = 1.414
var a float64 = 1.414

丸め誤差の処理例

a := 0.1
a += 0.2	// a = 0.30000000000000004
fmt.Println(a == 0.3) // false
fmt.Println(math.Abs(a-0.3) < 0.0001)	// true

他にも割り算より掛け算を先に行うなど

整数

符号付き、符号なしで以下の通り

int8uint8int16uint16int32uint32int64uint64

他の言語と似たようなものでintuintを指定したときに、どれを指すかはアーキテクチャに依存する

古いPCなどの32bit環境ならint32だし、64bitの新しいPCだとuint64という具合

16進数を使う

16進数を使うときは大多数の言語と同じで0xから始める

次の2の宣言は等価

var red, green, blue uint8 = 0, 141, 213
var red, green, blue uint8 = 0x00, 0x8d, 0xd5

Printfでは以下のように使う

fmt.Printf("color: #%02x%02x%02x;" red, green, blue) // color: #008dd5;

型を調べる

Printfで%Tで指定するらしい

fmt.Printf("%T", 1.111)	// float64

もっと良い書き方ありそうだけど

クソデカ数値を扱う(bigパッケージ)

uin64でも扱えない整数はbigパッケージというものを使う。

import "math/big"

func main() {
  lightSpeed := big.NewInt(299792)	//クソデカ整数が想定される場合
}

他にも浮動小数点のbig.Floatや、分数を扱うbig.Ratなどがあるらしい。

文字列

文字列の書き換え

C言語みたいに文字列に含まれる文字を参照して書き換えたりできない。c := meesage[4]みたいにアクセスはできる

生の文字列リテラル

本にこう書いてあったけどこの翻訳モヤる

fmt.Println(`
複数行にわたるテキストも
	←インデントもちゃんと表示できます
\nエスケープシーケンスはエスケープされずにテキストとして出力されます。
`)

文字列の切り出し

スライスと同様にtext[始端(含む):終端(含まない)]で文字列を切り出せる

text := "abcde"

fmt.Println(text[1:4]) // bcd
fmt.Println(text[:3]) // abc
fmt.Println(text[3:]) // de
fmt.Println(text[:]) // abcde

インデックスが示すのは文字数ではなくバイト数
日本語の場合は1文字3byte

text := "機動戦士"

fmt.Println(len(text))	// 12
fmt.Println(text[6:])	// 戦士

Printfで整数を文字列として扱う。

GoはUnicodeを扱うためにruneという型(実態はuint32のエイリアス。uint8をbyteと呼ぶのと同じ)を用意している

Printfでは%cで整数値に対応する文字を出力する。

var pi runde = 960
fmt.Printf("%c", pi)	// π

型変換

Goではa := 110 + "円になります"のように文字列に数字を結合しようとしたら怒られる

型変換はベーシックにfloat64(777)のように囲む

符号ありなし、およびサイズが違う整数間でも型変換は必要になる

文字列に変換するときはstring()fmt.Sprtintf()という関数の返り値を使用する

ブールはstring(false)bool(1)とかするとエラーになる

この場合には素直にif文でstringに処理したり、param == 1のようにする

インクリメントや代入演算子

だいたい他の言語と同様に使えるが、Goでは前置のインクリメント++countはサポートしてない

制御構文

比較演算子、論理演算子

他の言語と同じ

if文

if 条件 {
} else if 条件 {
} else {
}

省略宣言を使うとif文の中で新しい変数を宣言できる

if num:=rand.Int(3); num == 3 {
  処理
}

switch文

C言語等ではbreakを使わない限りデフォルトで一つ下のcaseを実行する(下に落ちる)が、goではcaseブロック内で明示的にfallthroughのキーワードを使わなければ下に落ちることはない

switch 変数 {
case 値:
  処理
case 値:
  処理
default:
  処理
}
switch {
case 条件:
  処理
case 条件
  処理
default:
  処理
}

省略宣言を使うとswitch文の中で新しい変数を宣言できる

switch num := rand.Int(10); num {
case 0:
  処理
case 1:
  処理
default:
  処理
}

for文

よく見るfor文の形に近づけるには省略形式の変数宣言を使う

for count := 10; count > 0; count-- {
  処理
}

その他バリエーション

var count = 10
for count > 0 {
  処理
  count--
}

条件を指定しなければ無限ループ

for {
  処理
  if 条件 {
  	break
  }
}

コレクション系

配列

宣言

var arr [3]string
var arr [3][3]string

代入

arr[1] = "text"

参照

fmt.Print(arr[2])

初期化

arr := [3]string{"青", "黄", "赤"}
or
arr := [...]string{"青", "黄", "赤",}	// 要素数をコンパイラが計算する。最後にカンマが必須

繰り返し処理

for i := 0; i < len(arr); i++ {
  fmt.Println(arr[i])
}
or
func main() {
  arr := [3]string{"青", "黄", "赤",}
  for i, item := range arr {
    fmt.Println(i, item)
  }
}

rangeを使った書き方で、配列のインデックスや要素が不要な場合は_と置くことでコンパイラにunusedエラーを吐かせないようにすることができる

配列の代入や引数渡しはコピー

arr1 := [3]string{"青", "黄", "赤",}
arr2 := arr1
arr1[0] = "緑"
fmt.Println(arr1)	// [緑 黄 赤]
fmt.Println(arr2)	// [青 黄 赤]

スライス

配列をスライスに切り出す

arr[始端(含む):終端(含まない)]で配列を切り出す

arr := [...]string{
  "a", // 0
  "b", // 1
  "c", // 2
  "d", // 3
  "e", // 4
}

fmt.Println(arr[1:4]) // [b c d]
fmt.Println(arr[:3]) // [a b c]
fmt.Println(arr[3:]) // [d e]
fmt.Println(arr[:]) // [a b c d e]

配列を経由しないスライスの宣言

slice := []string{"a", "b", "c",}

配列とスライスの違い

スライスと配列は一見似ているが厳密には違うものと考えて良さそう

func main() {
  arr := [...]string{"a", "b", "c",}
  slice := arr[:]
  fmt.Printf("%T\n",arr)	// [3]string
  fmt.Printf("%T\n",slice)	// []string
}

配列は宣言時にサイズが指定されているが、スライスは指定しない

また、代入や関数の引数に渡した先で中身を書き換えると元のスライス/配列も書き換わるという点で配列と異なる

ポインタのセクションを参照

slice1 := []string{"青", "黄", "赤",}
slice2 := slice1
slice1[0] = "緑"
fmt.Println(slice1)	// [緑 黄 赤]
fmt.Println(slice2)	// [緑 黄 赤]

Goは基本的に値渡しを行うので2つの変数を調べてもアドレスは異なるが、参照先は同じであるためこのようになる。

参考:https://christina04.hatenablog.com/entry/2017/09/26/190000

配列を使うとサイズが大きい場合に大量のコピーが走るので基本的に配列よりもスライスを使うべきかも

スライスに要素を追加する

slice := []string{"青", "黄", "赤"}
slice = append(slice, "虹色")
fmt.Println(slice)

appendの挙動

スライスが持つことができる要素数(容量)はcapで確認できる

appendを行うと、 元スライスの倍の容量を原則確保する

倍にしても必要量確保できない場合は必要量を確保する

func dump(slice []string) {
	fmt.Printf("長さ %v, 容量 %v %v\n", len(slice), cap(slice), slice)
}

func main() {
  slice := []string{"a", "b"}
  dump(slice)	// 長さ 2, 容量 2 [a b]
  dump(slice[:1])	// 長さ 1, 容量 2 [a]
  slice = append(slice, "c")
  dump(slice)	// 長さ 3, 容量 4 [a b c]
  slice = append(slice, "d", "e", "f", "g", "h", "i")
  dump(slice)	// 長さ 9, 容量 9 [a b c d e f g h i]
}

また、appendを行った行った場合は新しいスライスが作られる

func main() {
  slice1 := []string{"a", "b"}
  slice2 := append(slice1, "c")
  slice1[0] = "x"
  slice2[1] = "y"
  fmt.Println(slice1)	// [x b]
  fmt.Println(slice2)	// [a y c]
}

appendを大量に行うのは遅くなるので避けたほうが無難

スライスの容量を制限する

3個目のインデックス指定でスライスの容量を制限できる

スライスのサイズを容量よりも大きく指定するとエラーになる

slice := []string{"a", "b", "c", "d", "e"}[0:4:4]
// 長さ 4, 容量 4 [a b c d]
fmt.Printf("長さ %v, 容量 %v %v\n", len(slice), cap(slice), slice)

スライスの容量を事前に割り当てる

append時にスライスの容量が不足すると新しい配列を割り当ててコピーする比較的重い処理が走る

なので、なるべく十分な容量のスライスを事前に確保しておくことができる

slice := make([]string, 0, 10) // 長さが0、容量が10のスライス

可変個引数関数

func echo(text ...string) {
  for _, item := range text {
    fmt.Println(item)
  }
}

マップ

宣言

Goではキーの型も指定する

配列同様に最後の要素にもカンマが必要

stock := map[string]int{
  "apple": 8,
  "orange": 16,
  "grape": 10,
}

存在しないキーを参照した場合は、int型なら0を返すなどエラーにならない

存在するキーかどうかを判別するには代入時に2つの値を用意する

stock := map[string]int{
  "apple": 8,
  "orange": 16,
  "grape": 10,
}
	
fmt.Println(stock["banana"])	// 0
banana, ok := stock["banana"]	// ここではokとしているが名前は任意
fmt.Println(banana, ok)	// 0 false

if文と組み合わせて、以下のような書き方もできる

if apple, ok := stock["apple"]; ok {
  fmt.Printf("りんごの在庫は%v個", apple)	// りんごの在庫は8個
} else {
  fmt.Println("りんごの在庫はありません")	// 実行されない
}

マップを事前に割り当てる

スライスと同様にmakeで

stock := make(map[string]int, 8)

マップをセットとして使う

Goはセット(要素が重複しないコレクション)を提供していないが、マップで代用できる

ブール値を使って重複要素を消す例

signal := []string{"blue", "yellow", "green", "blue"}
set := make(map[string]bool)

for _, v := range signal {
  set[v] = true	// 重複要素を取り除く
}

// スライスに戻す処理
setSlice := make([]string, 0, len(set))
for i, _ := range set {
  setSlice = append(setSlice, i)
}
fmt.Println(setSlice)	// [blue yellow green]

関数系

通常の関数

関数名が大文字で始まる場合はエクスポートされる

func Intn(n int) int {
}
...
num := rand.Intn(10)

複数のパラメータを返す関数の書き方

func functionName(s string) (i int, err error) {
  ...
  return i, err
}
or
func functionName(s string) (int, error) {
  ...
  return i, err
}

...

num, err = functionName("text")

メソッド

新しい型を宣言する

type <new-type> <base-type>

新しい型は型のエイリアスとは異なる

以下はfloat64を根底の型にしているが、float64celsiusを混ぜて使うと型の不一致になる

type celsius float64

メソッドを宣言

kelvinToCelsius()のようにカスタムした型を関数によって変換することもできるが、メソッドを使ったほうがスッキリ書ける

func (k kelvin) celsius() celsius {
  return celsius(k - 273.15)
}

使い方は

var c celsius = k.celsius()

関数をたくさん書く場合は温度型の相互変換が煩雑になるが、メソッドなら各温度型にclesius()を持たせられるのでスッキリする

ファーストクラス関数

変数に関数を代入する

丸括弧()を使わない場合は関数を変数に代入できる

このとき、変数myfuncは関数型(関数型で変数宣言するときはvar myfunc func() int)になる

func myFunction(n int) int {
  return n * 2
}

func main() {
  myfunc := myFunction
  fmt.Println(myfunc(2))	// 4
}

関数の引数に任意の関数を渡す

関数を宣言するときに関数型の変数を引数に指定できるようにする

func myFunc(f func() int) {
  n := f()
  ...
}

関数型をベースに新しい型を宣言する

引数にf func() intみたいに書かなくて良くなる

type mytype func() int

func myFunc(f mytype) {
  n := f()
  ...
}

無名関数

func(){...}で無名関数を定義して、変数に代入したり、引数に渡すことができる

var f = func() {
  ...
}

jsの即時実行関数のような使い方もできる

func main() {
  func() {
    ...
  }()
}

無名関数でクロージャを利用するとき、値をコピーするのではなく、参照を保持する

func main() {
  var count int = 0

  var f = func() int {
    return count
  }

  for i := 0; i < 3; i++ {
    fmt.Print(f())	// 012
    count++
  }
}

構造体

宣言

var vector struct {
  x int
  y int
}

vector.x = 1
vector.y = 2
fmt.Println(vector.x, vector.y) // 1 2
fmt.Println(vector)             // {1 2}

型として定義

type vector struct {
  x int
  y int
}

var v vector

複合リテラルで初期化

複合リテラル{}を使った形はインデックス名を指定する/指定しないパターンがある

インデックス名を指定する場合は、要素をどの順番で書いても良く、新しい要素の追加に強い書き方

宣言にない名前を指定すると、その型のゼロ値で初期化される

type vector struct {
	x, y int
}

v1 := vector{x: 1, y: 2}
v2 := vector{2, 1}
fmt.Println(v1) // {1 2}
fmt.Println(v2) // {2 1}

ちなみに、Printfの%+vを使うとインデックス名も合わせて表示される

fmt.Printf("%+v\n", v2) // {x:2 y:1}

構造体はコピーされる

v1 := vector{x: 1, y: 2}
v2 := v1
v2.x += 2

fmt.Printf("%+v\n", v1) // {x:1 y:2}
fmt.Printf("%+v\n", v2) // {x:3 y:2}

構造体のスライス

vecs := []vector{
  {x: 0, y: 1},
  {x: 1, y: 0},
  {x: 2, y: 0},
}

構造体をJSONにエンコードする

package main

import (
  "encoding/json"
  "fmt"
  "os"
)

func main() {
  type stock struct {
    Apple, Orange int // インデックス名の先頭を大文字にしないとダメ
  }

  s := stock{Apple: 12, Orange: 22}
  bytes, err := json.Marshal(s)
  if err != nil {
    fmt.Println(err)
    os.Exit(1) // エラーがあれば終了
  }
  fmt.Println(string(bytes)) // {"Apple":12,"Orange":22}
}

Goではインデックス名の先頭は大文字を要求され、キャメルケースを使うのが慣例だが、大文字で構造体の要素にタグをつけることでjsonのインデックスを好きに指定できる

上記のコード例を書き直すと

type stock struct {
  Apple  int `json:"apple"`
  Orange int `json:"orange"`
}
...
fmt.Println(string(bytes)) // {"apple":12,"orange":22}

構造体にメソッドを紐付ける

Goにはクラスは無いが、構造体にメソッドを紐付けられるのでクラスのように使うことができる

type vector struct {
  x, y int
}

func (v vector) getArea() int {
  return v.x * v.y
}

func main() {
  v := vector{x: 10, y: 20}
  fmt.Println(v.getArea())
}

構造体のコンスタラクタ関数

goはコンストラクタを提供していないが、newで始まる規約に従った名前で関数を作れば同じことができる

エクスポートする場合は大文字Newで始まり、エクスポートしない場合は小文字newで始まるのが慣例になっている

func newVector(x, y int) vector {
  return vector{x: x, y: y}
}

func main() {
  v := newVector(10, 20)
  fmt.Println(v.getArea())
}

構造体の組織化

構造体に他の構造体を埋め込むとき、position positionのように書くのは煩わしいため、positionの1語に省略できる

package main

import (
  "fmt"
  "math"
)

type MovingObject struct {
  position	// position positionと書かなくて良い
  speed		// 紐付いたメソッドもMovingObejct.speed.movement()のようにアクセスできる
}

type position struct {
  x float64
  y float64
}

type speed struct {
  x float64
  y float64
}

func (s speed) movement() float64 {
  return math.Sqrt(s.x*s.x + s.y*s.y)
}

func main() {
  obj := MovingObject{
    position{x: 0, y: 0},
    speed{x: 1, y: 1},
  }
  fmt.Println(obj.speed.movement())	// 1.4142135623730951
}

interface

type bz struct{}

func (b bz) talk() string {
	return "ウ・ル・ト・ラ・ソウッ!"
}

func main() {
  b := bz{}

  var t interface {
    talk() string
  }

  t = b

  fmt.Println(t.talk())
}

interfaceは再利用可能なように型として宣言するほうが多い

// interfaceを型として宣言する場合は-erと命名するのが慣例
type talker interface {
  talk() string
}

type bz struct{}

func (b bz) talk() string {
  return "ウ・ル・ト・ラ・ソウッ!"
}

// talk()を定義している型のみ引数にできる
func shout(t talker) string {
  return t.talk() + "\ハーイ!/"
}

func main() {
  b := bz{}

  var t talker = b

  fmt.Println(t.talk())	// ウ・ル・ト・ラ・ソウッ!
  fmt.Println(shout(t))	// ウ・ル・ト・ラ・ソウッ!\ハーイ!/
}

Goのinterfaceはダックタイピング

Javaのclass hoge interface fuga { 〜 }などとは異なり、Goでは型に対して明示的にinterfaceを実装しなくとも、同じシグネチャのメソッドを保持していれば実装してあるものとみなせる

例えば、以下ではbz型に明示的にtalkerインタフェースを実装していないが、文字列を返すtalkメソッドを持っているのでbz型はtalkerインタフェースを実装しているとみなせるため、shoutの引数としてbzを渡すことができる

type talker interface {
  talk() string
}

type bz struct{}

func (b bz) talk() string {
  return "ウ・ル・ト・ラ・ソウ!"
}

func shout(t talker) string {
  return t.talk() + "\ハーイ!/"
}

同じくダックタイピングのRubyでは「もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである」という比喩があるらしい

ポインタ

基本的にポインタの多用は避けておくほうが良いが、ポインタレシーバを使うときに理解は必要そう

アドレス演算子

&で変数のアドレスを取り出せる

a := 51
fmt.Println(&a)	// 0xc00001a118

デリファレンス演算子

*でアドレスによって参照される値を求める

a := 51
fmt.Println(&a)	// 0xc00001a118
address := &a
fmt.Println(*address) // 51

ポインタの型

以下の例では、*int型のポインタであることを示しており、adressはint型以外の変数のアドレスを指すことを許さない

a := 51
address := &a
fmt.Printf("%T", address) // *int

構造体へのポインタ

type vector struct {
  x, y int
}
v := &vector{x: 1, y: 1}

fmt.Println(v)	// &{1 1}
// デリファレンスせずにアクセスできる
fmt.Println(v.x) // 1
// 構造体のフィールドのアドレスも参照できる
fmt.Println(&v.x) // 0xc00011c000

配列へのポインタ

arr := &[3]string{"blue", "yellow", "red"}

fmt.Println(arr) // &[blue yellow red]
// デリファレンス(*arr[0])せずにアクセスできる
fmt.Println(arr[0])  // blue
fmt.Println(arr[1:]) // [yellow red]

slice := &[]string{"blue", "yellow", "red"}
// マップやスライスはデリファレンスしないとエラーになる
fmt.Println((*slice)[1]) // yellow

ポインタによる引数

通常、引数はコピーされるが、ポインタを渡すことでアドレス渡しを明示的に行うことができる

func increment(num *int) {
  *num++
}

func main() {
  a := 0
  b := 107
  increment(&a)
  increment(&a)
  increment(&b)
  fmt.Println(a)	// 2
  fmt.Println(b)	// 108
}

ポインタレシーバ

Goでは以下のように構造体はポインタで渡されることが多い

type counter struct {
	num int
}

// ポインタのレシーバ
func (c *counter) increment() {
	c.num++
}

func main() {
	c1 := &counter{num: 18}
	c1.increment()
	fmt.Println(c1)     // &{19}
	fmt.Println(c1.num) // 19

	// アドレス演算子を使わなくてもincrementは行われる
	c2 := counter{num: 5}
	c2.increment()
	fmt.Println(c2)     // {6}
	fmt.Println(c2.num) // 6
}

なぜなら、以下のようにポインタレシーバを使わなければ構造体counterのnumはインクリメントされない

type counter struct {
	num int
}

// 普通のレシーバ
func (c counter) increment() {
	c.num++
}

func main() {
	c1 := counter{num: 18}
	c1.increment()
	fmt.Println(c1.num) // 18
}

配列の書き換え

func exchange(arr *[2]string) {
  str := arr[0]
  arr[0] = arr[1]
  arr[1] = str
}

func main() {
  arr := [2]string{"a", "b"}
  fmt.Println(arr)	// [a b]
  exchange(&arr)
  fmt.Println(arr)	// [b a]
}

暗黙的なポインタ

マップやスライスが関数の引数として使われるときは前述のとおり参照渡しとなるため、マップおよびスライスは実際にはポインタになっている

エラーハンドリング

nil

他言語でいうところのnullはGoではnil

nilの変数をデリファレンスするとプログラムがクラッシュする

var a *int
fmt.Println(a)

fmt.Println(*a)	// if a != nilでチェックしないとエラー
// panic: runtime error: invalid memory address or nil pointer dereference....

ポインタレシーバでのnilチェック

下記の例ではc *counternilが入る可能性があるのでチェックが必要

type counter struct {
  num int
}

func (c *counter) increment() {
  if c == nil {
    fmt.Println("Invalid receiver")
    return
  }
  c.num++ // 上記のif文でガードしていない場合はcはnilなのでプログラムがクラッシュする
}

func main() {
  var c *counter
  fmt.Println(c) // どこも指し示さないポインタなので<nil>
  c.increment()
}

ゼロ値がnilとなるもの

関数型、スライス、マップ、インタフェースのゼロ値はnilとなる

ただし、==でnilのチェックをするときに値はnilでもなんらかの値が入っていると等しくならないので注意する

例えば、インタフェース型にnilのポインタを代入してみる

var v interface{}
fmt.Printf("type:%T param:%v is-null:%v\n", v, v, v == nil) // type:<nil> param:<nil> is-null:true
// ポインタを代入してみると、nilなのにnilと等しくならないという結果が得られる
var p *int
v = p                                                       //
fmt.Printf("type:%T param:%v is-null:%v\n", v, v, v == nil) // type:*int param:<nil> is-null:false
fmt.Printf("type:%T param:%v is-null:%v\n", p, p, p == nil) // type:*int param:<nil> is-null:true
// %#vで型と値の両方を出力すると、単なるnilではないことが確認できる
fmt.Printf("%#v\n", v) // (*int)(nil)
fmt.Printf("%#v\n", p) // (*int)(nil)

マップやポインタや関数型などに対して、型や値などの情報を出力させるには%#vが使えるので覚えとこう

かしこい例外処理

Goにはtry&catch&finallyがないので、愚直にエラーチェックをすると、失敗しそうなコードを書くたびにエラーチェックをする必要があり煩雑になる

_, err := 失敗しそうな処理1
if err != nil {
  終了処理(ファイルのクローズなど)
  return err
}

_, err := 失敗しそうな処理2
if err != nil {
  終了処理(ファイルのクローズなど)
  return err
}

以下のテキストファイルを書き出す例ではsafeWriterという構造体によるテクニックと、deferという遅延実行のキーワードのおかげで例外処理をシンプルに実装している

safeWriterが無い場合は、失敗しそうなコードの直後でエラーチェックを繰り返す必要がある

合わせてdeferが無い場合は、nilチェックの中でファイルのクローズが確実に行われるように同じコードを何度も書く必要がある

package main

import (
  "fmt"
  "io"
  "os"
)

// エラー値をフィールドで保持する構造体
type safeWriter struct {
  w   io.Writer
  err error
}

func (sw *safeWriter) writeln(s string) {
  if sw.err != nil {
    return // すでにエラーが発生していたら書き込みをスキップする
  }
  _, sw.err = fmt.Fprintln(sw.w, s)
}

func proverbs(name string) error {
  f, err := os.Create(name)
  if err != nil {
    return err
  }
  // deferキーワードを指定すると、そのコードはproverbsがリターンを返すまで実行されない(遅延実行)
  // deferを使わない場合、以降のnilチェックのifブロック内でf.Close()を何度も書く必要がある
  defer f.Close() // deferでf.Close()が必ず実行されることが保証される

  sw := safeWriter{w: f}
  sw.writeln("1行目")
  sw.writeln("2行目")
  sw.writeln("3行目") // 途中でエラーが起きた場合それ以降の書き込みは行われない

  return sw.err
}

func main() {
  err := proverbs("proverbs.txt")
  if err != nil {
    fmt.Println(err)
    os.Exit(1)
  }
}

deferの仕様については以下とか参考に

https://qiita.com/Ishidall/items/8dd663de5755a15e84f2

errorsパッケージ

使用例

errorsのコンストラクタでは、エラーメッセージ用の文字列を受け取ることができる

package main

import (
  "errors"
  "fmt"
  "os"
)

func echo(str string) error {
  if str == "hoge" {
    return errors.New("hoge is invalid string")
  }
  fmt.Println(str)
  return nil
}

func main() {
  err := echo("wow")
  if err != nil {
    fmt.Println(err)	// invalid string
    os.Exit(1)
  }
}

変数として定義

変数として定義しておくと再利用も可能になる

var (
  ErrHoge = errors.New("hoge is invalid string")
  ErrFuga = errors.New("fuga is invalid string")
)

変数にしておくことで呼び出し側は処理を分岐させることもできる

if err != nil {
  switch err {
    case ErrHoge, ErrFuga:
      fmt.Println("invalid string")
    default:
      fmt.Println(err)
  }
  os.Exit(1)
}

独自のエラー型を定義

error型の定義は

type error interface {
  Error() string
}

なので、Errorという名前で文字列を返すメソッドを実装している型は、すべてErrorインタフェースを実装しているものとみなせる(ダックタイピング)

以下では

package main

import (
  "errors"
  "fmt"
  "os"
  "strings"
)

var (
  ErrInvalidString = errors.New("0123456789!? is invalid string")
  ErrTooLongString = errors.New("Too long string (limit < 11)")
)

type MyError []error

func (me MyError) Error() string {
  var s []string
  for _, err := range me {
    s = append(s, err.Error())
  }
  return strings.Join(s, ", ")
}

func echo(str string) error {
  var errs MyError

  if str == "0123456789!?" {
    errs = append(errs, ErrInvalidString)
  }
  if len(str) > 10 {
    errs = append(errs, ErrTooLongString)
  }
  if len(errs) > 0 {
    return errs
  }
  fmt.Println(str)
  return nil
}

func main() {
  err := echo("0123456789!?")
  if err != nil {
    fmt.Println(err)  // 0123456789!? is invalid string, Too long string (limit < 11)
    os.Exit(1)
  }
}

型アサーション

上記のecho関数はMyError型をerrorインタフェースに変換する(errorインタフェースのほうが汎用的で呼び出し側で柔軟に対応しやすいため)

個々のエラーにアクセスするためには、呼び出し側でそのインタフェースの基底となっている具体的な型に変換する必要がある

下記のように、err.(MyError)とすることで、errはMyErrorであるとアサーション(断言)し、正しければok=trueとなりerrsをMyError型の値が入る

func main() {
  err := echo("0123456789!?")
  if err != nil {
    if errs, ok := err.(MyError); ok {	// err.(MyError)でerrorインタフェースからMyError型に変換される
      fmt.Printf("%d error(s) occurred:\n", len(errs)) // 2 error(s) occurred:
      for _, e := range errs {
        fmt.Printf("- %v\n", e)
        // - 0123456789!? is invalid string
        // - Too long string (limit < 11)
      }
    }
    os.Exit(1)
  }
}

パニックを起こす

めったに使わない

panic("ウ・ル・ト・ラ・ソウッ!\ハーイ!/") // panic: ウ・ル・ト・ラ・ソウッ!\ハーイ!/ を出力してプログラムが停止する

パニックによるクラッシュを防ぐ

遅延された関数内recoverによってパニックから復旧できる

func main() {
  defer func() {	// deferによる遅延実行はpanicの手前でも実行される
    if e := recover(); e != nil {
      fmt.Println(e)// みんなが慌ててる
      fmt.Println("オラはすごいゾ天才的だゾ") // オラはすごいゾ天才的だゾ
    }
  }()
  panic("みんなが慌ててる")
}

goroutine

並列処理を行うにはgoキーワードを使う

time.Sleepはそこで処理を止めるが、goroutine用にsleep関数に切り出しているので、コード実行からおよそ1,2,3秒後にでfmt.Printlnが順番に実行される

package main

import (
	"fmt"
	"time"
)

func main() {
	go sleep("ウルトラ…?", 1)
	go sleep("ソウッ!", 2)
	time.Sleep(3 * time.Second)
	fmt.Println("\ハーイ!/")
}

func sleep(s string, t int) {
	time.Sleep(time.Duration(t) * time.Second)
	fmt.Println(s)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment