Rustのスライス
スライス(slice)を使うと、配列やベクタの一部分を借用できます。データの一部をのぞく「窓」のようなもので、データをコピーすることも、所有権を奪うこともありません。ひとつの値の借用を使ったことがあれば、スライスはその考え方を要素の範囲に広げたものだと思ってください。
配列をスライスする
Section titled “配列をスライスする”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が取り出されます。
範囲の省略形
Section titled “範囲の省略形”よく使う省略形をいくつか紹介します。
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]}文字列スライス
Section titled “文字列スライス”実は、あなたはもうスライスを使っています。&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 にコピーする、というやり方も考えられます。スライスなら、そんな手間はまったくいりません。参照(&)がひとつの値を指すのと同じように、スライスは既存のデータの一部を指すだけです。つまり、余計なメモリ確保もコピーもありません。そして、借用のページで学んだルールがそのまま当てはまります。
スライスを受け取る関数
Section titled “スライスを受け取る関数”スライスは、関数の引数として特に便利です。配列もベクタも受け取れるからです。
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は可変で借用させてくれません。
1. 半分に分ける
Section titled “1. 半分に分ける”ベクタ 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つは後半に入ります。
2. どんなリストでも最大値
Section titled “2. どんなリストでも最大値”いちばん大きい数を返す関数 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]));}実行結果:
93510スライスをループすると、数そのものではなく、各要素への参照(&i32)が渡されます。*n は「n が指している値」という意味なので、*n > biggest はふつうの数どうしの比較になります。この関数は、空のスライスを渡すとパニックします。numbers[0] が存在しないからです。そういう場合にOptionを返す方法は、あとで学びます。
- スライス(
&[T])は、配列やベクタの一部をコピーせずに借用します。 1..4はインデックス1から3までを選びます。..の片側を省略すると、先頭から、または最後までになります。&str自体がテキストのスライスです。文字列とスライスの振る舞いがよく似ているのはそのためです。- 配列とベクタの両方を受け取りたい関数では、引数の型に
&[T]を使いましょう。 - 可変スライス(
&mut [T])を使うと、関数は渡された要素だけを変更できます。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. スライスを使うと、配列やベクタの一部に対して何ができますか?
2. &numbers[1..4] に含まれるインデックスはどれですか?
3. 関数の引数の型として &[i32] がよい選択なのはなぜですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- スライス型 — The Rust Programming Language 日本語版:「文字列の最初の単語」の例を使って、一歩ずつスライスにたどり着きます。
- 配列とスライス — Rust By Example 日本語版:配列をスライスとして借用する、実行できるサンプルです。
slice— 標準ライブラリのドキュメント(英語):first、split_at、contains、sortなど、スライスが持つたくさんのメソッドが載っています。- Slice types — The Rust Reference(英語):
[T]と&[T]が正確に何を意味するのか知りたいときの、厳密な定義です。