スライス
コレクションの一部を参照する「スライス」について学びます。
スライスとは
スライスはコレクションの一部への参照です。所有権を持ちません。
fn main() {
let s = String::from("hello world");
let hello = &s[0..5]; // "hello"へのスライス
let world = &s[6..11]; // "world"へのスライス
println!("{}, {}", hello, world);
}
文字列スライス(&str)
基本的な使い方
fn main() {
let s = String::from("hello world");
let slice1 = &s[0..5]; // "hello"
let slice2 = &s[6..11]; // "world"
let slice3 = &s[0..]; // "hello world"(最初から)
let slice4 = &s[..5]; // "hello"(最初から5まで)
let slice5 = &s[..]; // "hello world"(全体)
println!("{}", slice1);
}
範囲の構文
| 構文 | 意味 | 例(“hello“の場合) |
|---|---|---|
[0..5] | 0から5未満 | “hello” |
[..5] | 最初から5未満 | “hello” |
[3..] | 3から最後まで | “lo” |
[..] | 全体 | “hello” |
[0..=4] | 0から4まで(含む) | “hello” |
メモリレイアウト
#![allow(unused)]
fn main() {
let s = String::from("hello world");
let hello = &s[0..5];
String (s) ヒープ
┌─────────────┐ ┌───────────────────────┐
│ ptr ─────────────────────→│ h e l l o w o r l d │
│ len: 11 │ └───────────────────────┘
│ cap: 11 │ ↑
└─────────────┘ │ ptr
┌──┴────────┐
&str (hello) │ len: 5 │
└───────────┘
}
&strは以下を持ちます:
- ポインタ(データの開始位置)
- 長さ
String と &str の関係
#![allow(unused)]
fn main() {
let s: String = String::from("hello"); // 所有権あり
let slice: &str = &s; // Stringへのスライス
let literal: &str = "hello"; // 文字列リテラル('static)
}
使い分け
| 型 | 特徴 | 用途 |
|---|---|---|
String | 所有権あり、変更可能 | 文字列を所有・変更したいとき |
&str | 参照、読み取り専用 | 文字列を読むだけのとき |
関数の引数は&strを推奨
#![allow(unused)]
fn main() {
// これより...
fn greet(name: &String) { ... }
// こちらが良い
fn greet(name: &str) { ... }
}
&strを受け取る関数は、Stringからも文字列リテラルからも呼び出せます:
fn greet(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let s = String::from("World");
greet(&s); // Stringから(自動的に&strに変換)
greet("World"); // 文字列リテラルから
}
配列スライス
配列やVecの一部も参照できます。
fn main() {
let arr = [1, 2, 3, 4, 5];
let slice: &[i32] = &arr[1..4]; // [2, 3, 4]
println!("{:?}", slice);
}
Vecのスライス
fn main() {
let v = vec![1, 2, 3, 4, 5];
let slice = &v[1..4]; // &[i32]型
println!("{:?}", slice); // [2, 3, 4]
}
スライスを使った関数
最初の単語を返す
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("最初の単語: {}", word); // "hello"
}
配列の合計を計算
fn sum(numbers: &[i32]) -> i32 {
let mut total = 0;
for n in numbers {
total += n;
}
total
}
fn main() {
let arr = [1, 2, 3, 4, 5];
let vec = vec![10, 20, 30];
println!("配列の合計: {}", sum(&arr)); // 15
println!("Vecの合計: {}", sum(&vec)); // 60
println!("一部の合計: {}", sum(&arr[1..4])); // 9 (2+3+4)
}
スライスの安全性
スライスは参照なので、所有権ルールに従います。
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // 不変借用
// s.clear(); // エラー!wordが有効な間は変更できない
println!("{}", word);
}
fn first_word(s: &str) -> &str {
&s[..5]
}
よくあるパターン
パターン1: イテレーションしながらインデックスを使わない
#![allow(unused)]
fn main() {
// 良くない(インデックスアクセス)
fn process_bad(data: &[i32]) {
for i in 0..data.len() {
println!("{}", data[i]);
}
}
// 良い(イテレータ使用)
fn process_good(data: &[i32]) {
for item in data {
println!("{}", item);
}
}
}
パターン2: 範囲チェック
#![allow(unused)]
fn main() {
fn safe_get(data: &[i32], index: usize) -> Option<&i32> {
data.get(index) // 範囲外ならNone
}
}
まとめ
| スライス型 | 元のデータ | 用途 |
|---|---|---|
&str | String, 文字列リテラル | 文字列の一部を参照 |
&[T] | 配列, Vec<T> | コレクションの一部を参照 |
| 特徴 | 内容 |
|---|---|
| 所有権 | なし(参照) |
| サイズ | ポインタ + 長さ |
| 安全性 | 借用ルールに従う |
確認テスト
Q1. &strとStringの違いは?
正解: B)
Stringは文字列データの所有権を持ち、変更可能です。&strは文字列への参照(スライス)で、所有権を持たず、読み取り専用です。
Q2. 関数の引数として文字列を受け取る場合、推奨される型は?
正解: C)
&strを受け取る関数は、Stringからも文字列リテラルからも呼び出せるため、より柔軟です。&StringだとStringからしか呼び出せません。
Q3. 以下のコードの出力は?let s = String::from("hello world"); let part1 = &s[..5]; let part2 = &s[6..]; println!("{}-{}", part1, part2);
正解: A)
&s[..5]はインデックス0から5未満で「hello」、&s[6..]はインデックス6から最後までで「world」になります。
Q4. let mut s = String::from("hello"); let slice = &s[..]; s.push_str(" world");がエラーになる理由は?
正解: B)
sliceがsへの不変参照を持っている間に、s.push_str()でsを変更しようとしているためエラーになります。スライスを先に使い切るか、変更後にスライスを取得する必要があります。
Q5. スライス&[T]の特徴として正しいものは?
正解: D) スライス
&[T]は配列やVec<T>の一部(または全体)を参照できます。所有権は持たず、借用ルールに従います。
Phase 2 完了!
おめでとうございます!Rust最重要のPhase 2を完了しました。
学んだこと:
- スタックとヒープの違い
- 所有権の3つのルール
- 参照と借用(不変参照、可変参照)
- ライフタイムの基礎
- スライス(&str、&[T])
**これらの概念はRustの核心です。**最初は難しく感じても、コードを書いていくうちに自然と理解できるようになります。
次のPhase: Phase 3: 構造化プログラミング