2015/12/12


golang の defer は後処理のキューの登録です。コードを見ていないので分かりませんが、おそらくこういうコードを書いたのだと推測します。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        f := Create(i)
        defer Delete(f)
    }
    fmt.Println("終了")
}

この処理、実際には作成者の意図に反して以下の様に動作します。

開始
1 を作成
2 を作成
3 を作成
4 を作成
5 を作成
6 を作成
7 を作成
8 を作成
9 を作成
10 を作成
終了
10 を削除
9 を削除
8 を削除
7 を削除
6 を削除
5 を削除
4 を削除
3 を削除
2 を削除
1 を削除

つまり後処理を LIFO に登録し、関数スコープを抜けたタイミングで最後に登録したキューから取り出して実行します。ですので大量のループを実行するとキューが爆発します。さらに defer はその瞬間の変数をキャプチャします。

package main

import (
    "os"
)

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer f.Close() // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer f.Close() // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer f.Close() // test3.dat の Close()
}

func main() {
    doSomething()
}

つまり test1.dat の f も test2.dat の f も test3.dat の f もキューに乗っかります。それを確認する為にこのコードを以下の様に修正します。

package main

import (
    "fmt"
    "os"
)

func closeFile(f *os.File) {
    fmt.Printf("%v を Close() します\n", f.Name())
    f.Close()
}

func doSomething() {
    f, err := os.Open("test1.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test1.dat の Close()

    f, err = os.Open("test2.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test2.dat の Close()

    f, err = os.Open("test3.dat")
    if err != nil {
        return
    }
    defer closeFile(f) // test3.dat の Close()
}

func main() {
    doSomething()
}

実行すると以下の様に出力されます(ファイルは存在しているものとします)。

test3.dat を Close() します
test2.dat を Close() します
test1.dat を Close() します

ですのでループの中で defer を使ってはいけません。ただしループの中で処理するステートメントが多く、defer を使って簡素化したい場合は、以下の様に関数スコープを作ってあげる必要があります。

package main

import (
    "fmt"
)

type foo struct {
    n int
}

func Create(n int) *foo {
    fmt.Printf("%v を作成\n", n)
    return &foo{n}
}

func Delete(f *foo) {
    fmt.Printf("%v を削除\n", f.n)
}

func main() {
    fmt.Println("開始")
    for i := 1; i <= 10; i++ {
        func() {
            f := Create(i)
            defer Delete(f)
            // ... 色んな処理
        }()
    }
    fmt.Println("終了")
}

もちろんこの場合、途中で return しても大域脱出にならないので注意が必要です。


2015/12/01


この記事は Go Advent Calendar 2015 の記事ではありません。

ccat は cat やソースに色が付けられて非常に便利なので結構使っていた。

ccat

ふと「これ、ブログ書く時なんかに HTML として出力される機能があたら便利じゃね?」と思ったので ccat に html 出力機能を書いて pull-request を送った。

Htmlize by mattn - Pull Request #35 - jingweno/ccat - GitHub
https://github.com/jingweno/ccat/pull/35

マージされ、新しいバージョンがリリースされたので試したい人はダウンロードするか自分でビルドするといいと思います。ちなみにこんなソースが出力されます。


2015/11/27


注意: まだ Vim Advent Calendar 2015 は始まっていません。

ctrlp.vimのエクステンションの作り方 - koturnの日記

はじめに この記事は ctrlpvim/ctrlp.vim のエクステンションの作り方についての記事であり, ctrlpの公式リポジトリのextensionsブランチ に書かれているエクステンションの...

http://koturn.hatenablog.com/entry/2015/11/19/200000

とても良い記事ですね。みんな CtrlP 拡張書いたらいいと思います。そして Vimmer おじさんから1点だけ。

let s:sample_var = {
      \ 'init': s:sid_prefix . 'init()',
      \ 'accept': s:sid_prefix . 'accept',
      \ 'lname''sample extension',
      \ 'sname''sample',
      \ 'type''path',
      \ 'nolim'1
      \}
if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
  call add(g:ctrlp_ext_vars, s:sample_var)
else
  let g:ctrlp_ext_vars = [s:sample_var]
endif
unlet s:sample_var

こういう時の if 文、出来れば1行で書きたくないですか。そういう時に使えるのが get です。get は第一引数(辞書もしくは配列)から第二引数のキーで値を取得し、もしキーが存在しなければ第三引数を返すという関数です。また Vim script では g: (: の後何も付けない)という辞書変数でグローバル変数が参照出来ます。s: でスクリプトスコープ、b: でバッファスコープです。ここテストに出ます。なので

if exists('g:ctrlp_ext_vars') && !empty(g:ctrlp_ext_vars)
  call add(g:ctrlp_ext_vars, s:sample_var)
else
  let g:ctrlp_ext_vars = [s:sample_var]
endif

これは

let g:ctrlp_ext_vars = get(g:, 'ctrlp_ext_vars', []) + [s:sample_var]

と書くことができ、さらには

let g:ctrlp_ext_vars = get(g:, 'ctrlp_ext_vars', []) + [{
      \ 'init': s:sid_prefix . 'init()',
      \ 'accept': s:sid_prefix . 'accept',
      \ 'lname''sample extension',
      \ 'sname''sample',
      \ 'type''path',
      \ 'nolim'1
\}]

とすれば sample_var の代入と破棄も必要なくなります。この技、GitHub で pull-request を送ると上級者 Vimmer に見られるので要チェックです。

えっ?見られたくない?そうですか。