こんにちは、ナナオです。

Goを触り始めるにあたり、どこで勉強するかなどをまとめます。

インストール

インストールはmiseでやりました。

Home | mise-en-place

mise use -g go

どこで勉強したか(するか)

GoにはA tour of Goという公式のチュートリアルが用意されているので、そこでやりました。

A Tour of Go

ローカルからこのチュートリアルを実行したい場合は以下のコマンドを実行します。

go install golang.org/x/website/tour@latest
$GOROOT/bin/tour

印象に残ったところ

naked 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))
}

whileはない

goではfor文がwhileを兼任しています。

package main

import (
	"fmt"
)

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}

更にrustでいうloop文もforで表現可能です。

func main() {
	for {
		// 無限ループ
	}
}

if let的なやつ

rustでいうところのif let …的な、if文で変数宣言できるやつもあります。

package main

import (
	"fmt"
)

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
		if v := sum % 2; v == 0 {
			fmt.Println("even: ", sum, v)
		} else {
			fmt.Println("odd: ", sum, v)
		}
	}
	fmt.Println(sum)
}

条件なしのswitch

条件なしでswitch文を書くことができます。

また、switch文にはbreakは不要です(勝手に解釈する)

ちょっとこの辺は他のプログラミング言語と混濁しちゃいそうですね。

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文

対象の関数実行を遅延評価するための構文です。GOTOと似てるかな…?

package main

import "fmt"

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

	fmt.Println("hello")
}

この場合、出力は以下のようになります。

hello
world

なんのために必要なんだ?と思っていましたが、ファイルを開いた後の片付けなどで使用するようですね。(pythonでいうwith文みたいな感じ?)

サンプルで学ぶ Go 言語: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
8
7
6
5
4
3
2
1
0

構造体

構造体のメンバを参照するには単純にポインタから.で取得できます。

(Cのアロー関数みたいな感じ?)

package main

import "fmt"

type Vertex struct {
	X int
	Y int
}

func main() {
	v := Vertex{1, 2}
	p := &v
	p.X = 1e9
	fmt.Println(v)
}

配列とスライス

配列は長さの決まった特定の型のリストです。

配列からスライスは:で取ることができます。

この辺の挙動もpythonと同じですね。

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}

	s = s[1:4]
	fmt.Println(s)

	s = s[:2]
	fmt.Println(s)

	s = s[1:]
	fmt.Println(s)
}

スライスの長さはlen、元の配列の長さはcapで取ることができます。

package main

import "fmt"

func main() {
	s := []int{2, 3, 5, 7, 11, 13}
	printSlice(s)

	// Slice the slice to give it zero length.
	s = s[:0]
	printSlice(s)

	// Extend its length.
	s = s[:4]
	printSlice(s)

	// Drop its first two values.
	s = s[2:]
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

出力は以下の通りです。

len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

スライスのデフォルトはnilです。

また、それとは別で空のスライスを作ることもできます。

// これはnil
var s []int
// これは空のスライス
s := []int{}

組み込みのmake関数を使ってスライスの動的生成が可能です。

ここで少しlenとcapの違いを理解しました。(コード内のコメントを参照)

package main

import "fmt"

func main() {
	a := make([]int, 5)
	printSlice("a", a)

	// 何も入っていないけど、容量は5個分あるスライスを生成
	// nilではないけど中身は空のスライス
	b := make([]int, 0, 5)
	// 出力は[]
	printSlice("b", b)

	// 0 - 1のスライスを取得
	// この時初めて中身のintのデフォルト値がスライス内に配置される
	c := b[:2]
	// 出力は[0, 0]
	printSlice("c", c)

	// 2 - 5のスライスを取得
	d := c[2:5]
	// 出力は[0, 0, 0]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s len=%d cap=%d %v\n",
		s, len(x), cap(x), x)
}

append関数でスライスに要素を追加することができます。

package main

import "fmt"

func main() {
	var s []int
	printSlice(s)

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s)

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s)

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

for文でrangeを使うことで反復できます。

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
	for i, v := range pow {
		// iはインデックス、vは要素のコピー
		fmt.Printf("2**%d = %d\n", i, v)
	}
	// 要素のコピーを無視したい場合はiだけでもOK
	for i := range pow {
		pow[i] = 1 << uint(i) // == 2**i
	}
	// インデックスを無視したいならアンダーバーを使う
	for _, value := range pow {
		fmt.Printf("%d\n", value)
	}
}

まとめ

今日は疲れたのでここまで。

Go面白い。

参考

macでa tour of goをローカルで実行したときに詰まった #Mac - Qiita