この資料は
error/1
, throw/1
, exit/1
の使い分けについて.
gg っても日本語のいいまとめが出てこないので,じゃあ書こうじゃないかと.
正確にまとめられてるかわからないですけど.
とりあえず catch してみましょうぞ
error/1
, throw/1
, exit/1
をそれぞれ catch
した.
1> catch error(hoge).
{'EXIT',{hoge,[{erl_eval,do_apply,6,
[{file,"erl_eval.erl"},{line,673}]},
{erl_eval,expr,5,[{file,"erl_eval.erl"},{line,431}]},
{shell,exprs,7,[{file,"shell.erl"},{line,686}]},
{shell,eval_exprs,7,[{file,"shell.erl"},{line,641}]},
{shell,eval_loop,3,[{file,"shell.erl"},{line,626}]}]}}
2> catch throw(hoge).
hoge
3> catch exit(hoge).
{'EXIT',hoge}
これから推測されることは以下.
-
error/1
とexit/1
はプロセスを殺す力を持っている(DOWN
)が,throw/1
は持っていない. -
throw/1
は大域脱出でしかない.プロセスを殺す力を持っておらず,投げられた例外は同一プロセス内でなんとかしなければならない. -
exit/1
ではスタックトレースはつかないのは,exit/1
が正常系のプロセス死にも利用されるから.
使い分け
catch
した結果をふまえると, この 3 つの関数の使い分けは以下の感じでいいだろう.
exit/1
文字通りプロセスを終了させたい場合に用いる.
単に役目が終わったプロセスを明示的に殺すだけなので,エラーではないのだね.
Eshell V7.0 (abort with ^G)
1> spawn(fun() -> exit(hogehoge) end).
<0.35.0>
2>
monitor すればプロセスが死んだ Reason がとれる.
Eshell V7.0 (abort with ^G)
1> spawn_monitor(fun() -> exit(hogehoge) end).
{<0.35.0>,#Ref<0.0.4.29>}
2> flush().
Shell got {'DOWN',#Ref<0.0.4.29>,process,<0.35.0>,hogehoge}
ok
throw/1
なにかエラーが発生したわけではない.ネストした関数呼び出しから一気に脱出 (大域脱出)するために用いる.
プロセス内でハンドリングされることを想定しているため,その try ... catch
がないとエラーとなりプロセスが死ぬ.
Eshell V7.0 (abort with ^G)
1> spawn(fun() -> throw(hogehoge) end).
<0.35.0>
=ERROR REPORT==== 24-Oct-2016::18:58:27 ===
Error in process <0.35.0> with exit value:
{{nocatch,hogehoge},[{erlang,apply,2,[]}]}
monitor してみると,ブロセスが死んだ直接的な原因は, hogehoge
が throw されたからではなくて, throw された hogehoge
が catch されなかったから,ということになっている.
1> spawn_monitor(fun() -> throw(hogehoge) end).
=ERROR REPORT==== 24-Oct-2016::18:59:41 ===
Error in process <0.37.0> with exit value:
{{nocatch,hogehoge},[{erlang,apply,2,[]}]}
{<0.37.0>,#Ref<0.0.4.34>}
2> flush().
Shell got {'DOWN',#Ref<0.0.4.34>,process,<0.37.0>,
{{nocatch,hogehoge},[{erlang,apply,2,[]}]}}
つまりプロセスを殺すことが目的だったら, throw/1
は不適切.
error/1
何か問題が発生した. badmatch
とか case_clause
は Erlang VM が吐いてくれるエラーで,それらと同じレベルで,なにかユーザ定義の「問題」が発生したよ,というのが error.
プロセスは続行できなくなり死ぬ.
4> spawn(fun() -> error(hogehoge) end).
=ERROR REPORT==== 24-Oct-2016::19:04:01 ===
Error in process <0.40.0> with exit value:
{hogehoge,[{erlang,apply,2,[]}]}
<0.40.0>
monitor してみると,見た目は throw と一緒. throw でプロセスが死ぬのは, nocatch
エラーだから.
ただし, error/1
の引数で指定した Reason (hogehoge
) がそのままエラーの原因になっている (nocatch はつかない).
5> spawn_monitor(fun() -> error(hogehoge) end).
{<0.42.0>,#Ref<0.0.4.51>}
=ERROR REPORT==== 24-Oct-2016::19:04:14 ===
Error in process <0.42.0> with exit value:
{hogehoge,[{erlang,apply,2,[]}]}
6> flush().
Shell got {'DOWN',#Ref<0.0.4.51>,process,<0.42.0>,
{hogehoge,[{erlang,apply,2,[]}]}}
まとめ
プロセスが死ぬ原因は 2 つ.
-
exit/1
により明示的に殺した(死んだ) - エラーが発生し,実行が続行できなくなった
それぞれをユーザが明示的にコーディングする方法(として適切なの)は,それぞれ以下.
-
exit/1
で明示的に殺す. -
error/1
で明示的にエラーを発生させる.
つまり, error/1, throw/1, exit/1 の使い分けは,簡単に書けば以下.
-
error/1
: プロセスの実行が続行できなくなるようなエラーが発生した -
exit/1
: プロセスは役割を果たしたので死ぬ (もしくは死ぬことで役割を果たす) -
throw/1
: 大域脱出