今回、mruby-io-consoleを実装した時にmrb_protect
の使い方をおぼえたので、内容を共有します。
mrb_protect
はmruby標準添付ライブラリーmruby-error
にあるCの関数です。
mrb_protect
は、指定した関数内でエラーが起こった場合、エラーをraiseせずエラーオブジェクトを返して処理をアプリケーション側に委ねるものです。
ようするにC内でmrubyのensure
的なことがやりたい時に便利です。
実際にmrb_ensure
もあるのですが、ensure節でやりたいことが簡単なものであればmrb_protect
で十分だと思います。
build_config.rbでmruby-error
を使えるようにして(conf.gembox 'default'
に含まれてます)、
Cのソースで
#include "mruby/error.h"
とすれば使えます。
定義は以下。
mrb_value mrb_protect(
mrb_state *mrb, // おなじみのやつ
mrb_func_t body, // コールバック関数
mrb_value data, // コールバック関数の引数
mrb_bool *state // エラーが起こったらtrue、でなければfalse
);
実装例として、メソッドに与えられたブロックを実行して、エラーでもエラーじゃなくても特定の関数を実行する場合を書いてみます。
// mrb_protectで守りたい部分
static mrb_value
only_yield(mrb_state *mrb, mrb_value block)
{
// blockをyieldで実行する。
return mrb_yield_argv(mrb, block, 0, NULL);
}
// 絶対に実行させたい関数
static void
zettai_zikkou(void)
{
printf("YATTA!\n"); // 絶対に"YATTA!"と表示する
}
static mrb_value
honyahonya_method(mrb_state *mrb, mrb_value self)
{
mrb_value block;
mrb_value result;
mrb_bool state;
// blockを取得
mrb_get_args(mrb, "&", &block);
// resultにはエラーが起こらなかったらonly_yieldの返り値が、
// エラーが起こったらエラーオブジェクトが入る。
result = mrb_protect(mrb, only_yield, block, &state);
// mrb_protectのおかげで
// エラーが起きようが起きまいが絶対に実行したい関数が呼べる
zettai_zikkou();
if (state) { // trueならエラー
mrb_exc_raise(mrb, result); // ブロックで起こったエラーをそのまま発生させる
}
return result; // エラーじゃなかったらonly_yieldの返り値(ブロックの実行結果)を返す
}
こうしてmrb_protect
を使うことによって、mruby-io-consoleでもrawモード時にエラーが起こっても、rawモードのまま終了することなく、cookedモードに戻すといったことが実現できました。
リンク
rawモードとcookedモードについて: http://qiita.com/ksss/items/9c8a237f452cd8aa7c11
mruby-errorのソース: https://github.com/mruby/mruby/blob/master/mrbgems/mruby-error/src/exception.c
mruby-io-consoleのソース: https://github.com/ksss/mruby-io-console/blob/f7baf75361f37bdcf4667da24bb98a5006b45c1c/src/io-console.c#L68
mrb_protect導入PR: https://github.com/mruby/mruby/pull/2845