String#[]

Ruby 初心者スレッド Part 19
http://pc11.2ch.net/test/read.cgi/tech/1208100393/
より。

496 名前:デフォルトの名無しさん[] 投稿日:2008/05/11(日) 11:38:08
str0 = "bar"
の時、マニュアルによると
p str0[3, 1] #=> nil
らしいですが、自分の環境(1.8.6 mswin32)では""が返ってきます
何が悪いのでしょうか

497 名前:デフォルトの名無しさん[sage] 投稿日:2008/05/11(日) 11:54:30
'bar'[2, 1] #=> 'r'
'bar'[3, 1] #=> ''
'bar'[4, 1] #=> nil

本当だ。なんかバグっぽい挙動だな

5/15追記: 読んだのはruby-1.8.6-p114

void Init_String(){
    // 前略
    rb_define_method(rb_cString, "[]", rb_str_aref_m, -1);
    // 後略
}

とのことなので、rb_str_aref_mを見る。

static VALUE rb_str_aref_m(int argc, VALUE *argv, VALUE str){
    if (argc == 2) {
    	if (TYPE(argv[0]) == T_REGEXP) {
    	    return rb_str_subpat(str, argv[0], NUM2INT(argv[1]));
    	}
    	return rb_str_substr(str, NUM2LONG(argv[0]), NUM2LONG(argv[1]));
    }
    if (argc != 1) {
	    rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc);
    }
    return rb_str_aref(str, argv[0]);
}

String#[begin, length] の形式なのでargc == 2のはず。引数にRegexpオブジェクトも
ないので呼ばれるのはrb_str_substrだ。
RHGによるとNUM2LONG()みたいなマクロの名前で、NUM = Fixnum や Bignumらしいので、
VALUEの引数二つをCレベルのlongに変換して、rb_str_substrに渡す。


rb_str_substrに行き着いた。

VALUE rb_str_substr(VALUE str, long beg, long len){
    VALUE str2;
    
    if (len < 0) return Qnil;
    
    // (A) [4, 1]がnilなのはたぶんココから
    if (beg > RSTRING(str)->len) return Qnil;
    
    if (beg < 0) {
    	beg += RSTRING(str)->len;
    	if (beg < 0) return Qnil;
    }
    
    // (B) lenが長すぎる指定だと、begから終りまでに切り詰められる
    if (beg + len > RSTRING(str)->len) {
	    len = RSTRING(str)->len - beg;
    }
    
    if (len < 0) {
	    len = 0;
    }
    
    if (len == 0) {
    	str2 = rb_str_new5(str,0,0);
    } else if (len > sizeof(struct RString)/2 &&
	      beg + len == RSTRING(str)->len && !FL_TEST(str, STR_ASSOC)) {
    	str2 = rb_str_new4(str);
    	str2 = str_new3(rb_obj_class(str2), str2);
    	RSTRING(str2)->ptr += RSTRING(str2)->len - len;
    	RSTRING(str2)->len = len;
    } else {
	    str2 = rb_str_new5(str, RSTRING(str)->ptr+beg, len);
    }
    
    OBJ_INFECT(str2, str);
    
    return str2;
}

[3, 1]だと(B)でlen == 0にされて、その後空文字列を返しちゃうのか。
(A)が

    if (beg >= RSTRING(str)->len) return Qnil;

だといいのかな。それともこの挙動が正しくてマニュアルの間違いなんだろうか。
str[3] が nil だからマニュアルどおりのほうがしっくりくるような気はする。