Go言語入門メモ①

社内勉強会に参加してGoに入門した。

環境

  • Mac (M1)
  • VSCode (Goの拡張機能を入れる)
  • Go 1.19 (研修は1.17でビルドする)

Windows環境は作るのMacより面倒らしい。

研修の資料は社外秘なので、やったところと同じ範囲の A Tour of Go を参考にする。

HelloWorld

おなじみの入門儀式である。
適当に作ったディレクトリに hello.goファイル作って、ソースをコピペする。

// hello.go
package main  // これはmainパッケージです!と高らかに宣言

import "fmt"  // fmtモジュールを使いたいという気持ちの表れ

// mainパッケージのmain関数はプログラム実行時最初に叩かれると決まっている
func main() {
	fmt.Println("Hello, World")  // fmtモジュールのPrintln関数でテキスト出力
}

この時点で既にJSとの文化の違いを感じる🤔 けど似てる部分もある。

hello.goと同じルートで go mod init hello をターミナルで実行すると、go.mod ファイルが作成される。
これはJSで言うところのpackage.jsonのようなもので、構成とかが保存される設定ファイルである。
中身はそのままGoなのでとてもスッキリしている。

// go.mod
module hello

go 1.17

go run . で実行できる。

ディレクトリ構成

研修だと演習問題を1つずつこなしていく形式で、1つ親のディレクトリ作ってその中に演習問題ごとのディレクトリとモジュールを作成するのがおすすめ、とあったので、JSのノリでディレクトリ構成を分けたらエラーになったw

つまりこうしたかった。

/go-study
   /01_hello
      go.mod
      hello.go
   /02_printf
      go.mod
      printf.go
// 01_hello/go.mod
module hello

go 1.17
// 01_hello/hello.go

package main

import "fmt"

func main() {
	fmt.Println("Hello, World")
}

Workspace Mode

Go 1.18 以上であれば、ワークスペースモードを利用できるので、
ルートに go.work ファイルを追加して、use関数で各モジュールを設定しておく。
(各ディレクトリの go.mod バージョン指定は1.18以上にしなければならい)

// go.work
go 1.19

use (
	./01_hello
	./02_printf
)

これだとルートで go run hello のように各 go.mod で指定したモジュール名を利用して実行できる。

VSCode Workspace

バージョン1.18以下だとワークスペースモードは使えないので、VSCodeのワークスペース設定でディレクトリを追加していくことにした。
全て同じ命名でいいし、ディレクトリ複製がしやすいから、演習こなしてくのには良さそう。

// GoStudy.code-workspace
{
  "folders": [
    {
      "path": "./01_hello"
    },
    {
      "path": "./02_printf"
    }
  ],
  "settings": {}
}

各ディレクトリで go run . を実行すればおk。

モジュールの追加

Goでモジュールを追加する場合、それが公式のものであれば自動でやってくれるが、
ユーザーが開発したものは get コマンドによるインストールが必要。

go get github.com/sirupsen/logrus

不要になったときはremoveするのではなくて、ソースから削除してから tidy を叩く

go mod tidy -v

JavaScriptと違うGoの文化について

JS使いがハマりそうなところは大体同じなんじゃないか説。

ダブルクォートとシングルクォート

Goでもダブルクォートはstringだが、シングルクォートはrune型になる。
JSで日常的にシングルクォート使ってると、めちゃくちゃ打ち間違える。

命名における大文字と小文字の違い

関数や構造体の名前の先頭が、
大文字の場合:パプリック(外部パッケージから参照できる)
小文字の場合:プライベート(同じパッケージ内からしかアクセスできない)
となる。

これはpublicとか宣言書いた方が目が滑りにくいと思うけどなあ🤔

変数はvar

宣言の呪文は var である。 最近JSだとvar使わなくなってるので懐かしさを覚える。
カンマ区切りで羅列できるし、型も指定できる。

package main

import "fmt"

var c, python, java bool  // この変数全部bool型

func main() {
	var i int // int型の変数
	fmt.Println(i, c, python, java)
}

varで宣言しつつ変数に値を代入するときもカンマ区切りが使える。
その変数の型は代入した値のものに設定されるので、型の宣言は省略して良い。

package main

import "fmt"

var i, j int = 1, 2

func main() {
	var c, python, java = true, false, "no!"
	fmt.Println(i, j, c, python, java)
}

// > 1 2 true false no!

挙動はJSとそう変わらんけど、省略しすぎると読みづらくないか?🤔

関数の中に限っては、 := を利用することで varを省略することができる。

package main

import "fmt"

func main() {
	var i, j int = 1, 2
	k := 3
	c, python, java := true, false, "no!"

	fmt.Println(i, j, k, c, python, java)
}

VSCode上だと色分けされるんだけど、
やたら省略する書き方って目が滑るからあんまり好きじゃないなあ。

定数はconst

const で宣言すると定数となる。必ず const という呪文が必要で、 := では宣言することができない。
定数で利用できる型は、文字(character)、文字列(string)、boolean、数値(numeric) のみである。
定数として宣言した変数には代入することができない、っていうのは他の言語と同じ。

package main

import "fmt"

const Pi = 3.14

func main() {
	const World = "世界"
	fmt.Println("Hello", World)
	fmt.Println("Happy", Pi, "Day")

	const Truth = true
	Truth = 1 // [error] cannot assign to Truth (untyped bool constant true)
	fmt.Println("Go rules?", Truth)
}

値が数値の定数は、定数宣言時に型が指定されていなければ状況によって必要な型を取る

package main

import "fmt"

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}

func main() {
  fmt.Printf("Type: %T Value: %v\n", Small, Small)
  fmt.Printf("Type: %T Value: %v\n", needInt(Small), needInt(Small))
  fmt.Printf("Type: %T Value: %v\n", needFloat(Small), needFloat(Small))
  fmt.Printf("Type: %T Value: %v\n", needFloat(Big), needFloat(Big))
}

自動で初期値入れてくれる

変数を宣言するとき何も値を入れなかった場合、型に合わせた初期値が自動的に設定される。
これは普通に便利だと思った。

package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

// > 0 0 false ""

数値の型多すぎぃ!

上記のruneもそうだけど、Goにおける型の種類見てると、見慣れないものがいろいろある。

// 論理値型
bool

// 文字列型
string

// 符号付整数型
int  int8  int16  int32  int64

// 符号なし整数型
uint uint8 uint16 uint32 uint64 uintptr

// uint8 の別名
byte 

// int32 の別名(Unicode のコードポイントを表す)
// 文字列にうっかりシングルクォート使うとこれになる
rune 

// 浮動小数点型
float32 float64

// 複素数型
complex64 complex128

数値の型めっちゃあるな…🤔

package main

import (
	"fmt"
	"math/cmplx"
)

var (
	ToBe   bool       = false
	MaxInt uint64     = 1<<64 - 1
	z      complex128 = cmplx.Sqrt(-5 + 12i)
)

func main() {
    // 変数 Tobe は bool 型で値は false
	fmt.Printf("Type: %T Value: %v\n", ToBe, ToBe)
     
    // 変数 MaxInt は unit64 型で値は 18446744073709551615
	fmt.Printf("Type: %T Value: %v\n", MaxInt, MaxInt) 
	
    // 変数 z は complex128 型で値は (2+3i)
    fmt.Printf("Type: %T Value: %v\n", z, z)
}

Goのint64といったでかい数値をJSで受け取る場合、numberではなくBigIntになって死ぬ。

型変換

型を関数のように使うことで型変換ができる

var i int = 42
var f float64 = float64(i)
var u uint = uint(f)

なら string(i) で文字列にできるかというとできなくて、
intをstringにするといった変換はモジュールの利用が必要なようだ。

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var x int64 = int64(9223372036854775807)
	var y int = 123

	var sx = strconv.FormatInt(x,  10) // int64 to string
    var sy = strconv.Itoa(y) // int to string

	fmt.Printf("Type: %T Value: %v\n", x, x) 
	fmt.Printf("Type: %T Value: %s\n", sx, sx) 

	fmt.Printf("Type: %T Value: %v\n", y, y) 
	fmt.Printf("Type: %T Value: %s\n", sy, sy) 
}

関数

パラメーターと戻り値に型の指定が必要である。TypeScriptに似ている。
型は変数の後に指定する。同じ型の場合は省略も可能。

package main

import "fmt"

// xとyはint型、戻り値もint型です
func add(x int, y int) int {
	return x + y
}

// xとyが同じ型なので省略ができる
func add2(x, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

戻り値が複数ある場合はカンマ区切りで並べる。

package main

import "fmt"

func swap(x, y string) (string, string) {
	return y, x
}

func main() {
	a, b := swap("hello", "world")
	fmt.Println(a, b)
}

戻り値に名前をつけることで、returnに変数を並べ書くことを省略できる。
naked returnとかいうかっこいい名前がついているが、何をreturnしているのが探さなければならないような、複雑な関数で使うべきものではない。

package main

import "fmt"

func split(sum int) (x, y int) {
	x = sum * 4 / 9
	y = sum - x
	return
}

func main() {
	fmt.Println(split(17))
}

エラー処理

Goはtry-catchのような例外処理を持たないので、関数内でエラーが発生したときにerror型を返す戻り値が設定されている。エラーが発生しない場合errorにはnilが入るというところで判別ができる。

func main() {
    s := "cat"
    num, err := strconv.Atoi(s)
   
    if err != nil {
        fmt.Println("数値に変換できませんでした")
    }
    fmt.Printf("%d \n", num)
}

関数によってはセンチネルエラーという、Errから始まる定数が設定されている場合があり、どういったエラーなのか種類の判別が可能である。

未使用のための変数

Goは使ってないモジュールや変数、関数なんかがあるといちいちエラーで怒られる。
ゴミが残らないのでいいんだけども🤔

関数が戻り値を2つ返すが、呼び出し側では片方しか使わない。という場合は結構ある。

// errorは使わん…
int, error := strconv.Atoi("10")

使わないことが決まっている変数は、アンダーバーにすれば怒られなくなる。

// errorをアンダーバーに変更
int, _ := strconv.Atoi("10")

ifとfor文は括弧が不要

中括弧 { } は必要。

// for文
sum := 0
for i := 0; i < 10; i++ {
  sum += i
}

// if文
if x < 0 {
  return true
}

while文は省略したforである

forは初期化と後処理も省略できるが、セミコロンすら省略できてしまう。
Goにはwhileがないのでforを使う。

sum := 1
for sum < 1000 {
  sum += sum
}

ループ条件すら省略可能なので、無限ループがお手軽に作れる。

for {
}

Switch文はbreakしなくていい

Goのswitch文は上から下にcaseで指定された条件を評価し、当てはまったらそこしか実行しないので、breakは不要である。

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go runs on ")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("OS X.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

Switch文のcaseは定数や整数じゃなくてもいい

条件式とかも使える。これはもうif文の省略形と思った方がいいのかも🤔

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("When's Saturday?")
    // todayはtime.Weekday型でGo playground上だと値はTuesday
	today := time.Now().Weekday()
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	default:
		fmt.Println("Too far away.")
	}
}
package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("Good morning!")
	case t.Hour() < 17:
		fmt.Println("Good afternoon.")
	default:
		fmt.Println("Good evening.")
	}
}

遅延実行

deferステートメントを使うと、呼び出し元の関数が終わるまで実行を遅延させることができる。

package main

import "fmt"

func main() {
	defer fmt.Println("world")

	fmt.Println("hello")
}
// hello world

たくさん defer すると、全部スタックし、最後にdeferへ渡したものから順に実行される。

package main

import "fmt"

func main() {
	fmt.Println("counting")

	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}

	fmt.Println("done")
}

// > counting done 9 ... 0

使い所がイメージできんのだけどPromiseのようなもんかな?🤔

クラスという概念がない

継承とか移譲のようなクラスにまつわるあれこれもない。

その代わり構造体というものがある。typestruct がその宣言である。

// ねこの名前、種類、毛色、年齢を表す構造体
type cat struct {
    name string
    type string
    color string
    old int
}

構造体を利用するときは必ず初期化が必要ある。

myCat := cat{}   // 変数がそれぞれの型の初期値で初期化される

myCat := cat{ name: "たま", type: "雑種" }  // 値を指定して初期化

……どうみてもクラスやないか?🤔

インターフェース

クラスはないが、インターフェースはある。
インターフェースに定義されてるメソッドを実装しなければならない、という縛りもPHPなんかと同じ。

type AutoFeeder interface {
  start()
  stop()
}

大規模になってくるとこういう縛るの欲しくなってくるもんね。

「Go言語入門メモ①」への1件のフィードバック

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください