Rubyにおける良いスタイルとして、ブロック付きメソッドでリソースを自動管理するという手法がある。
この安全性を破壊してみよう。
前提知識
まず、確認として次を見てみよう。
File.open("foo.txt") {|f|
f.read(3)
}
1行目で開いたFile
オブジェクトf
は3行目で自動的にclose
される。ブロック中の制御フローが多少複雑でも、例外が発生しても確実に3行目でclose
される。
最近では他の様々なメジャーな言語が独特の構文で似た機能を提供しているから、10年前とは異なり特にRubyの目立つ機能というわけでもなくなったが、とにかくブロックによるリソース管理は良いものだ。
しかし、安全は不自由でもある。ブロック終了時点で確実にファイルを閉じてしまうため、File.open
を呼んだメソッドが終了したあとでf.read
を呼ぶことはできない。
def open_file(name)
File.open(name) {|f| return f }
end
f = open_file("foo.txt")
f.read(3) # IOError: closed stream
破壊への道のり
時として、人はそれでも自由を必要とする。そんな時はちょっとしたトリックで自動リソース管理を破壊しよう。
def open_file(name)
iter = File.enum_for(:open, name)
return iter.next, ->{ loop { iter.next } }
end
f, close = open_file("foo.txt")
f.read(3)
close.call # ちゃんとファイルを閉じるのは自己責任
これで安全性を取っ払って自由にリソースリークできる。
10000.times.map do
f, _ = open_file("foo.txt") # Errno::EMFILE: Too many open files @ rb_sysopen - foo.txt
[f, f.read(3)]
end
終わりに
実のところ、File.open
の場合なら単にブロック付きでないバージョンを呼べば良い。しかし、場合によってはそういう安全でないAPIは提供されていないこともある。また、安全でないAPIはコーディング規約で利用が禁止されているかもしれない。
そういうやむを得ない場合に、それでもどうしてもリソースをリークさせる必要があるときに本稿が役に立てば幸いである。