コーディング · プログラミング言語
Go言語入門チュートリアル(初心者向け)
結論
Go(Golangとも呼ばれます)は、Googleが開発したシンプルなコンパイル型の静的型付け言語で、高速なビルド、扱いやすい並行処理、信頼性の高いサーバーを目指して作られました。Goをインストールし、main パッケージを書いて go run hello.go で実行します。少数の型、スライスとマップ、メソッドを持つ構造体、暗黙的なインターフェース、並行処理のためのゴルーチンとチャネル、通常の値として返されるエラーが中心的な内容です。
Goとは何か、何に使われるか
Goは、GoogleでRobert Griesemer、Rob Pike、Ken Thompsonが設計し、2009年にオープンソースとして公開されました。単一の静的バイナリにコンパイルされ、起動が速く、ガベージコレクタとネットワーク向けの充実した標準ライブラリを備えています。クラウドインフラやコマンドラインツールで使われ、Docker、Kubernetes、Terraform、PrometheusはGoで書かれています。多くの企業がWeb APIやマイクロサービスにも採用しています。言語が意図的に小さく作られているため、チームが違ってもコードが似た見た目になります。
Goのインストールと Hello, world の実行
go.dev/dlからGoをダウンロードし、go version で確認します。フォルダを作ってモジュールを開始し、main.go を追加します。
package main
import "fmt"
func main() {
fmt.Println("Hello, world")
}go mod init example.com/hello
go run .
# Hello, world
go build # produces a standalone binary named hello実行は package main の func main から始まります。コードの整形には gofmt(または go fmt)を実行します。Goのコードはすべて同じスタイルで、実際のソースファイルではインデントにタブを使います。
変数と基本の型
変数は var name type = value で宣言するか、関数内では型を推論する短縮形 name := value で宣言します。代入されていない変数にはゼロ値(0、""、false、nil)が入ります。
| 型 | 例 | ゼロ値 |
|---|---|---|
int, int64 | n := 42 | 0 |
float64 | x := 3.14 | 0 |
bool | ok := true | false |
string | s := "hi" | "" |
rune | r := 'é' | 0(Unicodeコードポイント) |
byte | b := byte(65) | 0(uint8 の別名) |
package main
import (
"fmt"
"strconv"
)
func main() {
name := "Ada"
count := 7
avg := float64(count) / 2 // explicit conversion is required
fmt.Printf("%s has %d items, avg %.1f\n", name, count, avg) // Ada has 7 items, avg 3.5
n, err := strconv.Atoi("12")
fmt.Println(n+1, err) // 13 <nil>
}Goは数値型どうしを暗黙的に変換しません。また、使っていない変数や使っていないimportはコンパイラがエラーにします。
制御構文と関数
条件式を括弧で囲む必要はなく、波括弧は必須です。for は唯一のループキーワードで、従来型、while型、range型のループをすべて表せます。関数は複数の値を返せます。
package main
import "fmt"
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func main() {
score := 72
if score >= 90 {
fmt.Println("A")
} else if score >= 70 {
fmt.Println("B") // B
} else {
fmt.Println("C")
}
for i := 0; i < 3; i++ {
fmt.Print(i, " ") // 0 1 2
}
fmt.Println()
n := 10
for n > 1 { // while-style loop
n /= 2
}
q, r := divmod(17, 5)
fmt.Println(n, q, r) // 1 3 2
switch day := "SAT"; day {
case "SAT", "SUN":
fmt.Println("weekend") // no break needed
default:
fmt.Println("weekday")
}
}スライス、マップ、構造体
スライスは配列を伸縮できる形で参照するもので、日常的に使うリスト型です。マップはハッシュテーブルです。構造体は名前付きのフィールドをまとめ、メソッドはレシーバを持つ関数です。
package main
import (
"fmt"
"sort"
)
type Point struct {
X, Y int
}
func (p Point) Add(o Point) Point { return Point{p.X + o.X, p.Y + o.Y} }
func (p *Point) Scale(k int) { p.X *= k; p.Y *= k } // pointer receiver modifies p
func main() {
nums := []int{3, 1, 2}
nums = append(nums, 5)
sort.Ints(nums)
fmt.Println(nums, len(nums), nums[1:3]) // [1 2 3 5] 4 [2 3]
ages := map[string]int{"ann": 31, "bob": 25}
ages["cy"] = 40
age, ok := ages["zed"]
fmt.Println(age, ok, len(ages)) // 0 false 3
for i, n := range nums {
fmt.Print(i, ":", n, " ") // 0:1 1:2 2:3 3:5
}
fmt.Println()
p := Point{1, 2}.Add(Point{3, 4})
p.Scale(2)
fmt.Printf("%+v\n", p) // {X:8 Y:12}
}マップの反復順序は意図的にランダムです。順序を固定したい場合はキーをソートしてください。大文字で始まる名前(Point、Add)は、パッケージの外に公開されます。
インターフェース
インターフェースはメソッドのシグネチャを列挙します。型がそれらのメソッドを持っていれば、そのインターフェースを満たします。implements キーワードはありません。
package main
import (
"fmt"
"math"
)
type Shape interface {
Area() float64
}
type Circle struct{ R float64 }
type Rect struct{ W, H float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (r Rect) Area() float64 { return r.W * r.H }
func main() {
shapes := []Shape{Circle{1}, Rect{2, 3}}
total := 0.0
for _, s := range shapes {
total += s.Area()
}
fmt.Printf("%.2f\n", total) // 9.14
}Goにはジェネリクス(Go 1.18以降)もあり、たとえば func Max[T int | float64](a, b T) T のように書けます。主に slices や maps パッケージなどのライブラリ関数を通じて使うことになります。
特徴的な機能:ゴルーチンとチャネル
ゴルーチンは go キーワードで開始する軽量なスレッドで、1つのプログラムで何千も実行できます。ゴルーチンどうしはチャネルを通じて通信し、チャネルは値を安全に受け渡します。sync.WaitGroup は、ゴルーチンのグループが終わるのを待ちます。
package main
import (
"fmt"
"sync"
)
func square(n int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
results <- n * n
}
func main() {
results := make(chan int, 5)
var wg sync.WaitGroup
for i := 1; i <= 5; i++ {
wg.Add(1)
go square(i, results, &wg)
}
wg.Wait()
close(results)
sum := 0
for r := range results {
sum += r
}
fmt.Println(sum) // 55
}defer は、囲んでいる関数が戻るときに実行する呼び出しを予約します。Goのコードがファイルを閉じたりミューテックスをアンロックしたりする方法です。開発中は go run -race . を使うと、データ競合を検出できます。
エラー処理
Goには、通常の失敗に対する例外がありません。失敗しうる関数は最後の戻り値として error を返し、呼び出し側がすぐに確認します。エラーを %w でラップすると、元のエラーを errors.Is で調べられるまま、文脈を追加できます。
package main
import (
"errors"
"fmt"
"os"
)
func readConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
return data, nil
}
func main() {
_, err := readConfig("missing.json")
if errors.Is(err, os.ErrNotExist) {
fmt.Println("no config file:", err)
}
}panic と recover もありますが、プログラミングのミスや本当に想定外の状態のためのもので、通常のエラー処理には使いません。
小さな完成プログラム:小さなWebサーバー
標準ライブラリだけで、このプログラムはHTTPでJSONを返します。go run . で実行し、http://localhost:8080/hello?name=Ann を開きます。
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type Greeting struct {
Message string `json:"message"`
Time time.Time `json:"time"`
}
func hello(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "world"
}
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(Greeting{"Hello, " + name, time.Now()})
if err != nil {
log.Println("encode:", err)
}
}
func main() {
http.HandleFunc("/hello", hello)
log.Println("listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}各フィールドの型の後ろにある文字列は構造体タグで、JSONのキー名を指定します。各リクエストは、自動的にそれぞれのゴルーチンで処理されます。
初心者がよくやる間違い
_でエラーを無視する。問題にならないと確信できる場合を除き、返されたerrorはすべて確認しましょう。appendが元のスライスをその場で変更すると思い込む。s = append(s, x)のように、必ず戻り値を使います。- ゴルーチンを開始したあと、それらが終わる前に
mainが戻ってしまう。sync.WaitGroupやチャネルで待ちます。 - メソッドが構造体を変更する必要があるのに、値レシーバを使う。
func (p *Point)のようなポインタレシーバを使います。
次のステップ
インタラクティブなTour of Goを進め、その後Effective Goとpkg.go.devの標準ライブラリのドキュメントを読みましょう。単体テストとベンチマークのための go test、タイムアウトとキャンセルのための context パッケージ、依存関係を管理するモジュールも学びます。
よくある質問
GoとGolangのどちらが正式名称ですか?
正式名称はGoです。「Golang」は、以前のサイトのアドレス golang.org に由来し、今でも検索キーワードとして使われています。
Goはオブジェクト指向ですか?
部分的にそうです。Goにはメソッドとインターフェースがありますが、クラスや継承はありません。構造体の埋め込みと小さなインターフェースの組み合わせで型を作ります。
GoはPythonより速いですか?
CPUに負荷がかかる処理の大半では、はい。Goはネイティブの機械語にコンパイルされ、静的型付けだからです。ただし、重い処理がCで書かれたライブラリの内部で行われる場合は、Pythonでも高速に動作します。
ゴルーチンとスレッドの違いは何ですか?
ゴルーチンはGoのランタイムが管理し、小さなスタックから始まって必要に応じて拡張されます。ランタイムが多数のゴルーチンを少数のOSスレッドに割り当てて実行するため、ゴルーチンの生成コストははるかに小さくなります。