ruby の Exception クラスでは、コンストラクタにメッセージを渡すことができる。

e = Exception.new('foo')
puts e.to_s # foo
puts e.message # foo

このメッセージを #instance_variable_get で取得しようとしたのだが取れない。#instance_variables で空配列が返って来る.

e.instance_variables #=> []

C の実装を見てみると、どうやらこれは、rb_iv_set の使い方によるもののようだ。

[1] pry(main)> show-source StandardError#initialize

From: error.c (C Method):
Owner: Exception
Visibility: private
Number of lines: 11

static VALUE
exc_initialize(int argc, VALUE *argv, VALUE exc)
{
    VALUE arg;

    rb_scan_args(argc, argv, "01", &arg);
    rb_iv_set(exc, "mesg", arg);
    rb_iv_set(exc, "bt", Qnil);

    return exc;
}

rb_iv_set(exc, "@mesg", arg); ではなく、rb_iv_set(exc, "mesg", arg); になっている。@がないと Ruby からは触れない。残念。

ちなみになぜ、エラーメッセージに触りたかったのかというと、子クラスで Exception#to_s メソッドをオーバーライドしたかったからである。

[3] pry(main)> show-source StandardError#to_s

From: error.c (C Method):
Owner: Exception
Visibility: public
Number of lines: 8

static VALUE
exc_to_s(VALUE exc)
{
    VALUE mesg = rb_attr_get(exc, rb_intern("mesg"));

    if (NIL_P(mesg)) return rb_class_name(CLASS_OF(exc));
    return rb_String(mesg);
}

これを少しカスタマイズした以下のような関数を定義したかったのだが、無理でした.という話。Berkshelf に投げたこの pull request に関連する。

def to_s
  if @message
    @message
  else
    "Awesome Error Message"
  end
end

FYI: なお、CRuby のコードは pry-doc gem を入れて追った。