コレクションの一部を安全に参照する方法を学びます
所有権システムの重要な要素として「スライス」があります。スライスは、コレクション全体への参照を持つのではなく、コレクションの一部への参照を可能にするデータ型です。特に文字列の扱いにおいて、スライスは中心的な役割を果たします。
スライスとは?
スライスは、配列やベクタ、文字列などの連続したデータ構造の一部分を指す参照です。スライス自体はデータを持たず、元のデータへのポインタとその長さを保持します。これにより、データのコピーを作成することなく、効率的にデータの一部にアクセスできます。
例えば、配列の一部を参照したい場合、スライスを使用できます。
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
は含まないインデックスです。 文字列の扱い: `String` と `&str`
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
は頻繁に登場するので、その性質をしっかり掴んでおきましょう!