はじめに
Rust では、所有権を持ち拡張可能なテキストに String を使い、テキストを借用して参照するビューに &str を使います。文字列スライスを使うと、文字をコピーせずに文字列全体、または指定したバイト範囲を参照できます。
この実験では、所有権を持つテキストを &str として借用する関数に渡し、借用したコマンドキーワードを作成します。その後、UTF-8 テキストをサポートされていない方法でインデックス指定して、コンパイラーのエラーを確認します。最後に、UTF-8 の有効な境界を考慮したスライスに修正します。
所有権を持つテキストとリテラルを &str として借用する
このステップでは、借用した文字列スライスを受け取る 1 つの関数に、異なる 2 種類のテキストを渡します。
用意されたプロジェクトに移動し、ソースファイルを開きます。
cd /home/labex/project/string-slices
nano src/main.rs
String 型は、拡張可能なテキストの所有権を持ちます。&str 型は「string slice」と読み、UTF-8 として有効なテキストを借用して参照するビューです。"preview" のような文字列リテラルは、プログラムの実行中ずっとテキストを保持するため、最初から借用した文字列スライス型になっています。
show_text(label: &str) 関数は、どちらの種類のテキストも参照できます。/* Step 1: borrow command */ だけを &command に置き換えます。
show_text(&command);
Rust は、借用した &String を、関数が要求する &str ビューとして自動的に扱います。この変換で文字がコピーされたり、所有権が移動したりすることはありません。
Ctrl+O で保存し、Enter キーを押してから、Ctrl+X で終了します。プログラムを実行します。
cargo run --quiet
出力は次のようになります。
Text: serve reports
Text: preview
Keyword: <>
Status: ✅ ready
どちらの呼び出しも、同じ &str パラメーターに渡されます。空のキーワードは、次のステップで使用する準備済みのプレースホルダーです。
コピーせずに部分文字列を借用する
このステップでは、keyword が command の先頭にある serve だけを参照するようにします。
ソースファイルを開きます。
nano src/main.rs
&text[start..end] という形式のスライスは、指定した範囲を借用します。開始位置は範囲に含まれ、終了位置は含まれません。Rust の文字列範囲は バイト で数えます。serve の ASCII 文字はそれぞれ 1 バイトなので、範囲はバイト 0 から始まり、バイト 5 の直前で終わります。
Step 2 の行にある空のリテラル "" だけを、&command[0..5] に置き換えます。
let keyword = &command[0..5]; // Step 2: replace the empty slice.
作成された &str は command の内部を参照します。2 つ目の文字列を割り当てたり、コピーしたりすることはありません。command がスコープ内に残っているため、keyword を使用している間、この借用したビューは有効です。
保存して終了し、次のコマンドを実行します。
cargo run --quiet
最後の行が次のようになれば成功です。
Keyword: <serve>
所有権を持つ command には完全な serve reports テキストが残っています。一方、keyword は先頭 5 バイトだけを参照する借用ビューです。
UTF-8 の文字境界を守る
このステップでは、整数インデックスによるアクセスを意図的に試し、コンパイラーの説明を確認します。その後、正しい UTF-8 スライスに置き換えます。
UTF-8 の文字が占めるバイト数は、すべて同じではありません。"✅ ready" では、チェックマーク記号が 3 バイトを使用します。そのため、バイト 0 が必ず「最初の文字」を表すわけではありません。Rust では、String に対する status[0] も許可されていません。
ファイルを開き、Step 3 の出力行の先頭にある // だけを削除します。
nano src/main.rs
保存して終了し、チェックを実行します。ここでは失敗するのが正しい結果です。
cargo check
次の安定したエラーメッセージを探します。
error[E0277]: the type `str` cannot be indexed by `{integer}`
この診断メッセージは、すべての文字が 1 バイトの位置を持つかのように扱うことを防ぎます。もう一度ファイルを開き、失敗する出力行を次の 2 行に置き換えます。
let symbol = &status[0..3];
println!("Status symbol: {symbol}");
0..3 の範囲は、3 バイトの記号の直後で正確に終わります。用意されたテキストとそのバイト境界があらかじめ分かっているため、この操作は有効です。ただし、任意のテキストから人間が見る最初の文字を取得する一般的な方法ではありません。UTF-8 文字の途中で範囲を切ると実行時にパニックが発生するため、文字列の範囲には有効な境界を指定する必要があります。
保存して終了し、チェックと実行を行います。
cargo check
cargo run --quiet
完全な出力は次のようになります。
Text: serve reports
Text: preview
Keyword: <serve>
Status: ✅ ready
Status symbol: ✅
この出力が表示されれば、最後のスライスが元の String をコピーせずに、完全な UTF-8 文字を 1 つ選択できたことが分かります。
まとめ
所有権を持つ String 値と借用した &str ビューを関連付け、割り当てを行わずに部分文字列を選択しました。また、UTF-8 テキストをバイトでインデックス付けされた文字配列として扱えない理由を学びました。安全な文字列範囲は、UTF-8 の有効な境界から始まり、その境界で終わる必要があります。


