cv.cm

コーディング · プログラミング言語

Go言語入門チュートリアル(初心者向け)

約10分で読めます

結論

Go(Golangとも呼ばれます)は、Googleが開発したシンプルなコンパイル型の静的型付け言語で、高速なビルド、扱いやすい並行処理、信頼性の高いサーバーを目指して作られました。Goをインストールし、main パッケージを書いて go run hello.go で実行します。少数の型、スライスとマップ、メソッドを持つ構造体、暗黙的なインターフェース、並行処理のためのゴルーチンとチャネル、通常の値として返されるエラーが中心的な内容です。

JSONフォーマッタを開く

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, int64n := 420
float64x := 3.140
boolok := truefalse
strings := "hi"""
runer := 'é'0(Unicodeコードポイント)
byteb := 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スレッドに割り当てて実行するため、ゴルーチンの生成コストははるかに小さくなります。

コーディング講座の一覧