Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

Go関連の比較的新しいTips

READMEにpkg.go.devのバッジを貼る

godoc.orgはpkg.go.devに移行していくことが告知されているので、新しいプロジェクトではREADME.mdに貼っているバッジを移行しましょう。pkg.go.devのURLやバッジは

// バッジ
https://pkg.go.dev/badge/<package path>

// リンク
https://pkg.go.dev/<package path>

の形を取ります。例えばgithub.com/lufia/backoffの場合は以下のように書きます。

# Backoff
...summary...

[![GoDev][godev-image]][godev-url]

...description...


[godev-image]: https://pkg.go.dev/badge/github.com/lufia/backoff
[godev-url]: https://pkg.go.dev/github.com/lufia/backoff

pkg.go.devのバージョンを更新する

GitHubなどで新しいタグをpushしても、何もしなければ(少なくとも数日は)モジュールインデックスに反映されません。すぐに更新したい場合、最新バージョンを明記してgo getしておきましょう。

// アクセスすればいいので-dオプションをつけてもいい
% go get github.com/lufia/backoff@v1.3.0

これで数時間後には反映されるはずです。モジュールの動作は以下の記事がとても詳しいので読んでおくとよくわかります。

Goバイナリのランタイムバージョンを調べる

runtime.Versionを使うと、Goでビルドされたコマンド自身は、どのバージョンでビルドされたのかを実行時に調べることができますが、ファイル名を指定して調べる方法は(少なくとも簡単に調べる方法は)提供されていませんでした。

// 自身のランタイムバージョンしか取れない
fmt.Println("Version:", runtime.Version())

Go 1.13から、go versionコマンドに-mオプションが追加されました。このコマンドに実行ファイルを渡すと、どのバージョンでビルドされたのかを調べられるようになりました。モジュールが使われている場合は、モジュールのバージョンも調べられます。また、ディレクトリを渡した場合は、ディレクトリに含まれるたGoバイナリ全てを調べます。

% go version -m ~/bin/act
/Users/lufia/bin/act: go1.14.6
    path    github.com/nektos/act
    mod github.com/nektos/act   v0.2.10   h1:aMSXUGybVyLIqe3ak9GyCtRVpBxwAiSBR5stqas0lj0=
    dep github.com/MichaelTJones/walk   v0.0.0-20161122175330-4748e29d5718 h1:FSsoaa1q4jAaeiAUxf9H0PgFP7eA/UL6c3PdJH+nMN4=
    dep github.com/andreaskoch/go-fswatch   v1.0.0    h1:la8nP/HiaFCxP2IM6NZNUCoxgLWuyNFgH0RligBbnJU=
    dep github.com/containerd/containerd    v1.3.3    h1:LoIzb5y9x5l8VKAlyrbusNPXqBY0+kviRloxFUMFwKc=
    dep github.com/containerd/continuity    v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
    dep github.com/docker/cli   v0.0.0-20190822175708-578ab52ece34 h1:H/dVI9lW9zuagcDsmBz2cj8E8paBX5FarjO7oQCpbVA=
    dep github.com/docker/distribution  v2.7.1+incompatible   h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
    dep github.com/docker/docker    v0.0.0-20200229013735-71373c6105e3 h1:hq9QaRK9JJOg7GItpuSSA3MrBoEN3c3llxQappEq9Zo=
...

1.13より前のバージョンでビルドしたコマンドは、ランタイムのバージョンだけならgoversionで調べられます。こちらもgo versionと同様に、ディレクトリを渡せます。

% go get github.com/rsc/goversion
% goversion ~/bin

チャネルのスライスでselectする

動的に増減する複数のチャネルを使って、どれでもいいので送信可能になったチャネルへデータを送る、または受信可能なデータを取り出す動作を実装したい場合にreflect.Selectが使えます。以下の例は受信しか行っていませんが、だいたいの使い方はこんな雰囲気。

package main

import (
    "fmt"
    "math/rand"
    "reflect"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    a := make([]chan int, 5)
    for i := 0; i < len(a); i++ {
        a[i] = make(chan int)
    }
    cases := make([]reflect.SelectCase, len(a))
    for i, c := range a {
        cases[i] = reflect.SelectCase{
            Dir:  reflect.SelectRecv,
            Chan: reflect.ValueOf(c),
        }
    }

    const N = 10
    go func() {
        for i := 0; i < N; i++ {
            off := rand.Int() % len(a)
            a[off] <- i
        }
    }()
    for i := 0; i < N; i++ {
        // casesに含まれるチャネルのどれかにデータが届いたらSelectを抜ける
        off, v, ok := reflect.Select(cases)
        fmt.Println(off, v, ok)
    }
}

ところで、Goの前身となる言語にはこういった機能が言語仕様に盛り込まれていたのですが、Goでは意図的に外されたようです。なので回避できるなら使わない方が良いのかもしれません。代わりに、例えばchan chan intのようにチャネルを送受信するチャネルを使うと、同じようなことはできます。

runtime.GOOSとbuild constraintsのどっちを使う

golang-nutsより。

all.bashのタイムアウト

Goをソースからコンパイルするときテストを実行しますが、このテストは10分弱でタイムアウトします。マシン性能が悪くタイムアウトしてしまう場合、環境変数GO_TEST_TIMEOUT_SCALEに2を設定すると、タイムアウトが標準の2倍になります。Plan 9でのテストは一部とても遅いものがあるので、この設定が必要です。

presentスライド

Go関連の発表でよく使われる、テキストを書くとスライドにしてくれるpresentというツール*1があるのですが、以前は独特な記法で書く必要がありました。最近この記法が、Markdownに似たものに変更されたようです。

とはいえ、今も古い記法に対応していて、どっちの記法を使うのかは、テキスト中に#(スペースを含む)が1つでもあればMarkdown風の記法が使われます。

ただし、talks.godoc.orgはまだ新しい記法に対応していません。