JavaプログラマーのためのGo言語入門
こちらはJava to Go in-depth tutorialの日本語訳です
原文の著者に許諾を得て翻訳・公開いたします。
このチュートリアルは、JavaプログラマーがすばやくGo言語にキャッチアップできるようにすることを目的としています。
目次
- Hello stack
- 主な違い
- シンタックス(文法)
- 定数
- 構造体
- ポインタ
- スライス
- 値の作成
- メソッドとインターフェース
- エラー
- PanicとRecover
- ゴルーチンとチャネル
- Hello server
Hello stack 1
まずはじめに簡単な例を見ていきましょう。この例ではシンプルな抽象データ型をGoで実装しています。
// collectionパッケージはstring型を格納できるスタックを実装している |
- 最上位の宣言文の前に直接書かれているコメントはドキュメントコメントになります。ドキュメントコメントはプレーンテキストで書かれます。
- 変数を宣言するときは、変数名の後ろに型を書きます。
- 構造体(
struct
)はJavaでいうところのクラスに該当します。Goの構造体はメソッドをメンバーに含めることはできません。変数のみを構造体のメンバーに含めることができます。 - コード上の
(s *Stach)
という部分でメソッドのレシーバーを宣言しています。これはJavaでいうところのthis
に該当します。 :=
という演算子は変数の宣言と初期化を同時に行ってくれます。変数の型は初期化式から自動で導かれます。
以下のコードは、抽象データ型 collection.Stack
を用いたHello worldプログラムです。
package collection_test |
- この
collection_test
というテストパッケージはcollection
パッケージと同じディレクトリに配置します。 - 1つめの
import
文のfmt
はGoの標準パッケージです。 2つめimport
文は“go-for-java-programmers/collection”ディレクトリのパッケージを使うことを示しています。 - ソースコード上では
fmt
やcollection
という短い名前でこれらのパッケージにアクセスできます。
NOTE: Goでスタックを実装する慣用的な方法は、スライスを直接使用することです。詳しくはImplement a stack (LIFO)を参照してください。
主な違い
オブジェクト指向プログラミング
- Goにはコンストラクタを伴うクラスという概念がありません。インスタンスメソッド、クラスの継承構造、メソッドの動的ルックアップで実現したいことを、Goではstructとinterfaceを用いて実現します。
- Goでは、どんな型に対してもメソッドを作成できます。レシーバーをボックス化する必要もありません。レシーバーはJavaでいうところの
this
に対応します。レシーバーには値そのものかポインタが入ります。 - Javaの
public
やpackage-private
に似た2つのアクセスレベルがGoには存在します。トップレベルで宣言された 2変数や関数の名前が大文字で始まる場合はpublic
、小文字で始まる場合はpackage-private
のアクセスレベルになります。
関数型プログラミング
ポインタと参照
- Goは、オブジェクトや配列だけでなく、すべての型でポインタを使用できます。任意の型
T
には対応するポインタ型*T
が存在します。これは型T
の値へのポインタを示しています。 - Goでは無効なポインタに
nil
を使用しますが、Javaではnull
を使用します。 - Goの配列は値型です。配列が関数の引数として使用される場合、その関数は配列へのポインターではなく配列のコピーを受け取ります。実際には、配列ではなくスライスを関数の引数に渡すことがほとんどです。スライスは参照型です。
- 特定の型(マップ, スライス, チャネル)は値型ではなく、参照型です。つまり、マップを関数に渡してもマップはコピーされません。渡されたマップを関数内で変更した場合、変更は呼び出し元にも反映されます。Javaの用語を使うと、これはマップへの参照が行われているからだと説明できます。
組み込み型
- 文字列型はGoの言語仕様として組み込まれています。string はbyte型のスライスのように振る舞いますが、イミュータブルです。
- ハッシュテーブルはGoの言語自体に組み込まれています。Goではmapと呼びます。
エラーハンドリング
並行処理
用意されていない機能
- Goは暗黙的な型変換をサポートしていません。異なる型を混在させるような操作では、明示的な型変換が必要です。一方、数値型の定数を宣言するときには、型を指定せずに定数を宣言できます。この場合、宣言時の段階では、数値の上限は未確定になります。詳しくはUntyped numeric constants with no limitsを参照してください。
- Goでは関数のオーバロードをサポートしていません。同じスコープ内の関数及びメソッドにはユニークな名前を付ける必要があります。代替手段としてはOptional parameters and method overloadingを参照してください。
- Goには組み込みのスライスやマップといったジェネリクス及び、
append
やcopy
といったジェネリクス用関数があります。しかし、ジェネリクス用関数を独自実装できる機構はありません。代替手段としては、Generics (alternatives and workarounds)を参照してください。
シンタックス(文法)
宣言
変数の宣言の仕方は、Javaと比べると逆になっています。Goでは変数名の後ろに型名を記述します。これによってGoのコンパイラが「変数××の型は○○である」と解釈しやすくなります。
Goの書き方 | Javaでざっくりイコールな書き方 |
---|---|
var v1 int | int v1 = 0; |
var v2 *int | Integer v2 = null; |
var v3 string | String v3 = “”; |
var v4 [10]int | int[] v4 = new int[10]; (Arrays are values in Go.) |
var v5 []int | int[] v5 = null; |
var v6 *struct{ a int } | class C { int a; } C v6 = null; |
var v7 map[string]int | HashMap<String, Integer> v7; v7 = null; |
var v8 func(a int) int | interface F { int f(int a); } F v8 = null; |
一般的に、宣言ではキーワードの後ろにオブジェクト 3の名称が続きます。キーワードとはconst
、type
、var
やfunc
などです。キーワードの後ろに括弧でまとめて宣言を書くこともできます。
var ( |
関数を定義する時、引数の名前の付け方は統一する必要があります。それぞれの引数に名前をつけるか、または全く名前をつけないかです。いずれかに名前をつけ、いずれかは省略するということはできません。複数の引数が同じ型の場合、グループ化することは可能です。
func f(i, j, k int, s, t string) |
変数は宣言の時に初期化することもできます。初期化も行う場合、変数の型を特定することもできますが、必須ではありません。型が特定されていない場合は、初期化式の右辺の値の型が設定されます。
var v9 = *v2 |
もしも変数が明示的に初期化されていない場合でも、型は指定する必要があります。この場合、初期値は暗黙的にゼロ値(0、nil
、””など)が与えられます。Goの世界では初期化されていない変数は存在しません。
宣言の省略形
関数の中では、:=
で宣言を簡略化することもできます。例えばこの文は
v10 := v1 |
下の文と同様の意味を持ちます。
var v10 = v1 |
関数型
Goでは、関数は第一級オブジェクトに属しています。Goの世界では、引数と戻り値の型が同一の関数は全て同じ関数型をしているとみなされます。
type binOp func(int, int) int |
複数割り当て
Goでは代入で複数の値を割り当てることができます。右側の式は左側の被演算子に割り当てられる前に評価されます。
i, j = j, i // iとjを置き換える |
関数が複数の値を戻り値として返すこともできます。その場合括弧()の中に列挙して示します。戻り値を一度に複数の変数に保存することもできます。
func f() (i int, pj *int) { ... } |
ブランク識別子
ブランク識別子は_
(アンダースコア)で表され、複数の戻り値が返ってくる式で値を無視したい場合に用いられます。
v1, _ = f() // f()の関数から返ってきた2つ目の値を無視する |
セミコロンとフォーマット
セミコロンやフォーマットで悩む必要はありません。「gofmt」を使えば、唯一のスタンダードであるGoのスタイルに整形できます。このスタイルは最初は違和感を感じるかもしれませんが、他のスタイルと同じように良く、また慣れてしまえば快適なものとなるでしょう。
実際、セミコロンを使う機会はGoではめったにありません。理論上、Goの全ての宣言はセミコロンで終わります。しかしGoは、行が明らかに処理途中のものでない限り、空白でない行の終わりに暗黙的にセミコロンを挿入します。これによって、場合によっては改行が許されないケースもでてくるのです。例えば、下のような書き方は許されません。
func g() |
この場合、g()
のすぐあとにセミコロンが挿入されてしまいます。その結果、関数を定義しているのではなく関数を宣言しているとみなされてしまいます。同様に、下のような書き方もできません。
if n == 0 { |
この場合、else
の前の}
の直後にセミコロンが挿入されてしまい、結果として文法エラーになります。
if文(条件文)
Goではif文、for文の条件式、switch文の値を括弧()で囲みません。一方、if文やfor文のボディは中括弧{}で囲む必要があります。
if a < b { f() } |
さらに、if文やswitch文ではオプショナルな初期化式を記述することもできます。多くの場合これはローカル変数を設定するときに用いられます。
if err := file.Chmod(0664); err != nil { |
For文
Goにはwhile文もdo-while文もありません。for文を単一の条件と一緒に用いることができ、これがwhile文と同様の動きになります。条件を完全に省略すると無限ループ文となります。
for文は文字列(string
)、配列(array
)、スライス(slice
)、マップ(map
)やチャネル(channel
)を range
句に指定できます。通常であれば下のように書きますが、
for i := 0; i < len(a); i++ { ... } |
a
の各要素に対して繰り返して処理をしたい場合、下のように書くことができます。
for i, v := range a { ... } |
上の書き方では、i
にインデックスが割り当てられ、v
に配列やスライス、文字列などの要素の連続する値が割り当てられます。
- 文字列の場合は、
i
はバイトごとのインデックスとなり、v
はrune
型のUnicodeのコードポイント 4となります(rune
はint32
のエイリアスです)。 - mapでの繰り返しはキー・バリューのペアの反復値を生成しますが、チャネルは反復値を1つだけ生成します。
BreakとContinue
Javaと同じように、Goでもbreak
とcontinue
でラベルを指定できますが、for文、switch文、select文の中でラベルを参照する必要があります。
Switch文
Goのswitch文では、break
を書かなくても、switch
から抜け出ることができます。コードブロックの最後にfallthrough
文を置くことで、次の case
に処理を回すことができます。
switch n { |
しかし、case
は複数の値を持つことができます。
switch n { |
case
の中の値は、例えば文字列やポインタなど、等価比較演算子で扱うことのできるどんな型でも使えます。switch式がない場合、その式はtrue
とみなされます。
switch { |
インクリメントとデクリメント
++
と--
は後置演算子として文の中でのみ使うことができます。式の中で扱うことはできません。例えば、n = i++
と書くことはできません。
Defer文
defer文を使うことで、呼び出し元の関数がreturnされたタイミングで実行されるべき処理を記述できます。
defer宣言された関数は、呼び出し元の関数がどのようにretrunされたかに関わらず実行されます。 5
defer宣言された関数の引数は、defer宣言されたタイミングで計算され、実行時に使用されるまで保存されます。 6
f, err := os.Open("filename") |
定数
Goの定数はuntypedな状態にすることもできます 7。このルールは下記に適用されます。
- 数値リテラル、
- 型なしの定数のみを用いている式、
- 型が与えられていない、もしくは初期化式が型なしであるconst式
型なしの定数の値は、型のある値が必要になったタイミングで型定義されます。これにより、Goでは明示的な型変換が行われないにも関わらず、定数を比較的自由に扱うことができます。
var a uint |
Go言語では型定義のない数値の定数に上限値は明確にされません。型が必要になったときにのみ上限は適用されます。
const huge = 1 << 100 |
もしも変数宣言において型が定義されておらず、対応する式が型のない数値の定数だった場合、その数値は、値が文字列なのかintegerなのか浮動小数点なのか複素定数なのかによってrune
、int
、float64
かcomplex128
の型にそれぞれ変換されます。
c := 'å' // rune (int32のエイリアス) |
Goでは列挙型を扱いません。その代わりに、連続して増え続ける値を唯一const宣言できるiota`という特別な名称をつけることができます。constの初期化式が省略された時は、先に定義された式が再利用されます。
const ( |
構造体
構造体はJavaでいうクラスのようなものですが、構造体のメンバーにはメソッドを含めることはできません。構造体は変数のみで構成されます。構造体のポインタは、Javaでいう参照変数のようなものです。Javaのクラスとは対照的に、Goの構造体は直接の値として定義することもできます。どちらの場合でも、構造体のメンバーにアクセスするには.
を用います。
type MyStruct struct { |
Goでは、ユーザー定義型に対してメソッドを追加できます。これは構造体をベースにしたユーザー定義型に限った話ではありません。詳しくはメソッドとインターフェースをご参照ください。
ポインタ
int, struct, arrayの代入操作は、オブジェクト実体をコピーすることを意味します。Javaでいう参照変数をGoで実現するためにはポインタを使用します。
任意の型Tには、対応するポインタ型 *T
があり、型Tの値へのポインタを示します。
ポインタ変数が参照するメモリ領域を割り当てるには、組み込み関数 new
を使用します。これは、型を引数として受け取り、割り当てられたストレージへのポインタを返す関数です。割り当てられたストレージ領域は、その型に対応するゼロ値で初期化されます。例えば、new(int)
はint用にストレージの割り当てを新規で行い、その領域を値0で初期化し、そして *int
型を持つそのアドレスを返します。
T p = new T()
というJavaコードをGoコードに置き換えてみましょう。T
は2つの int
型インスタンスを持つクラスだとします。これに対応するGoコードは次のとおりです。
type T struct { a, b int } |
より慣用的には次のように書きます。
p := new(T) |
var v T
は型Tの値を保持するための変数を宣言していますが、こういった宣言方法はJavaには存在しません。
複合リテラルを使用して値を初期化することもできます。例えば:
v := T{1, 2} |
これは以下と同じです。
var v T |
型Tの変数xの場合、アドレス演算子 &x
はxのアドレス(*T
型の値)を提供します。例えば:
p := &T{1, 2} // pは型 *Tを持つ |
変数xがポインタ型変数の場合、ポインタの間接参照 *x
は、xが指す値を示します。ポインタの間接指定はほとんど使用されません。GoはJavaと同様に、変数のアドレスを自動的に取得できます。
p := new(T) |
スライス
スライスは概念的には下記の3つのフィールドをもつ構造体です。
- 配列に対するポインタ
- 長さ
- 容量
スライスでは[]
演算子を使ってスライス内部の配列の要素にアクセスします。
- 組み込み関数である
len
関数はスライスの長さ(length
)を返します。 - 組み込み関数である
cap
関数はスライスの容量(capacity
)を返します。
ある配列やスライス(例えばa
)から新規のスライスを生成する場合、a[i:j]
の形で生成できます。このa[i:j]
は
- インデックス
i
からインデックスj
の手前までのa
を参照したスライスになります。 j-i
の長さを持っています。i
が省略されていた場合、スライスは0を起点とします。j
が省略された場合、スライスはaの長さ(len(a))までの長さとなります。
新しくできたスライスはa
が参照しているものと同一の配列を参照します。つまり、新しいスライスで要素が変更された場合、a
の要素も同じように変更されます 8。
新しいスライスの容量は、純粋にa
からi
を引いた差分となります。配列の容量と配列の長さはイコールです。
var s []int |
もし、[100]byte
型の値(byte
100個分の配列、例えばバッファ)を作り、関数に参照渡しをしたいのであれば、[]byte
型の引数を持つ関数を宣言し、配列をスライスに変えて、その引数に渡してあげるのが良いでしょう 9。スライスは、下記に書いてあるようなmake
関数でも作り出すことができます。
スライスには組み込み関数append
が備え付けられており、JavaのArrayList
とほぼ同様の機能を持っています。
s0 := []int{1, 2} |
スライス構文は文字列と一緒に使うこともできます。文字列のスライスは、オリジナルの文字列の部分文字列を返します。
値の作成
Mapやチャネルの値は、組み込み関数であるmake
関数によって割り当てられていなければなりません。例えば、
make(map[string]int) |
をコールすると、新しくmap[string]int
型で割り当てられた値が返ってきます。
new
とは対照的に、make
はアドレスではなくオブジェクトそのものが返ってきます。これはMapやチャネルが参照型であるという事実に一致しています。
Mapの場合、make
では第2オプション引数に容量ヒントを渡すことができます。
チャネルの場合は、第2オプション引数はチャネルのバッファの容量となります。デフォルトは0
です(バッファがない状態)
make
関数はスライスを割り当てる場合にも使用されます。この場合make
関数は、スライスのもとになる配列にメモリを割り当て、それを参照するスライスを返します。必須の引数として、スライスの要素数を渡さなければなりません。第2オプション引数でスライスの容量を指定できます。
m := make([]int, 10, 20) // new([20]int)[:10]と同意 |
メソッドとインタフェース
メソッドは、レシーバーを持っていることを除いて、通常の関数定義のような見た目をしています。レシーバーは、Javaインスタンスメソッドのthis参照に似ています。
type MyType struct { i int } |
上記の例では、 MyType
に関連付けられた Get
メソッドを宣言しています。このメソッドの中で、レシーバーはpという名前を付けられています。
メソッドは定義済みの型に対して宣言されます。レシーバーを別の型に変換すると、変換後の新しい変数は変換前の型のメソッドではなく、変換後の型のメソッドを持つようになります。
組み込み型から派生した新しい型を宣言することにより、組み込み型にメソッドを定義できます。その新しい型は、もとの組み込み型とは全く別のものとなります。
type MyInt int |
インタフェース
GoインターフェースはJavaインタフェースに似ていますが、Goインタフェースの場合、インタフェースが要求するメソッド群を提供している型はみな、そのインタフェースの実装として扱われます。明示的な宣言は必要ありません。
以下のインタフェースが定義されているとします。
type MyInterface interface { |
MyType
はすでに Get
メソッドを持っているので、 Set
メソッドを追加することにより、 MyType
が MyInterface
を満たすようになります。
func (p *MyType) Set(i int) { |
MyInterface
を引数にもつ関数は皆、 *MyType
型の変数を受け容れます。
func GetAndSet(x MyInterface) {} |
Javaの用語を使うとすると、 *MyType
の Set
および Get
を定義すると、 *MyType
が自動的に MyInterface
を implement
します。
型は複数のインタフェースを満たすことができます。これはダックタイピングの一種です。
アヒルのように歩き、アヒルのように泳ぎ、アヒルのように鳴く鳥を見るとき、私はその鳥をアヒルと呼ぶ。
– James Whitcomb Riley
埋め込み(委譲)
型を匿名フィールドとして埋め込むことで、派生型を実装できます。
type MySubType struct { |
事実上、MySubTypeがMyTypeの派生型として実装されます。
func f2() { |
Set
メソッドは MyType
型から継承されます。これは匿名フィールドのメソッドが、派生型のメソッドへと昇格されるためです。
この場合、 MySubType
には MyType
型の匿名フィールドがあるため、 MyType
のメソッドは MySubType
のメソッドになります。Get
メソッドはオーバーライドされ、 Set
メソッドは継承されています。
これはJavaのクラス継承と同じではなく、委譲という方式をとっています。匿名フィールドのメソッドが呼び出されたとき、そのメソッドのレシーバは、派生型(MySubType
)の方ではなく、内包する匿名フィールド(MyType
)になります。つまり、匿名フィールドのメソッドは動的に派生型のメソッドとしてディスパッチされません。Javaの動的メソッドルックアップに相当するものが必要な場合、interface
を使用してください。
func f3() { |
型アサーション
あるインタフェース型が実装されている変数は、型アサーションを使用して、異なるインタフェース型を持つように変換できます。これは実行時に動的に変換されます。Javaとは異なり、2つのインタフェース間の関係を宣言する必要はありません。
type Printer interface { |
Printer
への変換は動的に行われます。xが Print
メソッドを定義している限り機能します。
エラー
Javaでは通常例外を使用するケースでも、Goでは2つの異なるメカニズムがあります。
- 大抵の関数ではエラーを返します。
- 本当にリカバーできない状況のとき、例えば範囲外のインデックスだった場合などにのみ、実行時の例外を生み出します。
Goでは複数の値を返すことができますが、それによって通常の戻り値に加え、詳細なエラーメッセージを返すことが簡単にできます。慣例的に、そのようなメッセージには、シンプルな組み込みインタフェースであるerror型が存在します。
type error interface { |
例えばos.Open
関数は、ファイルを開くことができなかった場合、nil
でないエラー値を返します。
func Open(name string) (file *File, err error) |
下記のコードではファイルを開くためにos.Open
関数を用いています。エラーが生じた場合は、エラーメッセージをログに出力して処理を中断するlog.Fatal
関数を呼び出します。
f, err := os.Open("filename.ext") |
エラーのインタフェースはError
のメソッドのみ必要としますが、特定のエラーとなるとしばしばその他のメソッドも持っています。それによって、呼び出し側がエラーの詳細を検知できます。
PanicとRecover
panicは、ゴルーチンのスタックを巻き戻し、途中でdefer宣言された関数を実行してからプログラムを停止するランタイムエラーです。
panicはJavaの例外処理(exceptions)に似ていますが、ランタイムエラーのみを対象としています。例えば、nil
ポインタを参照しようとしたときや、配列の範囲外領域にインデックスしようとしたときにpanicが発生します。EOFなどエラーイベントを表現するために、Goプログラムは上記の error
という組み込み型を使用します。
組み込み関数recoverを使用して、panic状態のゴルーチンの制御を取り戻し、通常の実行を再開できます。
recover
を呼び出すとスタックの巻き戻しが停止します。recover
はpanic
に渡した引数を返します。
巻き戻し中に実行されるコードはdefer宣言された関数内のコードのみであるため、recover
はdefer宣言された関数内でのみ有用です。ゴルーチンがパニックになっていない場合、recover
は nil
を返します。
ゴルーチンとチャネル
ゴルーチン
Goでは、go
文を使用して、新たなスレッド(ゴルーチン)を立ち上げることができます。go
文に続く関数は新しく作成されたゴルーチン上で実行されます。 1つのプログラム内のすべてのゴルーチンは、同じアドレス空間を共有します。
ゴルーチンは軽量であり、スタック領域割り当て程度のコストしかかかりません。はじめはスタックの割り当てを小さく抑え、必要に応じてヒープストレージへの割り当てと解放を行いながら大きくしていきます。内部的にゴルーチンは、複数のOSスレッド間で多重化されるコルーチンのように機能します。
go list.Sort() // list.Sort()はパラレルに実行される |
Goには関数リテラルがあります。関数リテラルはクロージャーとして機能し、 go
文と組み合わせると強力になります。
// delayで指定した時間が立つと、Publish関数は標準出力にtextを書き出す |
変数 text
および delay
は、Publish
関数とその内部の関数リテラルの間で共有されます。
チャネル
チャネルは、2つのゴルーチンの処理を同期させたり、通信させたりするメカニズムを提供します。 <-
演算子は、チャネルの方向(送信または受信)を指定します。方向が指定されていない場合、そのチャネルは送受信可能です。
chan Sushi // Sushi型の値を送受信するために使用できる |
チャネルは参照型であり、make
で作成できます。
ic := make(chan int) // バッファを持たないint型のチャネル |
チャネルに値を送信するには、<-
を二項演算子のように使用します。チャネルから値を受信するには、<-
を単項演算子のように使用します。
ic <- 3 // チャネルに3を送信する |
- チャネルがバッファを持たない場合、受信チャネルから値を取り出すまでの間、送信チャネルは処理をブロックします。
- チャネルがバッファを持つ場合、送信チャネルから渡される値がバッファに書き込まれる余地があるときは処理がブロックされません。逆に、バッファがいっぱいになっているときは、受信チャネルから値を取り出すまで、送信チャネルは処理をブロックします。
- 受信チャネルは取り出せる値が存在するまでの間、処理をブロックします。
close
関数はこれ以上チャネルに値を送信できないようにできます。
close
関数が呼び出されたとします。close
関数が呼び出されるまでの間にすでにチャネルに送信された値は問題なく受信チャネルから取り出すことができます。その後の受信処理ではブロックは発生されず、ゼロ値を返します。- 受信チャネルからは値の他に、チャネルが閉じているかどうかの指標を取り出すことができます。
ch := make(chan string) |
次の例では Publish
関数がチャネルを返すようにします。 text
が発行されたときにメッセージをブロードキャストするためにこのチャネルが使われます。
// delayで指定した時間が経過したのち、Publish関数はtextを標準出力に書き出す |
Publish関数はこのように使えます。
wait := Publish("important news", 2 * time.Minute) |
select文
select文はGoの重要な並行処理ツールキットの1つです。select
は通信時に続行する処理を選択します。
- どの通信も成功し得るとき、そのうちの1つがランダム選択され、対応する処理が実行されます。
- defaultのケースが存在しない場合、いずれかの通信が成功するまでselect文は処理をブロックします。
以下のコードは、select文を使用した乱数ジェネレーターの実装例です。
rand := make(chan int) |
もう少し現実的な実装例を見てみましょう。次のコードはselect文をつかって、受信操作にタイムリミットを設けている例です。
select { |
time.After
は標準ライブラリの関数です。一定時間たった後に、現在時刻を送信するチャネルを返す関数です。
Hello server
これまでのピースがどのようにはまっていくのかを、ちょっとした例でお見せして終わることにしましょう。server
パッケージはチャネルを経由してWork
リクエストを受け入れるサーバーを実装しています。
- リクエストごとに別々の
goroutine
で処理が行われます。 Work
の構造体それ自身は、結果を返すために用いられるチャンネルを含んでいます。
package server |
こんな風に使えるでしょう。
package server_test |
さらに学びたい場合
Tutorialsは初心者にも上級者にもためになるサイトです。ベストプラクティスや、本番環境に匹敵するコード例が揃っています。
オススメの記事
Goに関連した入門記事です。
- Goを学ぶときにつまずきやすいポイントFAQ
- PythonistaがGo言語に入門してみた
- 1.後入れ先出し(LIFO: Last In First Out; FILO: First In Last Out)の構造 ↩
- 2.
{}
の中で宣言されていない変数・関数をトップレベルで宣言された変数・関数と読んでいる。import文はトップレベルで宣言されている。main packageのmain関数はトップレベルで宣言されている。 ↩ - 3.Javaの世界で呼ばれる「オブジェクト」と同義です。 ↩
- 4.Unicodeのコードポイントとは、全ての文字を4桁の16進数で一意に表現したコード体系の値です。 ↩
- 5.panicが発生して、呼び出し元の関数が強制的にreturnされても、defer宣言された関数は実行されます。 ↩
- 6.サンプルコード ( https://play.golang.org/p/XDaWkZqEZ9K ) ↩
- 7.untypedな状態の時は型が決まっておらず、式や代入の中でその定数が用いられる時、型が決定されます。 ↩
- 8.サンプルコード ( https://play.golang.org/p/J3JBKvSmYJW ) ↩
- 9.サンプルコード ( https://play.golang.org/p/jTKvVIBqwMa ) ↩