あらすじとしては、
- RustでJavaScriptのRegExpパーサーを書いてた
RegExp
は、u
かv
フラグがつくと、正規表現をUnicode単位で処理できるようになる- そうでない場合、UTF-16のコードユニット単位で処理する
/^.{1}$/.test('𠮷'); // false
/^.{1}$/u.test('𠮷'); // true
/^.{1}$/v.test('𠮷'); // true
これをRustでやるには・・・?という話。
Rustのchar
はUnicode scalar value
char - Rust https://doc.rust-lang.org/std/primitive.char.html
またの名を、Unicodeコードポイントとのこと。
Rustで文字といえばchars()
ってことで、これをそのまま使った場合こうなる。
let s = "Hello, 👈🏻𠮷野家あっち/xyz";
let chars = s.chars().collect::<Vec<_>>();
println!("{chars:?}");
出力されるのは、['H', 'e', 'l', 'l', 'o', ',', ' ', '👈', '🏻', '𠮷', '野', '家', 'あ', 'っ', 'ち', '/', 'x', 'y', 'z']
という文字たち。
EmojiのSkin toneとか国旗とかはバラけるけど、吉野家の”よし”はちゃんと土かんむりで1つになってる。
というわけで、Unicodeモードであるなら、char
をこのまま使えばよさそう。
(複雑な絵文字を見たまま分割するのは、JSでもIntl.Segmenter
とかそういう実装がないとできないし。)
そしてchar
は内部的にu32
そのままらしいので、場合によってはas u32
して取り回すほうが便利かもしれない。
https://doc.rust-lang.org/std/primitive.char.html#method.from_u32
UTF-16単位
Unicodeモードじゃない場合は、encode_utf16()
というAPIで変換してから扱う。
let s = "Hello, 👈🏻𠮷野家あっち/xyz";
let s = s.encode_utf16();
これでu16
のイテレータが取得できる。
ただこっちの場合、ループしてる1つはサロゲートペアの一部であるかもしれず、もはやchar
として復元できる保証はなくなってしまう。
u16
はas u32
に変換することはできるので、画一的に扱いたいならばそうする。
まとめ
fn main() {
let s = "Hello, 👈🏻𠮷野家あっち/xyz";
let count = 20;
println!("--- char(= unicode scalar value) ---");
// `chars()` returns a `char`, which is a Unicode scalar value, not a Unicode code point.
// It can be converted to a `u32`(internally `u32`) to see the code point.
for i in 0..=count {
let cp = s.chars().nth(i).map(|c| c as u32);
let ch = cp.and_then(char::from_u32); // Always `Some`
println!("[{i}]: {:?} = {:?}", cp, ch);
}
println!("--- utf16 code unit ---");
// `encode_utf16()` returns a `u16`, which is a UTF-16 code unit.
// It can be converted to a `u32`(just extend) to see the code point.
for i in 0..=count {
let cp = s.encode_utf16().nth(i).map(|c| c as u32);
let ch = cp.and_then(char::from_u32); // May be `None`
println!("[{i}]: {:?} = {:?}", cp, ch);
}
}
これを実行すると表示されるのはこれ。
--- char(= unicode scalar value) ---
[0]: Some(72) = Some('H')
[1]: Some(101) = Some('e')
[2]: Some(108) = Some('l')
[3]: Some(108) = Some('l')
[4]: Some(111) = Some('o')
[5]: Some(44) = Some(',')
[6]: Some(32) = Some(' ')
[7]: Some(128072) = Some('👈')
[8]: Some(127995) = Some('🏻')
[9]: Some(134071) = Some('𠮷')
[10]: Some(37326) = Some('野')
[11]: Some(23478) = Some('家')
[12]: Some(12354) = Some('あ')
[13]: Some(12387) = Some('っ')
[14]: Some(12385) = Some('ち')
[15]: Some(47) = Some('/')
[16]: Some(120) = Some('x')
[17]: Some(121) = Some('y')
[18]: Some(122) = Some('z')
[19]: None = None
[20]: None = None
--- utf16 code unit ---
[0]: Some(72) = Some('H')
[1]: Some(101) = Some('e')
[2]: Some(108) = Some('l')
[3]: Some(108) = Some('l')
[4]: Some(111) = Some('o')
[5]: Some(44) = Some(',')
[6]: Some(32) = Some(' ')
[7]: Some(55357) = None
[8]: Some(56392) = None
[9]: Some(55356) = None
[10]: Some(57339) = None
[11]: Some(55362) = None
[12]: Some(57271) = None
[13]: Some(37326) = Some('野')
[14]: Some(23478) = Some('家')
[15]: Some(12354) = Some('あ')
[16]: Some(12387) = Some('っ')
[17]: Some(12385) = Some('ち')
[18]: Some(47) = Some('/')
[19]: Some(120) = Some('x')
[20]: Some(121) = Some('y')
UTF-16のほうは、char
に変換できてない場合があることがわかる。