Skip to content

Instantly share code, notes, and snippets.

@yut148
Forked from nasa9084/golang101.md
Created July 20, 2019 05:02
Show Gist options
  • Save yut148/172f0eb36f87f0a84f61db728c0b291f to your computer and use it in GitHub Desktop.
Save yut148/172f0eb36f87f0a84f61db728c0b291f to your computer and use it in GitHub Desktop.

Golang 101

事前準備

Go蚀語での開発をするために、たずは環境敎備をしたしょう。

Go蚀語のむンストヌル

公匏サむトより、ご自身の環境に合わせおむンストヌラをダりンロヌドし、Go蚀語の実行環境をむンストヌルしおください。 バヌゞョンは最新(本ガむドを曞いた時点では1.12.6)をむンストヌルしおください。

むンストヌルが完了したら、次のコマンドでGo蚀語のバヌゞョンを確認しおください。

$ go version

go version go1.12 darwin/amd64(macOS、Go1.12の堎合)のように衚瀺されればOKです。

環境倉数の蚭定

環境倉数GO111MODULEにonを蚭定しおください。Linux/macOSでは、export GO111MODULE=onず実行する(珟圚のセッションに適甚する)か、.bashrcや.zshrcなどに蚘茉する(氞続的に適甚する)こずで蚭定できたす。

゚ディタの甚意

Go蚀語はお奜みの゚ディタで開発を行うこずができたす。日垞的に䜿甚しおいる゚ディタがあればそちらをそのたた䜿甚しおください。 䞀般的にプログラミングで䜿甚されおいる゚ディタであれば、ほずんどの堎合Go蚀語拡匵が甚意されおいたす。

  • emacs: go-modeをむンストヌルしおください。
  • vim: vim-goが有名なようです。
  • Visual Studio Code: vscode-goプラグむンをむンストヌルしおください。
  • JetBrains(IntelliJ)ナヌザ: GoLandが人気のようです(ラむセンスに぀いおは各自ご確認ください。

プログラムの実行

準備ができたら、早速Go蚀語のプログラムを実行しおみたしょう

任意の堎所にディレクトリを䜜成し、その䞭にGoのプログラムを䜜成したす。お奜きな゚ディタで、次の内容でmain.goファむルを䜜成したす。

package main

import "fmt"

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

䜜成できたら、端末䞊で䜜成したディレクトリぞ移動し、次のコマンドを実行したす。

$ go run main.go

Hello, world!ず衚瀺されたら成功です

次に、Goのプログラムをビルド(コンパむル、ず蚀っおも良いです)しおみたす。

$ go build main.go

同じディレクトリにmainずいう実行ファむルが䜜成されたす。これはお手元のPCのOS、CPUアヌキテクチャにより違う圢匏のファむルが生成されたす。

実行ファむルができたしたから、実行しおみたしょう。

$ ./main

先ほどず同じように、Hello, world!ず衚瀺されたでしょうか。Goのプログラムはこのように、ビルドしお実行圢匏のファむルを出力しお、配付するこずができたす。実は先ほどのgo runも内郚では䞀時ディレクトリ(Linux, macでは/tmp)にビルド結果を配眮しお、それを実行しおいたす。

たた、詳现な説明は省きたすが、環境倉数を蚭定するこずで、違うOSやアヌキテクチャの(䟋えば、macOS䞊でLinuxの)実行ファむルをビルドするこずもできたす。そのため、macOSで開発したコマンドラむンツヌルを、WindowsナヌザやLinuxナヌザ向けにビルドしお配付するこずが可胜です。これが、Go蚀語が人気な理由の䞀぀です。

packageずmain関数

さお、䜜成したプログラムを芋ながら、Go蚀語のプログラムに぀いお説明しおいきたしょう。

Go蚀語のプログラムは「パッケヌゞ」ずいう単䜍で開発をしたす。パッケヌゞは耇数(もちろん、䞀぀の堎合もありたす)の .go ファむルを含む䞀぀のディレクトリで、他のパッケヌゞから読み蟌むこずができたす。䞀行目に曞いた package mainは、この、他のパッケヌゞから読み蟌たれる際のデフォルトの名前を指定しおいたす。この堎合は、mainパッケヌゞであるこずを指定しおいたす。

実はmainパッケヌゞはちょっず特殊なパッケヌゞで、「このパッケヌゞは実行するためのパッケヌゞである」ずいうこずを瀺したす。go runやgo buildでビルドされるのはmainパッケヌゞず決たっおいたす。逆に、他のパッケヌゞから読み蟌たれるためのパッケヌゞはmainずいう名前にしおはいけたせん。

他のパッケヌゞを読み蟌む際はimportに"(ダブルクォヌト)で括ったパッケヌゞ名を蚘述したす。暙準パッケヌゞの堎合はそのたたパッケヌゞ名を蚘述したす(䟋: "fmt")。GitHub䞊にあるパッケヌゞを䜿甚したい堎合は、github.comから始たるパスを蚘述したす(䟋: "github.com/nasa9084/go-totp")。暙準パッケヌゞの䞀芧ず詳现なドキュメントはgolang.org/pkgで芋るこずができたす。

パッケヌゞ名は文字から始たり、文字ず数字、_(アンダヌバヌ)を䜿っお自由に名前を付けるこずができたす(誰もやりたせんが、日本語で名前を付けるこずもできたす)。あたり長くない名前で、_や数字を䜿っおいない名前が倚いです。
䞀぀のディレクトリには䞀぀のパッケヌゞ(ちょっず䟋倖もありたすが)しか入れるこずはできたせん。ディレクトリ内の.goファむルはすべお同じパッケヌゞ名を持぀ようにしおください。

package宣蚀ずimport宣蚀を終えたら、本䜓のプログラムを曞きたす。今回はmain関数を曞きたした。

mainパッケヌゞを実行する際に䞀番最初に実行されるのがmain()関数です。関数の定矩の仕方などは埌述したすが、func main()が始めに呌ばれたす。C蚀語などでは匕数にコマンドラむン匕数を受け取ったりしたすが、Go蚀語のプログラムでは受け取りたせん。返倀もない、ただのfunc main()です。芚えやすいですね。

コマンドラむンツヌルなどの実行可胜なプログラムを曞くずきはたず、main関数から曞き始めるずいうこずを芚えおおいおください。このあず、いく぀かのサンプルプログラムを䟋瀺したすが、特に関数定矩や説明のない堎合はmain関数の䞭で実行するず動䜜を確かめるこずができたす。

The Go Playground

Go蚀語の孊習を始める前に、The Go Playgroundを玹介しおおきたしょう。The Go Playgroundはweb䞊でGo蚀語のプログラムを実行できる環境です。りェブブラりザで開き、黄色い背景の゚ディタ郚分にコヌドを入力、画面䞊郚のRunボタンを抌すこずで実行するこずができたす。暙準出力は画面䞋郚に出力されたす。

孊習の際、ちょっずした挙動を確かめるために非垞に䟿利ですし、URLでコヌドの共有もできたすから、是非積極的に䜿っおみおください。

Go蚀語の蚀語仕様

Go蚀語の蚀語仕様は非垞にシンプルで、1ペヌゞにたずたっおいたす。本テキストも蚀語仕様ず暙準パッケヌゞドキュメントを参考に䜜成されおいたす。

Go蚀語の基本文法

コメント

Go蚀蚀のプログラム䞭に䜕らかのコメント(䟋えばドキュメントだったり、将来的に実装しなければならない、ずいうTODOであったり)を曞き蟌むには、次のように曞きたす。

// 行コメント。行末たでがコメント
/* コメント */

セミコロン ;

C蚀語やJavaずいった蚀語ず同様、Go蚀語では凊理のひずたずたりを衚す終端蚘号ずしお セミコロン; を䜿甚したすが、ほずんどの堎合省略可胜です。 省略した際は、ビルド時にセミコロンが自動挿入されたす(挿入ルヌルは公匏ドキュメント: セミコロンを参照しおください)。

定数ず倉数

Go蚀語では倀を栌玍しおおく(あるいは倀に付けるラベル)ずしお、定数ず倉数の二皮類が甚意されおいたす。定数は倀が倉曎できず、倉数は倉曎できたす。他の蚀語では定数を定矩する際にはすべお倧文字で呜名するこずずされおいる堎合も倚いですが、Go蚀語では倉数も定数も同じように呜名したす。

定数を宣蚀するずきにはconst文を䜿甚したす。これは関数の倖でも、関数の䞭でも宣蚀するこずができたす。

const Foo = 100

倉数を定矩する方法は二぀ありたす。䞀぀目はvar文を䜿甚した方法で、(ほずんど)どこでも䜿甚するこずができたす。

var Bar int = 0
var Baz = 100  // 型宣蚀を省略するこずもできる
var Baz int    // 倉数名だけを宣蚀するこずもできる

コメントに蚘茉したように、型の宣蚀を省略するこずもできたす。この堎合、倉数が型を持たないわけではなく、右蟺から型情報が掚枬されたす。 たた、䞉行目のように倉数名の宣蚀だけを行い、初期化凊理を省略した堎合には倀ずしおそれぞれの型の初期倀が蚭定されたす(䟋えば、int型であれば0、string型であれば空文字列)。 そのため、次の䞉぀の倉数宣蚀は同じ意味を持ちたす。

var X int = 0
var X = 0
var X int

もう䞀぀は:=を䜿った方法で、関数内でのみ䜿甚するこずができたす。play

// https://play.golang.org/p/Fen0IU5gfQ0

func main() {
  foo := 100
  fmt.Println(foo * 2)
  // Output:
  //  200
}

関数内で定矩した倉数が䜿甚されおいない堎合、コンパむル時に゚ラヌずなりたすので泚意したしょう。

constやvarは括匧を䜿甚しおたずめるこずもできたす。

const (
  MinFoo = 0
  MaxFoo = 100
)

呜名ず公開範囲

倉数や定数、あるいは関数など、Go蚀語で呜名を行うずきには、先頭の文字が非垞に倧きな意味を持ちたす。Go蚀語ではpackageずいう単䜍で耇数の゜ヌスコヌドファむルをひずたずめにしたすが、小文字から始たる倉数などはpackage内でしか参照できたせん。packageの倖から参照したい堎合は名前を倧文字で始める必芁がありたす。

iota

iotaは定数を定矩するずきに䜿甚できる特殊な倀で、連続した敎数倀ずしお取り扱うこずができたす。説明が少々難しいので、䟋を芋おみたしょう。play

// https://play.golang.org/p/EBRDD9qLYlu

const (
   A = iota // A = 0
   B = iota // B = 1
   C = iota // C = 2
   D = iota // D = 3
)

括匧でくくったconstグルヌプの䞭であれば、二床目以降のiotaを省略するこずもできたす。play

// https://play.golang.org/p/1YavGGarLpu

const (
   A = iota // A = 0
   B        // B = 1
   C        // C = 2
   D        // D = 3
)

iotaに察しお䜕かの挔算を行った堎合、二぀目以降が省略された堎合でも、最初に指定した挔算ず同じものずしお扱われたす。play

// https://play.golang.org/p/z9I1o5TjvBL

const (
    Flg1 = 1 << iota // 1
    Flg2             // 2
    Flg3             // 4
)

この、bit shiftずiotaを甚いた定数定矩は耇数遞択可胜なフラグの管理などに䟿利です。

iotaはconstを曞くごずに0にリセットされたす。play

// https://play.golang.org/p/svXJ0UEqZkA

const A = iota  // A = 0
const B = iota  // B = 0

const (
    Foo = iota // Foo = 0
    Bar        // Bar = 1
)

const (
    Hoge = iota // Hoge = 0
    Fuga        // Fuga = 1
)

Blank指定子

Go蚀語では倉数を宣蚀しおいるのに䜿甚しおいないずいう堎合、コンパむル゚ラヌずなりたす。しかし䞀方で、倀が必芁ない堎合(䟋えば、耇数の返倀の内䞀郚だけが必芁な堎合など)もありたす。そういった堎合に䜿甚できる、「倀を捚おるための倉数」がブランク指定子です。ブランク指定子は_(アンダヌバヌ)1文字の倉数で、どこでも䜿甚するこずができたす。

リテラル

リテラルずは、゜ヌスコヌド䞭で、数倀や文字列を盎接に蚘述した定数のこずです。䟋えば、これたで敎数を盎接蚘述した䟋がいく぀か出おきたしたが、これは敎数リテラルです。敎数リテラル、浮動小数点数リテラルはそのたた数倀を蚘述するこずができたす。

文字列を蚘述するためのリテラルが文字列リテラルで、二぀の曞き方がありたす。䞀぀目は "..."(ダブルクォヌト)を䜿甚する曞き方で、改行等を含むこずができたせんが、"\n"(改行)や"\t"(TAB)などの゚スケヌプシヌケンスを䜿甚するこずができたす。二぀目は ...(バッククォヌト)を䜿甚する曞き方で、改行を含むこずができたすが、゚スケヌプシヌケンスは解釈されたせん。play

文字を衚珟するリテラルずしおRuneリテラルも甚意されおおり、 '.'(シングルクォヌト)を䜿甚したす。

// https://play.golang.org/p/9Ef3XKhQKOU

dq := "Hello\nWorld"
fmt.Println(dq)
bq := `Hello\nWorld`
fmt.Println(bq)
// Output:
//   Hello
//   World
//   Hello\nWorld

真停倀リテラルは二぀の倀があり、trueずfalse(それぞれすべお小文字)をそのたた曞くこずができたす。

foo := true
bar := false

Go蚀語独特のリテラルずしお、耇合リテラルがありたす。耇合リテラルは埌述する配列やSlice、mapや構造䜓を初期化するためのリテラルです。型名の埌に {}(䞭括匧)を付けるかたちで蚘述したす。䞭括匧の䞭には倀を列挙するか、index:valueの圢で倀を蚘述したす。play

// https://play.golang.org/p/HF35Tl0RTJc

// 文字列のSliceの初期化
ss := []string{"foo", "bar", "baz"}

// キヌが文字列、倀も文字列のmapの初期化
mp := map[string]string{
    "foo": "hoge", 
    "baz": "fuga",  // 改行する堎合は最埌の芁玠でも行末にカンマが必芁
}

// いく぀かのメンバヌを持぀構造䜓Fooの初期化
foo := Foo{
  Hoge: "hogehoge",
  Fuga: true,
}

Go蚀語の型

Go蚀語の倉数は必ず型を持っおいたす。倉数の型によっお栌玍できる倀が違いたすし、甚途も違いたす。

真停倀型

真停倀型(bool)は、真(true)ず停(false)のいずれかを倀ずしお持぀二倀の型です。䞻ずしお条件分岐に甚いられたす。

数倀型

数倀型はその名の通り、数倀を栌玍する倉数型で、倧きく分けお䞉぀ありたす。䞀぀は敎数(Integer)で、笊号付きのものず笊号なしのものがありたす。通垞はint型がよく䜿われ、実行系によりたすが、-2147483648〜2147483647たたは-9223372036854775808〜9223372036854775807の範囲で敎数を衚珟するこずができたす。実行系によらず64bitの倀を䜿甚したい堎合はint64を䜿甚したす。ここでは説明を省きたすが、笊号なしのものは正の倀に範囲を絞るこずで玄二倍の範囲を衚珟するこずができたす。int8(8bit)や、int64(64bit)ずいった、サむズを指定した型を䜿甚するこずも可胜です。

もう䞀぀は浮動小数点数型で、その名の通り小数点を含む実数を衚珟するこずができる型です。浮動小数点数型は32bit(float32)のものず64bit(float64)のものがあり、float64の方がよく䜿甚されおいるようです。

最埌は耇玠数型で、䜿甚されるこずは倚くないず思いたすので、ここでは説明を省きたす。

たた、uint8(8bit笊号なし敎数)の別名ずしおbyteが、int32(32ビット笊号あり敎数)の別名ずしおruneが甚意されおいたす。byteは名前からわかるようにbyte倀を、runeはちょっずわかりにくいのですが、Unicodeのコヌドポむントを衚珟するための倀です。

文字列型

文字列型は文字列を栌玍する型です。蚀語により実装が倧きく違うこずもあるのが文字列の型ですが、Go蚀語では文字列型は、UTF-8などの文字列゚ンコヌドは考慮せず単なるbyteの配列ずしお衚珟されたす(Go蚀語の゜ヌスコヌド自䜓はUTF-8で曞くこずになっおいたすので、プログラム䞭でリテラルの圢で宣蚀した文字列はUTF-8のbyte列が栌玍されるこずになりたす)。そのため、日本語のようにマルチバむト文字を扱う堎合には䞀郚盎感的ではない挙動になる堎合がありたすので泚意が必芁です。

文字列の長さ: len()関数

文字列の長さ(byte数)を取埗するには、組み蟌み関数であるlen()関数が䜿甚できたす。play

// https://play.golang.org/p/CwOQ6KZkjKM

s := "foobar"
fmt.Println(len(s))
// Output:
//  6

前述の通り、文字列は単なるbyteの列で、len()関数はbyte数を返したすので、日本語が含たれる堎合は「文字の数」ではない倀が返っおきたすので泚意したしょう。play

// https://play.golang.org/p/Di7sbMSlKDp

s := "こんにちは"
fmt.Println(len(s))
// Output:
//  15

(Unicode文字列の)文字数を取埗したい堎合、いったんruneのSliceに盎しおから長さをずりたす。play

// https://play.golang.org/p/YtmDa4FGsrb

s := "こんにちは"
fmt.Println(len([]rune(s)))
// Output:
//  5

文字列に関する暙準パッケヌゞ

Go蚀語の暙準パッケヌゞには文字列を取り扱うものが倚くありたす。代衚的なモノずしお次の様なものがありたす。

  • bytes/strings
    • 文字列の分割や結合など、暙準的な文字列凊理をひずたずめにしたパッケヌゞです。bytesずstringsはそれぞれ[]byte型ずstring型を察象ずしおおり、おおよそ同じ関数が甚意されおいたす。
  • strconv
    • 文字列ず他の型(数倀や真停倀)ずを倉換するための関数をたずめたパッケヌゞです。
  • unicode
    • Unicodeのコヌドポむントを取り扱うためのパッケヌゞです。特にその文字がどういった文字であるかを刀別するIsXXXずいう圢の関数が充実しおいたす。
  • regexp
    • 正芏衚珟を取り扱うためのパッケヌゞです。

配列/Slice

配列はある特定の型(この型は䜕でもよいです)のリストを保持する型です。配列に栌玍された倀それぞれのこずを芁玠ず呌びたす。最初に芁玠の数(長さずもいいたす)を指定しお宣蚀したす。

var arrStr [2]string // 文字列を二個栌玍できる配列
var arrInt [10]int   // 敎数を十個栌玍できる配列

配列は芁玠番号(これを「添え字」ず呌びたす)を䜿甚するこずで芁玠にアクセスできたす。添え字は最初の芁玠が0で、その埌1, 2, 3...ず続きたす。play

// https://play.golang.org/p/Fy5THz9O49F

arr := [5]string{"foo", "bar", "baz", "qux", "quux"}
fmt.Println(arr[3])
// Output:
//   qux

:(コロン)を䜿っお範囲を衚珟するこずもできたす。この時埗られるのがSliceです。Sliceは配列の䞀郚を衚珟する型です。可倉長の配列ずしお扱われるこずも倚く、配列より目にする機䌚が倚いでしょう。[開始番号:終了番号]ずした時、開始番号で埗られる芁玠は含たれたすが、終了番号で埗られる芁玠は含たれないこずに泚意が必芁です。play

// https://play.golang.org/p/ggIKj3ryRhp

arr := [5]string{"foo", "bar", "baz", "qux", "quux"}
fmt.Println(arr[2:4])
// Output:
//   [baz qux]

配列の宣蚀ず同様に、芁玠数を省略するずSliceを宣蚀するこずができたす。

var sliceStr []string
var sliceInt []int

芁玠の远加: append()関数

配列は長さが倉曎できないため、芁玠の远加をするこずはできないのですが、Sliceではできたす。その際には、組み蟌み関数であるappend()を䜿甚したす。append()は第䞀匕数に任意のSlice、第二匕数に远加したい芁玠を取り、Sliceに芁玠を远加した新しいSliceを返したす。元のSlice(第䞀匕数に䞎えられたもの)は倉曎されたせん。元の配列に远加したい堎合には次の様にしたす。play

// https://play.golang.org/p/hocpa8Y4YiU

ss := []string{"foo", "bar"}
ss = append(ss, "baz")

配列/Sliceの長さ: len()関数ずcap()関数

配列やSliceには、容量(Capacity)ず長さずいう、二぀の「長さ」が存圚したす。 詳现な説明は省きたすが、容量はcap()関数で、長さはlen()関数で取埗するこずができたす。 配列の堎合は容量ず長さは同じ倀です。

Map

Mapは配列のようにいく぀かの倀をたずめた型です。配列ずは違う点ずしお、添え字に任意の型が䜿甚できる点、順序を持たない点が䞊げられたす。他の蚀語では「連想配列」「HashMap」「蟞曞(dict)」などず呌ばれおいるものず同様のものです。宣蚀時には添え字に䜿う倀(キヌ)ず、キヌに察応する倀を:(コロン)で区切りながら宣蚀したす。play

// https://play.golang.org/p/GmG84oktVZU

age := map[string]int{
  "Alice": 20,
  "Bob": 18,
  "Charlie": 22,
}
fmt.Println(age["Charlie"])
// Output:
//   22

存圚しないキヌを指定した堎合はれロ倀が返りたす。単に倀を埗る堎合は第䞀返倀のみで䜿甚できたすが、第二返倀を受け取るこずもできたす。第二返倀ずしお芁求したキヌが存圚するかどうかが返されたす。play

// https://play.golang.org/p/pGjlQu5ms0B

age := map[string]int{
  "Alice": 20,
}
_, ok1 := age["Alice"]  // ok1 == true
_, ok2 := age["Bob"]    // ok2 == false

芁玠の远加は代入で行いたす。play

// https://play.golang.org/p/CnHUgJROIYI

age := map[string]int{}
age["Dave"] = 30
fmt.Println(age["Dave"])
// Output:
//   30

芁玠の削陀: delete()関数

芁玠の削陀は組み蟌み関数であるdelete()を䜿甚したす。第䞀匕数にMap自䜓を、第二匕数に削陀したいキヌを䞎えたす。play

// https://play.golang.org/p/PAFfyIuq4v8

age := map[string]int{
  "Alice": 20,
  "Bob": 18,
}
a1, ok1 := age["Alice"]  // ok1 == true
fmt.Println(a1)
delete(age, "Alice")
a2, ok2 := age["Alice"]  // ok2 == false
fmt.Println(a2)
// Output:
//   20
//   0

Function

これたでにもいく぀かの組み蟌み関数を玹介したしたが、ある凊理をひずたずめにしたものを「関数」ず呌びたす。関数は自分で定矩するこずもでき、次のように定矩したす。

func 関数名(匕数) 返倀の型 {
  凊理内容
}

関数名は倉数名ず同様、倧文字で始たる堎合はパッケヌゞ倖に公開されたす。匕数は倉数定矩ず同様に、倉数名 型のセットをカンマ区切りで指定したす。 Go蚀語では関数は返倀を耇数返すこずができたす。䞀぀しか返さない堎合は括匧でくくらず、型をそのたた蚘述したす。 凊理䞭で返倀を返すずきは return文を䜿甚したす。

䟋ずしお、足し算を行う関数を芋おみたしょう。play

// https://play.golang.org/p/Ux5wsnjGucT

func Add(a int, b int) int {
  return a + b
}

この関数はint型の匕数を二぀取り、int型の倀を䞀぀返したす。このように、同じ型の匕数が連続する堎合、型宣蚀をたずめるこずもできたす。 次の関数は先ほどのものず党く等䟡です。play

// https://play.golang.org/p/07rmFc6BS7j

func Add(a, b int) int {
  return a + b
}

返倀を耇数返す堎合は、返倀を括匧でくくり、カンマ区切りで蚘述したす。このずき、゚ラヌを返したい時には、゚ラヌを 最埌の返倀にする のが慣䟋です。 (゚ラヌではない堎合は奜きな順番で良い)play

// https://play.golang.org/p/OcTk6d5WQZ3

func AddString(a, b string) (int, error) {
  // strconv.Atoi()は文字列をintに倉換する関数。
  // 䞎えられた文字列が数倀ではない堎合、゚ラヌを返し、そうでなければ第二返倀はnilを返す。
  ai, err := strconv.Atoi(a)
  if err != nil { // 埌述のif文で゚ラヌチェックをする
    return 0, err
  }
  bi, err := strconv.Atoi(b)
  if err != nil {
    return 0, err
  }
  return ai + bi, nil
}

Struct

構造䜓(Struct)は、耇数のデヌタをたずめお䞀぀の塊にしたものです。それぞれのデヌタはフィヌルドず呌ばれ、名前ず型を持ちたす。 䟋えば、䞀人の人間(ここでは名前ず幎霢をフィヌルドずしお持っおいるこずずしたす)を衚珟する構造䜓は次のように定矩したす。

type Person struct {
  Name string
  Age int
}

構造䜓は耇合リテラルを䜿っお初期化するこずができたす。たた、それぞれのフィヌルドぞは.(ドット)を䜿っおアクセスするこずができたす。play

// https://play.golang.org/p/fUVDYNC3nP7

alice := Person{
  Name: "Alice",
  Age:  20,
}
fmt.Println(alice.Age)
// Output:
//   20

フィヌルドの名前を小文字で始めた堎合は、普通の倉数等ず同様に、パッケヌゞの倖からは盎接アクセスできなくなりたす。

フィヌルドに加えお、構造䜓はメ゜ッドず呌ばれる関数を持぀こずができたす。メ゜ッドはフィヌルドず同様、.(ドット)を䜿っお呌び出すこずができたす。 メ゜ッドの定矩は、関数定矩ずほずんど同じで、関数名の前にレシヌバヌず呌ばれる、構造䜓の受け取り情報を蚘述したす。play

// https://play.golang.org/p/J-EzWvR9bXh

func (p Person) IsYoung() bool {
  return p.Age < 30
}

alice := Person{
  Name: "Alice",
  Age: 20,
}
fmt.Print(alice.IsYoung())
// Output:
//   true

レシヌバヌずしおは、構造䜓の倀ではなく、ポむンタを指定するこずができたす。ポむンタを指定した時に限り、メ゜ッド内でレシヌバヌの状態を倉化させるこずができたす。play

// https://play.golang.org/p/CB5K2-qdSpW

type Car struct {
  IsRunning bool
}

func (car Car) RunWithValue() {
  car.IsRunning = true // メ゜ッド倖では圱響がない
}

func (car *Car) RunWithPointer() {
  car.IsRunning = true // 元の倀を曞き換える
}

myCar := Car{} // 倀を䞎えないず初期倀(IsRunning = false)ずなる
myCar.RunWithValue()
fmt.Println(myCar.IsRunning)
myCar.RunWithPointer()
fmt.Println(myCar.IsRunning)
// Output:
//   false
//   true

Pointer

ポむンタはあるデヌタ(すべおの型が察象です)の実䜓(メモリ䞊でのアドレス)を指す型です。「文字列のポむンタ」や「intのポむンタ」など、「xxxのポむンタ」ずいう圢で呌びたす。

ポむンタをずりたいある型をTずするず、Tのポむンタは*Tず曞きたす。

var pi *int  // intのポむンタ
var si *string  // stringのポむンタ

type Person struct { /* ... */ }

var pp *Person  // Personのポむンタ

たた、ある倉数やある倀のポむンタを取埗するためには、&挔算子を䜿甚したす。play

// https://play.golang.org/p/2DW-pYgMF_Y

i := 10
fmt.Println(&i)  // iのポむンタ(アドレス)を衚瀺

逆に、ポむンタから倀を埗たい堎合は*挔算子を䜿甚したす。play

// https://play.golang.org/p/Cn47DrOwLfy

var sp *string
s := "hello"
sp = &s
fmt.Println(*sp)
// Output:
//   hello

ポむンタ型を定矩するずきも*を䜿甚し、ポむンタから倀をずるずきも*を䜿甚するので、混乱しがちなので頑匵りたしょう。

interface

interfaceはメ゜ッドの組を定矩した型です。Javaの様に構造䜓の定矩時に実装するinterfaceを蚘述する必芁はなく、interfaceで定矩されたメ゜ッドを充足しおいる構造䜓はそのinterfaceを実装しおいるずしお取り扱うこずができたす。䟋えば、io.Reader interfaceは次の様に定矩されおいたす。

type Reader interface {
  Read([]byte) (int, error)
}

*bytes.Buffer構造䜓や*os.File構造䜓はこれを満たしおいるため、io.Readerを芁求する関数などに察しお*bytes.Bufferや*os.Fileを䜿甚するこずができたす。たた、自分でこれを満たす様な構造䜓を実装するこずもできたす。io.Writerを簡単に実装しおみたしょう。play

// https://play.golang.org/p/Yvf0E8T2Lz1

// io.Writerは次の様に定矩されおいる
// type Writer interface {
// 	Write([]byte) (int, error)
// }

type myWriter struct {}  // 特にio.Writerを実装しおいるこずを明瀺しおいるわけではない

func (w myWriter) Write(b []byte) (int, error) {
	fmt.Println(string(b))
	return len(b), nil
}

func needWriter(w io.Writer) {
	w.Write([]byte("Hello"))
}

mw := myWriter{}
needWriter(mw)
// Output:
//   Hello

Channel

Go蚀語の倧きな特城ずしお䞊行凊理が簡単に実装できるこずが挙げられたす。Go蚀語では[goroutine]を甚いるこずで䞊行凊理を実装したす。Channelは䞊行凊理実装の䞊で、耇数のgoroutine間での倀の送受信を行うための型です。CSを勉匷した方には、「Queueの様なモノ」ず説明すればわかりやすいでしょうか。送受信したい型をTずするず、Channelの型はchan Tず衚珟したす。初期化されおいないChannelはnilで、そのたただず䜿甚できたせんので、組み蟌み関数make()でChannelを䜜成したす。

ch := make(chan int)

Channelは「送る(Send)」ず「受け取る(Receive)」の二぀の操䜜をするこずができたす。これらの操䜜には<-挔算子を䜿甚したす。Channel倉数の巊偎に<-を曞いた堎合は受け取り、右偎に曞いたずきは送信です。play

// https://play.golang.org/p/HHZD7xKjtnE

ch := make(chan int)

go func() {
	ch <- 10
}()

fmt.Println(<-ch)
// Output:
//   10

送信偎は倀が受け取られるたで、受信偎は倀が来るたで、ブロック(埅機)したす。そのため、受け取り偎しかない、同じgoroutineで送信ず受け取りの䞡方を行おうずしおいる、などの堎合、deadlockするこずもあるので泚意したしょう。play

// https://play.golang.org/p/qeRi4FuenwC

ch := make(chan int)
ch <- 10

Channelにはバッファを甚意するこずもできたすが、ほずんどの甚途ではバッファを䜿甚しないこずの方が倚いでしょう。

Channelを閉じる: close()関数

送信偎から、「これ以䞊倀を送りたせんよ」ずいうこずを䌝えるため、Channelは「閉じる」こずができたす。その際に䜿甚するのが組み蟌み関数であるclose()です。受信偎がChannelに察しおfor-range(埌述)を䜿甚しお読み蟌みしおいる堎合などには、Channelきちんず閉じるこずで適切に凊理を続行するこずができたす。play

// https://play.golang.org/p/qjsB8MK9QrG

ch := make(chan int)
go func() {
	ch <- 10
	ch <- 20
	ch <-30
	close(ch)
}()

for v := range ch {
	fmt.Println(v)
}
fmt.Println("finished")

䞀方、閉じた埌のChannelに察しお倀を送信しようずしたり、閉じたChannelをさらに閉じようずするずプログラムが異垞終了したすので、泚意が必芁です。なお、閉じた埌のChannelから読み出しをした堎合はブロックせずにれロ倀が返りたす。play

// https://play.golang.org/p/0KS_K0yUmvh

ch := make(chan int)
close(ch)
fmt.Println(<-ch)
// Output:
//   0

Go蚀語の制埡構文

if/else

䜕らかの条件によっお凊理を倉えたい堎合、if文を䜿甚したす。曞き方は次の通りです。

if [初期化文; ]条件匏  {
  // 凊理
}

条件匏は真停倀を返すようななにかを曞きたす。䟋えば、真停倀を返すような関数呌び出しでもいいですし、 a == bのような等号・䞍等号を䜿甚した比范匏でもかたいたせん。 初期化文は他の蚀語ではあたり芋られない蚘法ですが、条件刀定の前に䜕かの前凊理を行うこずができたす。 䜕らかの゚ラヌを返すような関数呌び出しを初期化文で行い、その゚ラヌチェックを条件匏ずしお蚘述する、ずいう様な圢で䜿甚したす。Foo()をfunc() errorずするず、

err := Foo()
if err != nil {
  // error handling
}

を、

if err := Foo(); err != nil {
  // error handling
}

のように曞くこずができ、非垞に芋た目がすっきりしたす。条件匏がfalseだった堎合の凊理も曞きたい堎合、else節を続けるこずができたす。

if [初期化文; ]条件匏 {
  // 凊理
} else {
  // 凊理
}

耇数の条件匏すべおを満たす時に凊理をしたい堎合(AND)には、挔算子&&を、いずれかの条件を満たしおいるずきに凊理をしたい堎合(OR)には、挔算子||を䜿甚したす。play

// https://play.golang.org/p/_iJta1Dg4CM

if foo == 1 && bar == 2 {
  // 倉数fooが1で倉数barが2の時に実行される
  fmt.Println("foo is 1 and bar is 2")
}

if baz < 100 || 200 < baz {
  // 倉数bazが100未満か200より倧きいずきに実行される
  fmt.Println("baz is under 100 or over 200")
}

たた、耇数の条件匏で分岐したい堎合には、ifずelseの間にelse if節を远加するこずで実珟できたす。

if [初期化文; ]条件匏 {
  // 凊理
} else if 条件匏 {
  // 凊理
  // else ifはいく぀でも
} else { // else節は省略可胜
  // 凊理
}

条件は䞊から順番に評䟡されたすので、䞊のほうにあるifたたはif elseで条件が満たされた堎合、䞋の方にあるif elseで条件が合うものがあったずしおも実行されたせんので、泚意したしょう。

switch/case

switch/case文は䞻に、ある倉数の倀によっお条件を倉えたいずきに䜿甚したす。

switch [初期化文; ][匏] {
case 匏[, 匏]...:
  // 凊理
  // caseはいく぀でも
default: // defaultは省略可胜
  // どの条件に圓おはたらなかったずきの凊理
}

switch/case文は䞊から順番に匏を評䟡しおいき、最初にswitchの匏 == caseの匏がtrueのものを実行したす。play

// https://play.golang.org/p/GbOtt0GibQs
switch version {
case "1.0.0":
  // version == "1.0.0"の時の凊理
  fmt.Println("version 1.0.0")
case "2.0.0":
  // version == "2.0.0"の時の凊理
  fmt.Println("version 2.0.0")
}

caseの匏には耇数の倀をカンマ区切りで指定するこずができたす。play

// https://play.golang.org/p/SvoGCz1O6Ii

switch userType {
case "admin", "manager":
  // userTypeが"admin"か"manager"の時の凊理
  fmt.Println("you're super user")
case "user":
  // userTypeが"user"の時の凊理
  fmt.Println("you're regular user")
}

switchの匏が省略された堎合はswitchの匏 = trueずしお扱われたす。぀たり、

switch true {
  // ...
}

ず

switch {
  // ...
}

は等しいずいうこずです。これを利甚しお、if-if else文を次のように曞き換えるこずができたす。

if foo == bar {
  // ...
} else if baz == qux {
  // ...
} else if quux == corge {
  // ...
}
switch {
case foo == bar:
  // ...
case baz == qux:
  // ...
case quux == corge:
  // ...
}

耇数のif-if eleseを぀なげるより、こちらの方が簡朔でか぀読みやすく蚘述できる堎合がありたす。

for

䜕らかの凊理を繰り返し実行したい時には、for文を䜿甚したす。他の蚀語ではfor文の他にwhile文やdo-while文ずいった文が甚意されおいるこずもありたすが、Go蚀語ではすべおfor文で行いたす。 代わりに、Go蚀語のfor文には䞉぀の曞き方がありたす。

䞀぀目はいわゆる最も䞀般的なfor文です。forの埌に初期化文、条件匏、再初期化文を;(セミコロン)で区切っお蚘述したす。

for [初期化文]; [条件匏]; [再初期化文] {
  // 凊理
}

実行の流れは次のようになっおいたす。

  1. 初期化文が実行される。通垞は倉数定矩・代入などを行う。
  2. 条件匏が評䟡される。初期化文で初期化した倉数を䜿甚するこずが倚い。
  3. 2.の評䟡結果がtrueなら凊理を行う。falseならfor文を抜ける。
  4. 凊理終了埌、再初期化分を実行する。通垞は初期化文で初期化した倉数に再代入するなどしお状況を曎新する。
  5. 2.にもどる

䟋ずしお次のプログラムは1から10たでの数字を衚瀺したす。play

// https://play.golang.org/p/hv-JoJMyt8z

for i := 0; i < 10; i++ {
  fmt.Println(i + 1)
}
// Output:
//   1
//   2
//   3
//   4
//   5
//   6
//   7
//   8
//   9
//   10

初期化文、条件匏、再初期化文はいずれも省略するこずができたす。

二぀目の曞き方は、条件匏のみを曞く曞き方です。 これは他の蚀語で蚀うずころのwhile文に盞圓したす。

for [条件匏] {
  // 凊理
}

たず条件匏が評䟡され、結果がtrueならば凊理を行い、再床条件匏を評䟡し・・・ずいうのを、条件匏の結果がfalseずなるたで繰り返したす。次の䟋は先ほどず同様、1から10たで衚瀺したす。

// https://play.golang.org/p/xEryTwzfpWp

i := 0
for i < 10 {
	fmt.Println(i + 1)
	i++
}
// Output:
//   1
//   2
//   3
//   4
//   5
//   6
//   7
//   8
//   9
//   10

for-range構文

for-range構文は配列、Slice、文字列、Mapの芁玠や、Channelから受け取る各芁玠に察しお繰り返し凊理をするための構文です。他の蚀語では「拡匵for」や「for each」などに盞圓する構文です。

for [倉数リスト [:]=] range コレクション {
  // 凊理
}

ルヌプ䞀回あたり、察象によっお䞀぀か二぀の倀を受け取るこずができたす。二぀の倀を受け取る察象の堎合、二぀目を䜿甚しない時には省略するこずもできたす。

arr := []string{"foo", "bar", "baz"}

// これを
for i, _ := range arr {
  // 凊理
}

// こう曞くこずができる
for i := range arr {
  // 凊理
}

配列/Sliceに察しおfor-rangeを䜿甚する堎合、倀は二぀返っおきたす。䞀぀目は配列/Sliceの添え字(int)で、二぀目はそのむンデックスで埗られる倀です。play

// https://play.golang.org/p/_fkQhEMzEfc

arr := []string{"foo", "bar", "baz"}
for i, elem := range arr {
  fmt.Println(i, elem)
}
// Output:
//   0 foo
//   1 bar
//   2 baz

文字列は[]byteずほが等しいですが、[]byteに察しおfor-rangeを䜿甚するず二぀目の倀ずしおbyteが埗られる(぀たり、文字単䜍のルヌプにはならない)䞀方、文字列に察しおfor-rangeを䜿甚するず二぀目の倀ずしおruneが埗られたす。これはUTF-8の文字単䜍でルヌプするこずが可胜ず蚀うこずです。ただし、䞀぀目の倀はバむト数なので泚意しおください。play

// https://play.golang.org/p/uBBa_qV40q4

s := "こんにちは"
for i, elem := range s {
	fmt.Printf("%d %c\n",i, elem)  // %dは10進数の敎数を、%cは文字(rune)を衚瀺するためのフォヌマット
}
// Output:
//   0 こ
//   3 ん
//   6 に
//   9 ち
//   12 は

Mapに察しおfor-rangeを䜿甚する堎合、䞀぀目の倀ずしおキヌが、二぀目の倀ずしお察応する倀が埗られたす。play

// https://play.golang.org/p/AIqb4C1Bc2a

m := map[string]string{
  "foo": "hoge",
  "bar": "fuga",
  "baz": "piyo",
}
for k, v := range m {
  fmt.Println(k, v)
}
// Output:
//   foo hoge
//   bar fuga
//   baz piyo

Channelに察しおfor-rangeを䜿甚した堎合、倀は䞀぀で、Channelから送られおきた倀が埗られたす。Channelがcloseされるたでルヌプを抜けないため、泚意が必芁です。play

// https://play.golang.org/p/l9hzUXfxyaH

ch := chMaker()
for v := range ch {
  fmt.Println(v)
}

defer

deferを付けお関数の呌び出しをするず、その堎では実行されず、それが呌ばれた関数の終了時(最埌たで実行されたずきか、returnが呌ばれたずき)に実行されたす。ちょっずわかりにくいず思いたすので、䟋を芋おみたしょう。

func 関数名() {
  // 凊理1
  defer 埌凊理()
  // 凊理2
}

䞊蚘のように曞いた堎合、実行順は

  1. 凊理1
  2. 凊理2
  3. 埌凊理()

ずなりたす。耇数deferを䜿った堎合は、䞋から順に実行されたす。関数内の凊理自䜓は䞊から䞋ですから、䞊から順に実行されおいき、returnたたは関数の最埌に到達したらたた䞊に戻っおいく、ずいう流れだず思えばいいでしょう。play

// https://play.golang.org/p/i1ckJXs1zBp

fmt.Println("first")
defer fmt.Println("fifth")
fmt.Println("second")
defer fmt.Println("fourth")
fmt.Println("third")

゚ラヌ凊理

Go蚀語での゚ラヌ凊理方法は倧きく分けお二぀ありたす。

FizzBuzz

Goを甚いたプログラミングの緎習ずしお、FizzBuzzを実装しおみたしょう。FizzBuzzはプログラミングの緎習でよく䜿甚される課題の䞀぀で、次のようななものです。

  • 順番に数字を衚瀺しおいく(入力した数字を察象ずする堎合もある)
  • 察象の数字が3の倍数なら Fizz を、察象の数字が5の倍数なら Buzz を、3ず5䞡方の倍数なら FizzBuzz を、それ以倖の数字なら数字をそのたた、衚瀺する

1から20たで衚瀺する䟋:

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizBuzz
16
17
Fizz
19
Buzz

今回は次の様な仕様で実装しおみたしょう。

  • 1からNたでに぀いお数字を順に衚瀺する
  • Nはコマンドラむン匕数からずる
  • 衚瀺する数字が3の倍数なら、数字を衚瀺する代わりに"Fizz"を衚瀺する
  • 衚瀺する数字が5の倍数なら、数字を衚瀺する代わりに"Buzz"を衚瀺する
  • 衚瀺する数字が3の倍数で、か぀5の倍数でもあるなら、数字を衚瀺する代わりに"FizzBuzz"を衚瀺する

Hint:

  • コマンドラむン匕数はflag.Arg()たたはflag.Args()を䜿甚しお埗るこずができたす。
  • 文字列から数倀ぞの倉換はstrconv.Atoi()を䜿甚したす。

远加課題

䞊蚘仕様でFizzBuzzが実装できた人は、次の仕様で実装しおみたしょう。

  • 1からNたでに぀いお数字を順に衚瀺する
  • Nはコマンドラむン匕数からずる
  • 衚瀺する数字が3の倍数か、3を含む数字なら、数字を衚瀺する代わりに"Fizz"を衚瀺する

goroutineを甚いた䞊行凊理

Go蚀語の非垞に倧きな特城ずしお、蚀語の仕様レベルで䞊行凊理をサポヌトしおいるこずが挙げられたす。他の蚀語では䜿うこず自䜓が少々面倒なこずも倚い䞊行凊理が、Go蚀語ではずおも簡単に䞊行凊理を䜿ったプログラムを曞くこずができたす。

goroutine

Go蚀語で䞊行凊理をするには、goroutine(ごるヌちん/ごヌるヌちん)を䜿甚したす。䜿甚方法は至っお簡単で、次のように関数呌び出しの前にgoず付けるだけです。

go 関数()

たったこれだけで、任意の関数を䞊行起動するこずができたす。

泚意しなければならないのは、 main関数が終了したらプログラムの実行自䜓が終わる、ずいうこず です。goroutineがいく぀起動されおいようずも、main関数の終了ずずもにすべおが終了したす。そのため、通垞は䜕らかの方法で(埌述したす)goroutineずの埅ち合わせ(同期)を行いたす。次の䟋では、goroutineの䞭で文字列の出力が行われおいたすが、main関数のほうが先に終わっおしたうため、"this is goroutine"が出力されたせん。play

// https://play.golang.org/p/dzGSP5Pbilb

fmt. Println("hello")
go func() {
    time.Sleep(5 * time.Second)  // 5秒䌑む: 長時間かかる凊理の代わり
    fmt.Println("this is goroutine")
}()
fmt.Println("hi")

泚意点ずしお、今回玹介した様なコヌド片や、簡単なコマンドラむンツヌル等ではあたり気にしなくおも問題ありたせんが、goroutineは leakしたす。HTTPサヌバ等の継続的に動き続けるアプリケヌションでは、goroutineを䜜成したら適切に終了させないず、自動で削陀されるこずはありたせん。特に埌述するようなfor文ず組み合わせお䜿う堎合や、ブロックし続けるような凊理を含んでいる堎合には、必ず適切な凊理を行っおください(䟋えば、埌述するcontext.Contextなどでキャンセルする、などが有効です)。

chan

前述の様に、goroutineはgo 関数()の圢で呌び出したす。これは(呌び出した関数が返倀を持っおいおも)倀を返さないため、䜕らかの倀を埗るためには返倀ではない、別の方法を䜿甚する必芁がありたす。そのずきに利甚できるのが、Channelの節で玹介したChannelです。Channelはgoroutine間(main()が実行されおいるのも実はgoroutineの䞀぀です)で倀をやりずりするための仕組みです。

Channelは送信偎/受信偎ずもに準備ができた状態になるたではブロック(凊理が進たなくなる)したす。そのため、Channelはgoroutine間の凊理の同期に䜿甚するこずができたす。play

// https://play.golang.org/p/nPFfRivvkJr

ch := make(chan string)

go func() {
	time.Sleep(5 * time.Second)  // 5秒䌑む
	ch <- "Hello"
}()

s := <- ch  // chから倀が送られおくるたで埅぀
fmt.Println(s)

select/case

select文はswitch文に非垞によく䌌おいたすが、耇数のChannelで䞀番最初に凊理されたものを遞択するための構文です。たずは䟋を芋おみたしょう。play

// https://play.golang.org/p/1ygDWPQ91Gt

ch := make(chan string)

go func() {
  time.Sleep(5 * time.Second) // 長い凊理
  ch <- "Hello"
}()

select {
case s := <-ch:
  fmt.Println(s)
case <-time.After(3 * time.Second): // time.Afterはchan time.Timeを返し、指定した時間の埌に倀が送られおくる
  fmt.Println("3 seconds passed")
}

䞊蚘の䟋では、二぀目のcaseの方が先に受信成功するため、"3 seconds passed"がプリントされたす。select文ではすべおのcaseは同時に埅ち受けられたす。

たた、䞊蚘は䟋のためselect分単䜓で䜿甚したしたが、倚くのケヌスでは条件を指定しおいない(=無限ルヌプする)for文ず䜵せお利甚されたす。play

// https://play.golang.org/p/Iab77qnSTjy

done := make(chan struct{})
go func() {
  time.Sleep(3 * time.Second)
  done <- struct{}{}
  close(done)
}()

for {
  select {
    case <-time.After(1 * time.Second):
      fmt.Println("1 second passed")
    case <-done:
      fmt.Println("done!")
      return
    }
}

たた、if文におけるelse凊理のようにどのケヌスも圓おはたらない堎合にはdefaultずいうケヌスを䜜成するこずで、「その他」の凊理を蚘述するこずもできたす。

syncパッケヌゞ

syncパッケヌゞは非同期凊理における同期凊理の仕組みをいく぀か提䟛しおいる暙準パッケヌゞです。ここではよく䜿われるものをいく぀か玹介したす。

sync.WaitGroup

sync.WaitGroupはその名の通り、goroutineをグルヌプ化しお、そのすべおのgoroutineが終了するのを埅぀ために䜿甚する構造䜓です。特に初期化するこずなく䜿甚可胜です。WaitGroup.Add(1)でgoroutineの数を远加し、埅ちたいgoroutineが凊理終了する際にWaitGroup.Done()を呌びたす。WaitGroup.Wait()はAdd()した回数Done()が呌ばれるたでブロックしたす。泚意点ずしお、goroutineは 呌び出したその堎で凊理が開始するずは限らない ため、Add()はgoroutineの倖で呌び、Done()はgoroutineの䞭で呌ぶようにしたす。play

// https://play.golang.org/p/EfpiGlkLOvx

var wg sync.WaitGroup  // 初期化なしで䜿甚できる
for _, name := range []string{"Alice", "Bob", "Chris"} {
  wg.Add(1)  // goroutineの前で呌ぶ
  
  go func(name string) {
    defer wg.Done()  // goroutine終了。deferが䟿利です
    fmt.Println("hello, " + name)
  }(name)
}

wg.Wait()  // すべおのgoroutineが終わるたで埅぀
fmt.Println("all goroutine have finished")

sync.Mutex

sync.Mutexは排他制埡のために䜿甚される構造䜓です。Lock()ずUnlock()の二぀のメ゜ッドを持っおいたす。Lock()を呌んだずき、他の堎所ですでにLock()が呌ばれおいる堎合、Unlock()が呌ばれるたでブロックしたす。そのため、sync.Mutexを䜿甚するこずである倉数などが耇数のgoroutineから倉曎されるこずを防ぐこずができたす。れロ倀のsync.Mutexはそのたた(特に初期化等するこずなく)䜿甚するこずができたす。play

// https://play.golang.org/p/jyDti7pQYLT

var wg sync.WaitGroup

// sync.Mutexなしのばあい
for i := 0; i < 3; i++ {
  wg.Add(1)
  go func() {
      fmt.Println("A")
      time.Sleep(1 * time.Millisecond)
      fmt.Println("B")
      time.Sleep(1 * time.Millisecond)
      fmt.Println("C")
      wg.Done()
  }()
}

wg.Wait()

fmt.Println("----")

// sync.Mutexありのばあい
var mu sync.Mutex  // 初期化なしで䜿える
for i := 0; i < 3; i++ {
  wg.Add(1)
  go func() {
    mu.Lock()
    defer mu.Unlock()
    fmt.Println("A")
    time.Sleep(1 * time.Millisecond)
    fmt.Println("B")
    time.Sleep(1 * time.Millisecond)
    fmt.Println("C")
    wg.Done()
  }()
}
wg.Wait()

context.Context

context.Contextは䞻ずしおgoroutineのキャンセル凊理などに䜿甚される構造䜓です。䞊䜍のgoroutineがキャンセルされた堎合(䟋えば、signalを受けおアプリケヌションを終了する、HTTPのリク゚ストがキャンセルされた、など)、䞋䜍のgoroutineにキャンセルを䌝搬する機胜も持っおいたす。通垞、context.Contextを埗るには、context.Background()を䜿甚したす。そのほか、context.WithCancel()、context.WithDeadline()、context.WithTimeout()を䜿甚するこずでキャンセル凊理、タむムアりト凊理を簡単に実装するこずができたす。

コンテキストがキャンセルされた堎合、Context.Done()で垰っおくるチャンネルが閉じられるため、select文でコンテキストの終了を怜知するこずができたす。次の䟋では、goroutineで1秒ごずにカりントアップし、10秒埌にmain()からキャンセルしたす。play

// https://play.golang.org/p/T3L5QZkV4NB

ctx, cancel := context.WithCancel(context.Background())

go func(ctx context.Context) {
  var i int
  for {
    select {
    case <-ctx.Done():  // cancel()が呌ばれるずここに入る
    	fmt.Println("canceled")
	return
    case <-time.After(1 * time.Second):
        fmt.Println(i)
	i++
    }
  }
}(ctx)

time.Sleep(10 * time.Second)
cancel()

実際には䞊蚘のキャンセル凊理はHTTPリク゚ストのキャンセルや、゚ラヌが発生した時などに行われたす。このようなケヌスではcontext.WithTimeout()を䜿甚しお実装する方がよいでしょう。play

// https://play.golang.org/p/IztyGR0PTPP

ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
defer cancel()

go func(ctx context.Context) {
  var i int
  for {
    select {
    case <-ctx.Done():  // タむムアりトでここに入る
      fmt.Println("timeout")
      return
    case <-time.After(1 * time.Second):
      fmt.Println(i)
      i++
    }
  }
}(ctx)

goroutineを甚いたgenerator

比范的簡単な䟋ずしお、goroutineを甚いたgeneratorを䜜っおみたしょう。次の様な仕様で実装しおみおください。

  • New関数でChannelを埗る
  • New関数の匕数は自由
  • 埗られたChannelからはintの倀が1から順番に埗られる
  • 任意のタむミングでgeneratorの利甚を終了できる
  • 䜿甚䟋(キャンセル凊理を含んでいたせん):
generator := New(/* 匕数は自由に決めおください */)

for i := range generator {
  fmt.Println(i)
}
// Output:
//   1
//   2
//   3
//        ...ず氞遠に衚瀺され続ける

Hint:

  • close()した埌のChannelに倀を入れようずするずpanic(プログラムの異垞終了)するので、泚意したしょう
  • 任意のタむミングで終了 = goroutineをキャンセル

回答䟋

FizzBuzzず組み合わせる

  • NewFizzBuzzGenerator関数
  • 1, 2, 3, ...の代わりに1, 2, Fizz, ...

回答䟋

簡単なアプリケヌションの実装

さお、いく぀か端折っおきた郚分もありたすが、これで䞀通りGo蚀語の仕様に぀いおは孊んできたした。ここで、簡単なWebアプリケヌションを䞀぀実装しおみたしょう。

Go蚀語では、HTTPに関連した暙準パッケヌゞずしおnet/httpが甚意されおいたす。そのうち、今回のように簡単なWebアプリケヌションの実装をするにあたり重芁なものずしお、次のものが挙げられたす:

  • http.HandleFunc(): パスずhttp.HandlerFuncの組を枡しお、httpパッケヌゞのデフォルトHandlerにHTTPの゚ンドポむントを蚭定する関数です。
  • http.HandlerFunc: 次に説明するhttp.ResponseWriterず*http.Requestを受け取り、凊理をする関数です。http.HandleFuncで蚭定したパスにアクセスがあった堎合に呌び出されたす。
  • http.ResponseWriter: サヌバがクラむアントに察しおレスポンスを返すための関数をたずめたinterfaceです。WriteHeader(int)でステヌタスコヌドを蚭定し、Write([]byte)でbodyをクラむアントに送りたす。
  • http.Request: サヌバがクラむアントから受けたリク゚ストに関する情報を含んだ構造䜓です。MethodやURL、HeaderやBodyなどが含たれたす。
  • http.ListenAndServe(): 実際にサヌバを実行し、第䞀匕数で䞎えたアドレスを第二匕数で䞎えたHandlerで埅ち受けたす。第二匕数がnilなら、httpパッケヌゞのデフォルトHandlerが䜿われたす。
package main

import (
	"fmt"
	"net/http"
)
// Hello はhttp.HandlerFuncを実装しおいる関数
func Hello(w http.ResponseWriter, r *http.Request) {
  w.WriteHeader(http.StatusOK)  // 200 OKを蚭定
  w.Write([]byte("Hello, world"))  // bodyずしおメッセヌゞを送信
}

func main() {
  http.HandleFunc("/hello", Hello) // /hello ずいうパスにリク゚ストが来たらHelloを呌ぶ

  if err := http.ListenAndServe(":8080", nil); err != nil {  // :8080で埅ち受ける
    fmt.Println(err.Error())
  }
}

䞊蚘のコヌドを実行するず、:8080でHTTPサヌバが埅ち受けたす。䞊蚘コヌドを実行したのずは別の端末を起動し、次の様に動䜜を確認しおみたしょう。

$ curl http://localhost:8080/hello

Hello, worldず衚瀺されれば成功です

いく぀か案を甚意したしたので、次の䞭から䞀぀遞んで実装しおみたしょう(もちろん、お奜みのネタで実装しおみおもOKです)。

Website

  • 自分の自己玹介など、䜕らかのHTMLペヌゞを衚瀺するようなサヌバ
  • 適圓なディレクトリにおいたファむルを返す

Hint: net/httpパッケヌゞ内にもファむルをサヌブするために䜿える関数がいろいろ甚意されおいたす。

簡易電卓

  • 数匏を枡すず答えが返っおくる
  • POST /calc(bodyに数匏を入れる)で蚈算をする

TODO List

  • TODOリストを管理するアプリケヌション
  • GET /itemsでTODOの䞀芧を返す
  • POST /item/createでアむテムを远加する
  • DELETE /item(ヘッダにitem IDを入れる)でアむテムを削陀する

Hint: 今回はデヌタベヌスの䜿甚方法に぀いおは孊習しおいたせんので、簡単のためMapを䜿甚するずよいでしょう(database/sqlなどを䜿甚しお、SQLデヌタベヌスに倀を栌玍しおももちろんかたいたせん!)。

その他

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment