Rust の str は、UTF-8 形式のテキストデータを表す動的サイズ型(DST: Dynamically Sized Type)です。この型そのものはメモリ上のサイズが実行時に決まるため、直接変数として宣言することはできません。代わりに、参照型 &str や所有権型 Box を通じて操作されます。
動的サイズ型としての特性
str は、文字列の長さがランタイムで決定されるため、コンパイル時にサイズが不明です。たとえば、1 文字の "A" から複数バイトの絵文字まで、任意の長さの UTF-8 バイト列を含む可能性があります。この性質により、Rust の型システムは str をスタック上に配置することができず、必ず間接的なアクセス手段が必要になります。
&str:文字列スライスの役割
&str は、str の不変参照であり、一般的に「文字列スライス」と呼ばれます。この型は、以下のような状況で使用されます:
- リテラル文字列(例:
"こんにちは") Stringオブジェクトの一部または全体を参照する場合- 関数の引数として文字列を軽量に渡す場合
スライスは所有権を持たず、参照先のメモリが有効である限りのみ利用可能です。そのため、スライスはコピーが軽く、読み取り専用である点で効率的です。
静的メモリとヒープの違い
文字列データは、そのライフタイムと配置場所によって異なる形式で扱われます:
- 静的ストレージ:文字列リテラルはプログラムの読み取り専用セクションに格納され、型は
&'static strになります。例:let text: &str = "定数文字列"; - ヒープ上:動的に生成された文字列は
Boxで所有されます。これは、Stringと同様にヒープに配置されますが、可変性はなく、strの所有権のみを提供します。
実用的な例
文字列リテラルは、自動的に &'static str として扱われます:
let message: &str = "Rust は安全な言語です";
println!("{}", message);
String から &str への変換は、明示的または暗黙的に行えます:
let owned: String = "動的生成".to_string();
let borrowed: &str = owned.as_str(); // 明示的
let borrowed2: &str = &owned; // 暗黙的(Deref 自動変換)
Box を用いてヒープに所有権を持たせる例:
let dynamic: Box<str> = "動的長さの文字列".into();
let len = dynamic.len(); // 11
let first_char = dynamic.chars().next().unwrap(); // '動'
String と str の選択基準
String は、文字列を変更・拡張・所有する必要があるときに使用します。一方、&str は、既存の文字列データを読み取るだけの軽量な参照として最適です。関数の引数では、可能な限り &str を受け取ることで、String や文字列リテラルの両方を受け入れる柔軟性が得られます。