はじめてのGo―シンプルな言語仕様,型システム,並行処理

第2章 基本文法―覚えやすいコンパクトな言語仕様

この記事を読むのに必要な時間:およそ 9.5 分

switch

if/else文が繰り返す場合は,switch文を用いたほうがスッキリ書ける場合があります。Goのswitch文は非常に柔軟であり,値の比較だけでなく条件分岐にも使用できます。

値での分岐

まず,値を用いたswitch文は次のようになります。

func main() {
    n := 10
    switch n {
    case 15:
        fmt.Println("FizzBuzz")
    case 5, 10:
        fmt.Println("Buzz")
    case 3, 6, 9:
        fmt.Println("Fizz")
    default:
        fmt.Println(n)
    }
}

switch 文に指定した値に一致するcaseが実行され,どのcaseにも一致しなかった場合はdefaultが実行されます。caseには単一の値だけでなく,カンマで区切った複数の値も指定できます。

fallthrough

CやJavaなどのswitch文は,1つのcaseが実行されるとその次のcaseに処理が移るため,単一のcaseの実行で終わらせたい場合に,caseごとにbreakを書く必要がありました。しかしGoのswitch文では,caseが1つ実行されると次のcaseに実行が移ることなくswitch文が終了するため,breakをいちいち書く必要はありません。

ただ,caseの処理が終わったあとに,次のcaseに処理を進めたい場合もあります。そうした場合はcase内にfallthroughを書くことで,明示的に次のcaseに処理を進めることができます。

func main() {
    n := 3
    switch n {
    case 3:
        n = n - 1
        fallthrough
    case 2:
        n = n - 1
        fallthrough
    case 1:
        n = n - 1
        fmt.Println(n) // 0
    }
}

式での分岐

Goのswitch文では,caseに値だけでなく式も指定できます。

func main() {
    n := 10
    switch {
    case n%15 == 0:
        fmt.Println("FizzBuzz")
    case n%5 == 0:
        fmt.Println("Buzz")
    case n%3 == 0:
        fmt.Println("Fizz")
    default:
        fmt.Println(n)
    }
}

たとえば上記のようにcaseに式を指定すれば,評価結果がtrueになるcaseが実行でき,if/else 文の代わりに使用できます。

また,値,式以外にtype(型)を用いたswitch文もありますが,これについては3章で解説します。

関数

関数はfuncで始まります。引数も戻り値もない場合は次のように宣言します。

func hello() {
    fmt.Println("hello")
}

func main() {
    hello() // hello
}

引数がある場合は変数と型を指定します。複数の同じ型が続く場合は,型の宣言は最後の1つにまとめることができます。

func sum(i, j int) { // func sum(i int, j int) と同じ
    fmt.Println(i + j)
}

func main() {
    sum(1, 2) // 3
}

戻り値がある場合は引数の次に指定します。

func sum(i, j int) int {
    return i + j
}

func main() {
    n := sum(1, 2)
    fmt.Println(n) // 3
}

関数は複数の値を返せる

Goの大きな特徴の一つとして,関数は複数の値を返すことができます。戻り値が複数の場合は,型をカンマで区切って指定し丸括弧でくくります。returnはそれに対応した型の値を,同じくカンマで区切って返します。

func swap(i, j int) (int, int) {
    return j, i
}

func main() {
    x, y := 3, 4
    x, y = swap(x, y)
    fmt.Println(x, y) // 4, 3

    x = swap(x, y) // コンパイルエラー

    x, _ = swap(x, y) // 第二戻り値を無視
    fmt.Println(x) // 3

    swap(x, y) // コンパイル,実行ともに可能
}

関数の実行時には,戻り値を格納する変数を必要な数だけ用意する必要があります。関数が返す値の数と,受け取る変数の数が合わないとコンパイルエラーになります。ただし,無視したい戻り値がある場合は_で明示的に無視することで,戻り値を受け取らなくてもコンパイル,実行ともに可能です。

エラーを返す関数

Goでは,関数が多値を返せることを利用して,内部で発生したエラーを戻り値で表現します。関数の処理に成功した場合はエラーはnilにし,異常があった場合はエラーだけに値が入り,他方はゼロ値になります。

たとえばファイルを開くos.Open()は,1つ目の戻り値に*os.File2つ目にerrorを返します。

func main() {
    file, err := os.Open("hello.go")
    if err != nil {
        // エラー処理
        // returnなどで処理を別の場所に抜ける
    }
    // fileを用いた処理
}

自作のエラーは,errorsパッケージを用いて作ることができます。

package main

import (
    "errors"
    "fmt"
    "log"
)

func div(i, j int) (int, error) {
    if j == 0 {
        // 自作のエラーを返す
        return 0, errors.New("divied by zero")
    }
    return i / j, nil
}

func main() {
    n, err := div(10, 0)
    if err != nil {
        // エラーを出力しプログラムを終了する
        log.Fatal(err)
    }
    fmt.Println(n)
}

複数の値を返す場合もエラーを最後にする慣習があるため,自分でAPIを設計する場合もエラーを最後にするほうがよいでしょう。

異常を戻り値で表現できない場合については,後述のパニックとリカバで解説します。

名前付き戻り値

Goでは,戻り値にあらかじめ名前を付けることができます。先ほどの関数の戻り値に,次のように名前を付けてみます。

func div(i, j int) (result int, err error)

名前付き戻り値は,関数内ではゼロ値で初期化された変数として扱うことができます。また,変数に名前を付けている場合は,returnのあとに返す値を明示する必要がなく,returnされた時点での名前付き戻り値の値が自動的に返されることになります。

これを用いると,先の関数は次のように書くことができます。

func div(i, j int) (result int, err error) {
    if j == 0 {
        err = errors.New("divied by zero")
        return // return 0, errと同じ
    }
    result = i / j
    return // return result, nilと同じ
}

名前付き戻り値を用いることで,関数の宣言から戻り値の意味が読み取りやすくなると同時に,戻り値のための変数の初期化が不要になり,同じ型の戻り値が多かった場合のreturnの書き間違えなどを防ぐこともできます。

ただし,戻り値に名前を付けても,returnのあとに戻す値を明示することは可能です。プログラムのわかりやすさを重視して使い分けるとよいでしょう。

関数リテラル

関数リテラルを用いると,無名関数を作ることができます。即時に実行する関数は次のように記述できます。

func main() {
    func(i, j int) {
        fmt.Println(i + j)
    }(2, 4)
}

Goにおける関数は第一級オブジェクトであるため,関数を変数に代入したり関数の引数に渡すことができます。たとえば,上記の関数を代入する変数の型は次のようになります。

var sum func(i, j int) = func(i, j int) {
    fmt.Println(i + j)
}

func main() {
    sum(2, 4)
}

著者プロフィール

コメント

コメントの記入