Rubyの文字列がエンコード情報を持っている話

Rubyの文字列はエンコード情報を持っているという話。

struct RString {
    struct RBasic basic;
    union {
	struct {
	    long len;
	    char *ptr;
	    union {
		long capa;
		VALUE shared;
	    } aux;
	} heap;
	char ary[RSTRING_EMBED_LEN_MAX + 1];
    } as;
};

あれ?持ってないぞ?

と思ったら

int
rb_enc_get_index(VALUE obj)
{
...
      case T_STRING:
      case T_REGEXP:
	i = ENCODING_GET_INLINED(obj);
	if (i == ENCODING_INLINE_MAX) {
	    VALUE iv;

	    iv = rb_ivar_get(obj, rb_id_encoding());
	    i = NUM2INT(iv);
	}
	break;

とかなってて

#define ENCODING_GET_INLINED(obj) (int)((RBASIC(obj)->flags & ENCODING_MASK)>>ENCODING_SHIFT)

なんで

struct RBasic {
    VALUE flags;
    VALUE klass;
};

ここに詰まっているようだ。

#define ENCODING_MASK (((VALUE)ENCODING_INLINE_MAX)<<ENCODING_SHIFT)
#define ENCODING_INLINE_MAX 1023

それはさておき文字列を結合する時にはどうなるのか。

VALUE
rb_str_plus(VALUE str1, VALUE str2)
{
...
    enc = rb_enc_check(str1, str2);
rb_encoding*
rb_enc_check(VALUE str1, VALUE str2)
{
    rb_encoding *enc = rb_enc_compatible(str1, str2);
    if (!enc)
	rb_raise(rb_eEncCompatError, "incompatible character encodings: %s and %s",
		 rb_enc_name(rb_enc_get(str1)),
		 rb_enc_name(rb_enc_get(str2)));
    return enc;
}

rb_enc_compatibleは、まあ両方同じエンコーディングだったらすんなりそれが返ってくるけど、違う場合が…

rb_encoding*
rb_enc_compatible(VALUE str1, VALUE str2)
{

... (同じだったら片方を返したり、どっちかが-1だったら0を返したり)

    if (TYPE(str2) == T_STRING && RSTRING_LEN(str2) == 0)
	return (idx1 == ENCINDEX_US_ASCII && rb_enc_asciicompat(enc2)) ? enc2 : enc1;
    if (TYPE(str1) == T_STRING && RSTRING_LEN(str1) == 0)
	return (idx2 == ENCINDEX_US_ASCII && rb_enc_asciicompat(enc1)) ? enc1 : enc2;
    if (!rb_enc_asciicompat(enc1) || !rb_enc_asciicompat(enc2)) {
	return 0;
    }

    /* objects whose encoding is the same of contents */
    if (BUILTIN_TYPE(str2) != T_STRING && idx2 == ENCINDEX_US_ASCII)
	return enc1;
    if (BUILTIN_TYPE(str1) != T_STRING && idx1 == ENCINDEX_US_ASCII)
	return enc2;
...

ASCII互換のエンコーディングの文字列とASCIIとの結合だったらASCIIの側を昇格して…とか片方が7bitのコードレンジだったらどうこうして、とか色々頑張ってあった。大変だなぁ。

まとめ

というわけでよく独自路線を突っ走ってるという噂を聞くRuby1.9の文字列周りを追いかけてみました。
ざっくり言うとこんな感じだと思いました: