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