コンテンツにスキップ

Rustのスライス

スライス(slice)を使うと、配列やベクタの一部分を借用できます。データの一部をのぞく「窓」のようなもので、データをコピーすることも、所有権を奪うこともありません。ひとつの値の借用を使ったことがあれば、スライスはその考え方を要素の範囲に広げたものだと思ってください。

fn main() {
let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // インデックス1、2、3の要素
println!("{:?}", middle);
}

実行結果:

[20, 30, 40]
▶ 実際に試してみよう

1..4 は、forループで見たのと同じ種類の範囲です。インデックス1から始まり、インデックス4の手前で止まるので、インデックス1、2、3が取り出されます。

よく使う省略形をいくつか紹介します。

fn main() {
let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // 先頭からインデックス3の手前まで -> [10, 20, 30]
println!("{:?}", &numbers[2..]); // インデックス2から最後まで -> [30, 40, 50]
println!("{:?}", &numbers[..]); // 全体 -> [10, 20, 30, 40, 50]
}
▶ 実際に試してみよう

実は、あなたはもうスライスを使っています。&str は String(または別の文字列)のスライスなのです。この2つの型がとても相性よく使えるのは、そのためです。

fn main() {
let sentence = String::from("The quick brown fox");
let first_word = &sentence[0..3];
println!("{first_word}"); // "The"
}
▶ 実際に試してみよう

ここでは文字列をあえて英語のままにしています。文字列スライスの 0..3 は文字の数ではなくバイトの位置を表すからです。日本語の文字はUTF-8で1文字3バイトなので、同じ範囲でも結果が変わってしまいます(文字の途中で切ろうとするとパニックします)。

コピーではなくスライスを使う理由

Section titled “コピーではなくスライスを使う理由”

ほしい部分だけを新しい Vec や String にコピーする、というやり方も考えられます。スライスなら、そんな手間はまったくいりません。参照(&)がひとつの値を指すのと同じように、スライスは既存のデータの一部を指すだけです。つまり、余計なメモリ確保もコピーもありません。そして、借用のページで学んだルールがそのまま当てはまります。

スライスは、関数の引数として特に便利です。配列もベクタも受け取れるからです。

fn sum(numbers: &[i32]) -> i32 {
let mut total = 0;
for n in numbers {
total += n;
}
total
}
fn main() {
let array = [1, 2, 3];
let vector = vec![4, 5, 6];
println!("{}", sum(&array));
println!("{}", sum(&vector));
}
▶ 実際に試してみよう

&[i32] は、配列からのスライスも Vec からのスライスも、どちらも受け取れます。関数はひとつなのに、どちらの呼び出し方でも動きます。

可変スライスでデータを変更する

Section titled “可変スライスでデータを変更する”

ここまでのスライスは、データを読むだけでした。可変スライス(mutable slice)&mut [T] を使うと、関数は渡された要素を変更できます。ただし、変更できるのは渡された要素だけです。

fn double_all(numbers: &mut [i32]) {
for i in 0..numbers.len() {
numbers[i] *= 2;
}
}
fn main() {
let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // インデックス1と2だけ
println!("{:?}", scores);
double_all(&mut scores); // ベクタ全体
println!("{:?}", scores);
}

実行結果:

[1, 4, 6, 4, 5]
[2, 8, 12, 8, 10]
▶ 実際に試してみよう

1回目の呼び出しでは &mut scores[1..3] を渡しているので、double_all から見えるのはインデックス1と2だけです。その範囲の外にある要素には手出しできません。関数の中では、スライスはまたインデックス0から始まります。double_all にとっては、2つの数が入ったただのリストなのです。

これが安全なのは、借用のページできちんと学ぶルールのおかげです。関数が &mut のスライスを持っている間は、ほかの誰もその要素を使えません。だから、コードの2か所が同時に同じ要素を変更してしまうことは決してありません。また、ベクタも mut で宣言しておく必要があります。そうしないと、Rustは可変で借用させてくれません。

ベクタ vec![3, 6, 9, 12, 15, 18] を作ります。その前半と後半を、2つの別々のスライスとして表示してください。ヒント:let mid = numbers.len() / 2; を計算して、..mid と mid.. を使います。

解答を見る
fn main() {
let numbers = vec![3, 6, 9, 12, 15, 18];
let mid = numbers.len() / 2;
let first_half = &numbers[..mid];
let second_half = &numbers[mid..];
println!("前半: {:?}", first_half);
println!("後半: {:?}", second_half);
}

実行結果:

前半: [3, 6, 9]
後半: [12, 15, 18]
▶ 実際に試してみよう

mid を len() から計算しているので、数を増やしたり減らしたりしても、そのまま動きます。長さが奇数のときは、余った1つは後半に入ります。

いちばん大きい数を返す関数 largest(numbers: &[i32]) -> i32 を書いてください。配列、ベクタ、そしてベクタの最初の要素だけのスライスで呼び出してみましょう。ヒント:配列のときと同じように、numbers[0] から始めて残りをループします。

解答を見る
fn largest(numbers: &[i32]) -> i32 {
let mut biggest = numbers[0];
for n in numbers {
if *n > biggest {
biggest = *n;
}
}
biggest
}
fn main() {
let array = [7, 2, 9, 4];
let vector = vec![10, 35, 21];
println!("{}", largest(&array));
println!("{}", largest(&vector));
println!("{}", largest(&vector[..1]));
}

実行結果:

9
35
10
▶ 実際に試してみよう

スライスをループすると、数そのものではなく、各要素への参照(&i32)が渡されます。*n は「n が指している値」という意味なので、*n > biggest はふつうの数どうしの比較になります。この関数は、空のスライスを渡すとパニックします。numbers[0] が存在しないからです。そういう場合にOptionを返す方法は、あとで学びます。

  • スライス(&[T])は、配列やベクタの一部をコピーせずに借用します。
  • 1..4 はインデックス1から3までを選びます。.. の片側を省略すると、先頭から、または最後までになります。
  • &str 自体がテキストのスライスです。文字列とスライスの振る舞いがよく似ているのはそのためです。
  • 配列とベクタの両方を受け取りたい関数では、引数の型に &[T] を使いましょう。
  • 可変スライス(&mut [T])を使うと、関数は渡された要素だけを変更できます。

理解度チェック

1. スライスを使うと、配列やベクタの一部に対して何ができますか?

2. &numbers[1..4] に含まれるインデックスはどれですか?

3. 関数の引数の型として &[i32] がよい選択なのはなぜですか?

スコア: 0 / 3