読者です 読者をやめる 読者になる 読者になる

Crystal の文字列は UTF-8 固定でつらくない!

Crystal Ruby

これは「Ruby脳にはCrystalつらい Advent Calendar 2015」の18日目の記事です。

qiita.com

Ruby の文字列のエンコーディングはオブジェクト毎に異なる可能性があります。 エンコーディングが異なる文字列を結合したりマッチングさせようとすると例外になります。

u = "ほげ".encode("utf-8")
s = "ふが".encode("cp932")
u + s
#=> incompatible character encodings: UTF-8 and Windows-31J (Encoding::CompatibilityError)

Crystal の文字列は UTF-8 固定なのでそのようなことはありません。つらくない!

文字列リテラルは UTF-8 文字しか書くことができません。リテラル中で \000\377 表記で8進数で直接コードを記述できますが、これはバイナリデータではなく U+0000〜U+00FF の文字に相当するので、UTF-8 として正当な文字になります。

Crystal プログラムファイルを UTF-8 以外のエンコーディングで記述した場合はコンパイル時(というかパース時?)にエラーになります。

% crystal sjis.cr
Error: file './sjis.cr' is not a valid Crystal source file: Invalid byte sequence in UTF-8 string

文字列は文字単位以下には分割できないので安全です。 …と思ったら String#split(//) が実行時エラー(InvalidByteSequenceError)になるんですが…。バグかしら。

% crystal eval 'p "あいうえお".split(//)'
Invalid byte sequence in UTF-8 string (InvalidByteSequenceError)
[4358327] *CallStack::unwind:Array(Pointer(Void)) +87
[4358218] *CallStack#initialize<CallStack>:Array(Pointer(Void)) +10
[4358170] *CallStack::new:CallStack +42
[4445311] *Exception +31
[4445245] *InvalidByteSequenceError#initialize<InvalidByteSequenceError, String>:CallStack +29
[4445185] *InvalidByteSequenceError::new<String>:InvalidByteSequenceError +97
[4445077] *InvalidByteSequenceError::new:InvalidByteSequenceError +21
[4455674] *Char::Reader#invalid_byte_sequence<Char::Reader>:NoReturn +10
[4454880] *Char::Reader#decode_current_char<Char::Reader>:Char +208
[4454658] *Char::Reader#initialize<Char::Reader, String>:Char +34
[4454562] *Char::Reader::new<String>:Char::Reader +98
[4356762] *String#inspect<String, IO::FileDescriptor>:IO::FileDescriptor +90
[4447095] *Array(String) +695
[4446390] *Array(String) +6
[4346776] *p<Array(String)>:Array(String) +24
[4339430] ???
[4346448] main +32
[139666576783936] __libc_start_main +240
[4336713] _start +41
[0] ???

[追記]

Crystal 0.10 で直ったようです。

% crystal eval 'p "あいうえお".split(//)'
["あ", "い", "う", "え", "お"]