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の文字列周りを追いかけてみました。
ざっくり言うとこんな感じだと思いました:
- C: バイト列
- Pascal: バイト列+長さ
- Java: 16bit列+長さ。UTF-16固定。なので結合などの時に何も気にする必要がない。getBytesの引数にエンコーディングを渡して、他のエンコーディングでのバイト列を得ることが出来る。
- Ruby: バイト列+長さ+エンコーディング。結合などの時にはエンコーディングに互換性があるかどうかチェックする。
- Python: 「バイト列+長さ」な文字列と「Unicodeコードポイント列+長さ」なユニコード文字列とがある。16bitか32bitかはコンパイルオプション。文字列単体だとPascalみたい。ユニコード文字列単体だとJavaみたい。バイト列とユニコード文字列の結合はバイト列側がASCIIのときのみ互換(2系) / 非互換(3系)