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

Rust

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

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

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

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

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 は頻繁に登場するので、その性質をしっかり掴んでおきましょう!👍

コメント

タイトルとURLをコピーしました