[Rustのはじめ方] Part12: スライスと文字列の扱い

コレクションの一部を安全に参照する方法を学びます

所有権システムの重要な要素として「スライス」があります。スライスは、コレクション全体への参照を持つのではなく、コレクションの一部への参照を可能にするデータ型です。特に文字列の扱いにおいて、スライスは中心的な役割を果たします。

スライスは、配列やベクタ、文字列などの連続したデータ構造の一部分を指す参照です。スライス自体はデータを持たず、元のデータへのポインタとその長さを保持します。これにより、データのコピーを作成することなく、効率的にデータの一部にアクセスできます。

例えば、配列の一部を参照したい場合、スライスを使用できます。

fn main() { let numbers = [1, 2, 3, 4, 5]; // 配列全体のスライス let all: &[i32] = &numbers[..]; println!("全体のスライス: {:?}", all); // [1, 2, 3, 4, 5] // インデックス1から3まで(3は含まない)のスライス let part: &[i32] = &numbers[1..3]; println!("部分的なスライス: {:?}", part); // [2, 3] // 最初からインデックス2まで(2は含まない)のスライス let start: &[i32] = &numbers[..2]; println!("最初の部分のスライス: {:?}", start); // [1, 2] // インデックス3から最後まで let end: &[i32] = &numbers[3..]; println!("最後の部分のスライス: {:?}", end); // [4, 5]
}
ポイント: スライスは &[T] という型を持ちます。T は要素の型です。範囲指定 [start..end] を使って作成します。start は含む、end は含まないインデックスです。

Rustには主に2種類の文字列型があります。

  • String: ヒープ上に確保され、伸長可能で、所有権を持つUTF-8エンコードされた文字列。
  • &str (文字列スライス): 他の場所に保存されているUTF-8文字列データの一部または全体への参照(借用)。文字列リテラルは &'static str 型です。

String の操作

String は変更可能な文字列です。

fn main() { // 空のStringを作成 let mut s1 = String::new(); s1.push_str("こんにちは"); // 文字列スライスを追加 s1.push('!'); // 文字を追加 println!("s1: {}", s1); // s1: こんにちは! // 文字列リテラルからStringを作成 let s2 = String::from("世界"); println!("s2: {}", s2); // s2: 世界 // .to_string()メソッドでも作成可能 let s3 = "Rust".to_string(); println!("s3: {}", s3); // s3: Rust // 文字列の結合 (+) // s2はムーブされるため、以降は使えなくなる let s4 = s1 + " " + &s2 + " " + &s3 println!("s4: {}", s4); // s4: こんにちは! 世界 Rust // println!("{}", s1); // エラー: s1はムーブされた // format! マクロ (所有権を奪わない) let greeting = "ハロー".to_string(); let target = "ワールド".to_string(); let message = format!("{}, {}!", greeting, target); println!("{}", message); // ハロー, ワールド! println!("{}, {}", greeting, target); // greeting と target はまだ使える
}

文字列スライス (&str)

&str は不変の参照であり、文字列リテラルや String の一部を指すのに使われます。

fn main() { let my_string = String::from("Rustプログラミング"); // String全体のスライスを取得 let s_all: &str = &my_string[..]; println!("全体のスライス: {}", s_all); // 部分的なスライスを取得 (バイトインデックス指定) // "プログラミング" の部分 (UTF-8なのでバイトインデックスに注意) // "R" は 1バイト, "u" は 1バイト, "s" は 1バイト, "t" は 1バイト // "プ" は 3バイト, "ロ" は 3バイト, ... // "Rust" は 4 バイト let s_part: &str = &my_string[5..]; // 5バイト目から最後まで println!("部分的なスライス: {}", s_part); // "プログラミング" // &str から &str を作成 let literal = "これは文字列リテラルです"; let slice_of_literal = &literal[0..8]; // バイトインデックス 0 から 8 まで (8は含まない) println!("リテラルのスライス: {}", slice_of_literal); // "これは文字"
}
注意: Rustの文字列はUTF-8エンコードされています。文字によっては1バイト以上を消費するため、スライスを作成する際はバイトインデックスを指定します。文字の境界以外でスライスしようとするとパニックが発生します。
// fn main() {
// let hello = "こんにちは"; // 各文字3バイト
// // バイトインデックス 1 は "こ" の途中なのでパニック!
// // let slice = &hello[0..1];
// }
文字単位で操作したい場合は、.chars() メソッドなどを検討しましょう。

String&str の関係

  • String から &str を作成するのは簡単で低コストです(借用するだけ)。&my_string&my_string[..] のようにします。
  • &str から新しい String を作成するには、.to_string() メソッドや String::from() を使います。これはデータのコピーを伴います。
  • 関数が文字列データを必要とする場合、所有権が必要なければ &str を引数に取るのが一般的です。これにより、String&str の両方を柔軟に受け入れることができます(Deref 強制型変換のため)。
// &str を受け取る関数
fn print_message(message: &str) { println!("メッセージ: {}", message);
}
fn main() { let msg_string = String::from("これはStringです"); let msg_literal = "これは文字列リテラルです"; print_message(&msg_string); // Stringを&strとして渡す print_message(msg_literal); // &strをそのまま渡す
}
  • スライス (&[T], &str): コレクションの一部または全体への参照。データ自体は所有しない。
  • String: 所有権を持つ、伸長可能なUTF-8文字列。ヒープに格納される。
  • &str: 不変の文字列スライス。文字列リテラルや String の一部/全体を参照する。
  • 文字列のスライスはバイトインデックスで行う。UTF-8の文字境界に注意が必要。
  • 関数は、所有権が不要なら &str を引数に取ることで、String&str の両方を受け入れやすくなる。

スライスと文字列の基本を理解することは、Rustで効率的かつ安全なコードを書く上で非常に重要です。特に &str は頻繁に登場するので、その性質をしっかり掴んでおきましょう!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です