LoginSignup
51
25

More than 5 years have passed since last update.

golangにgoto文が存在する理由

Last updated at Posted at 2016-10-13

goto文

goto文といえばどこにでも飛べるという圧倒的自由度を誇る分、
文の流れを追いづらくなるといった可読性の問題等から基本的に使うべきでなく、
使った方が見やすい場合でも代替の方法があるならそちらを使うべきといったイメージがありました。
おそらく大半のプログラマもそんな感じだと思います。

しかしモダンでイケイケなことで定評のあるgolangにおいてもgoto文がサポートされています。
統一しただけとはいえ、while文すら捨てた新進気鋭のgolangが、
30未満という洗練された予約語の中にgoto文を残したわけです。
もちろん言語界隈はgo繋がりというだけで採用されるコネ社会ではありません。
goto文=悪というプロパガンダを刷り込まれた我々雛鳥世代が黙っているはずはありません。
ピーチクパーチク騒ぐに違いないことは歴史が証明しています。
Effective Go的なドキュメントにgoto文が残されている理由が明言されているのは必然ともいえます。

探してみました

ありませんでした。

代わり

公式なものはありませんでしたが
https://groups.google.com/forum/#!topic/golang-nuts/1WON0QAQzvQ
が参考になりました。

実際goto文でないとややこしくなる局面はあるようで
基本的にエラーハンドリング、特にシステム系やテストの部分で使うことが多いようです。
パフォーマンスとかにも効果あったりするんでしょうか。

こういう時、標準ライブラリがサンプル代わりというのはすごい良いものですね。
ライブラリで実際にgoto文を使っているところを挙げると

rand/rand.go
// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0).
func (r *Rand) Float32() float32 {
    // Same rationale as in Float64: we want to preserve the Go 1 value
    // stream except we want to fix it not to return 1.0
    // This only happens 1/2²⁴ of the time (plus the 1/2⁵³ of the time in Float64).
again:
    f := float32(r.Float64())
    if f == 1 {
        goto again // resample; this branch is taken O(very rarely)
    }
    return f
}
net/url.go
// parse parses a URL from a string in one of two contexts. If
// viaRequest is true, the URL is assumed to have arrived via an HTTP request,
// in which case only absolute URLs or path-absolute relative URLs are allowed.
// If viaRequest is false, all forms of relative URLs are allowed.
func parse(rawurl string, viaRequest bool) (url *URL, err error) {
    var rest string

    if rawurl == "" && viaRequest {
        err = errors.New("empty url")
        goto Error
    }
    url = new(URL)

    if rawurl == "*" {
        url.Path = "*"
        return
    }

    // Split off possible leading "http:", "mailto:", etc.
    // Cannot contain escaped characters.
    if url.Scheme, rest, err = getscheme(rawurl); err != nil {
        goto Error
    }
    url.Scheme = strings.ToLower(url.Scheme)

    if strings.HasSuffix(rest, "?") && strings.Count(rest, "?") == 1 {
        url.ForceQuery = true
        rest = rest[:len(rest)-1]
    } else {
        rest, url.RawQuery = split(rest, "?", true)
    }

    if !strings.HasPrefix(rest, "/") {
        if url.Scheme != "" {
            // We consider rootless paths per RFC 3986 as opaque.
            url.Opaque = rest
            return url, nil
        }
        if viaRequest {
            err = errors.New("invalid URI for request")
            goto Error
        }
    }

    if (url.Scheme != "" || !viaRequest && !strings.HasPrefix(rest, "///")) && strings.HasPrefix(rest, "//") {
        var authority string
        authority, rest = split(rest[2:], "/", false)
        url.User, url.Host, err = parseAuthority(authority)
        if err != nil {
            goto Error
        }
    }
    if url.Path, err = unescape(rest, encodePath); err != nil {
        goto Error
    }
    // RawPath is a hint as to the encoding of Path to use
    // in url.EscapedPath. If that method already gets the
    // right answer without RawPath, leave it empty.
    // This will help make sure that people don't rely on it in general.
    if url.EscapedPath() != rest && validEncodedPath(rest) {
        url.RawPath = rest
    }
    return url, nil

Error:
    return nil, &Error{"parse", rawurl, err}
}

などがありました。

上の乱数ライブラリの方をラベル使わずに書くとすれば

rand/rand.go
// Float32 returns, as a float32, a pseudo-random number in [0.0,1.0).
func (r *Rand) Float32() float32 {
    // Same rationale as in Float64: we want to preserve the Go 1 value
    // stream except we want to fix it not to return 1.0
    // This only happens 1/2²⁴ of the time (plus the 1/2⁵³ of the time in Float64).
    f := float32(1)//またはf := float32(r.Float64())
    for f == 1 {
        f = float32(r.Float64())
    }
    return f
}

こんな感じかと思いますがf:=float32(1)だと無駄な条件チェックとかもありますし、
f = float32(r.Float64())にすれば内容が被るので
こういったリトライする時はgoto文を使った方がいいのかもしれません。

下のURLの方も関数名である"parse"に引数であるrawurlと
どこも書くものは同じなので分かりやすくまとまっていると思います。

結論

無理に使う必要はないと思いますが
使い時はありそうです。

goto文自体元々目的があってできたわけで、ちゃんと使えば有用なのはもちろんそうですが
それだけではポインタだとかと、プログラマのスキルに頼ってしまうとキリがないので
goto文のような自由度のありすぎる構文は省かれるものだと思っていました。
現状はgoto文を使わない書き方が主流ですし、ポインタほど扱いづらい劇物でもないので、
goto文でなくては解決できない冗長さがあるならあってもいいのかなと思いました。
結局はっきりした理由は分かりませんでしたが
可読性を損なう書き方が出来ることよりも冗長さを排除出来なくなることの方が損失であると判断したのではないでしょうか。

51
25
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
51
25