Rustの文字と文字列について
はじめに
Rustの文字、文字列型(char, String, str)についてよく理解していなかったので調べてみました。
char型
文字型です。Rustの文字型は1バイトを表す型ではなく、Unicodeスカラー値1つを表す型です。なので、日本語のかな文字のような文字も表現することができます。
let c: char = 'あ';
char型は1文字を表すのに4バイトもの領域を使用します。
>> println!("{}", std::mem::size_of::<char>()); 4
また、char型に格納できるのはあくまでUnicodeスカラー値(サロゲートペアに使われるコードポイント[U+D800-U+DFFF]を除いたコードポイント1つ)なので、1⃣(U+0031, U+20e3)のように複数のコードポイントで構成される文字は表現できません。
String型
Rustでは、文字列はString型で表現されます。
ドキュメントを見てみると
A UTF-8 encoded, growable string.
と書いているので、UTF-8エンコードされた文字列を格納している、そして可変長であることがわかります。
余談なんですが、Javaと#Cの文字列の内部表現はUTF-16なんですねー。ここを読んで初めて知りました。
Rustにおいて、文字列リテラルはString型ではないので、String型のデータを作るには、文字列リテラルから変換するなどして、ひと手間加える必要があります。
// from関連関数を使うパターン let s = String::from("Hello, world!"); // 文字列リテラルのto_stringメソッドを使うパターン let s = "Hello, world!".to_string();
また、String型は可変長なので、あとから文字列を追加することもできます。
let s = String::from("Hello, "); s.push_str("world!"); // s = "Hello, world!"
String型の文字列はヒープ上にある
ドキュメントをみると、Stringは以下のように定義されています。
pub struct String { vec: Vec<u8>, }
Vec<u8>をラップしているだけっぽいですね。またまたドキュメントですが、Vecに関してこう書かれています。
If a Vec has allocated memory, then the memory it points to is on the heap... Vec will never perform a "small optimization" where elements are actually stored on the stack...
雑訳: もし、Vecがメモリ領域を確保する場合、そのメモリ領域はヒープ上にある... Vecは、実際には要素をスタック上に格納する"small optimization"を決して行いません…
なので、String型のデータはヒープ領域にあることがわかります。
String型の文字列に対して添字アクセスできない
String型は添字アクセスをサポートしていません。文字列に対して添字アクセスを実行する場合、以下の3つのいずれかの結果を期待するのではないでしょうか。
このように、同じ添字アクセスでも、どのような結果を期待するかはケースバイケースでしょう。
Rustはそれぞれに目的に応じて、文字列の要素にアクセスする異なる方法を提供します。
&str型
str型はString型のスライスです。
実践Rust入門によると、Rustのスライスは、配列のようにメモリ領域に同じ型が連続して並んでいるデータ構造の一部分を指し示すビューです。スライスには、不変または可変の参照かBoxというポインタの一種を通じてアクセスするので、型は&[T]、&mut [T]、Box<[T]>のように表記します。
str型はString型のスライスなので、通常&str、&mut strのような型の表記で見かけます。ちなみに、文字列リテラルの型は&strです。(正確には、文字列リテラルは静的なライフタイムを持つので、型は&'static strになります)
&str型をString型に変換するには
let s = "あ".to_string();
逆にString型を&str型に変換するには
let s = String::from("あ").as_str();
String型と&str型の使い分け
String型は可変長なので、文字列に逐次新しい文字列を追加したい場合に使えば良いかも知れません。(ログとかHTTPレスポンスの構築とか?)
&strはString型に対するビューで固定長なので、構文解析など文字列を変更せずに参照するだけの処理に使えば良いかもしれません。
おわりに
色々ややこしすぎないか… 途中で一度Unicode沼にハマりかけてしまった。