Rustのスライス型の概要

:::note info
この記事は、[はじめてのアドベントカレンダー Advent Calendar 2023](https://qiita.com/advent-calendar/2023/first-time) の13日目です。
:::

Qiita初投稿です。

記事執筆時点の環境
```shell-session
awayume@assam:~$ cargo -v -V
cargo 1.73.0 (9c4383fb5 2023-08-26)
release: 1.73.0
commit-hash: 9c4383fb55986096b414d98125421ab87b5fd642
commit-date: 2023-08-26
host: aarch64-unknown-linux-gnu
libgit2: 1.6.4 (sys:0.17.2 vendored)
libcurl: 8.2.1-DEV (sys:0.4.65+curl-8.2.1 vendored ssl:OpenSSL/1.1.1u)
ssl: OpenSSL 1.1.1u  30 May 2023
os: Ubuntu 22.04 (jammy) [64-bit]
awayume@assam:~$ rustc -v -V
rustc 1.73.0 (cc66ad468 2023-10-03)
binary: rustc
commit-hash: cc66ad468955717ab92600c770da8c1601a4ff33
commit-date: 2023-10-03
host: aarch64-unknown-linux-gnu
release: 1.73.0
LLVM version: 17.0.2
```

# スライスとは
一言で適当に説明すると、**配列とベクタをまとめて扱ったり一部を切り出して使うための型**です。

## 型
例えば保持する値の型が`u8`の場合、次のように書きます。
```rust
let sl: &[u8];
```
ここで「何故`&`が付くのか？」とお思いかもしれません。
では外してみるとどうなるか。
```rust:sl.rs
fn main() {
    let sl: [u8];
}
```
```shell-session
awayume@assam:~/programming/playground/rust-slice-sample$ rustc sl.rs
error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
 --> sl.rs:2:9
  |
5 |     let sl: [u8];
  |         ^^ doesn't have a size known at compile-time
  |
  = help: the trait `Sized` is not implemented for `[u8]`
  = note: all local variables must have a statically known size
  = help: unsized locals are gated as an unstable feature
help: consider borrowing here
  |
5 |     let sl: &[u8];
  |             +

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.
```
**コンパイルエラーになります**。

スライスは**常に参照でなければいけない**という特性をもっています。
そのため、常に `&[T]` のように書かなければならない[^1]のです。

## 定義方法
型とは言ってもスライスをスライスとしてそのまま定義することはできません。つまりどういうことかと言うと、**配列やベクタがないと存在できません**。

例えば、次のコード[^2]はコンパイルエラーになります。
```rust
let sl: &[u8] = [1, 2, 3];
let vsl: &[u8] = vec![1, 2, 3];
```
それもそのはず。型を `&[T]` と参照にしているのにも関わらず、明らかに右辺は参照ではありません。

次のコードはコンパイルが通ります。
```rust
let sl: &[u8] = &[1, 2, 3];
let vsl: &[u8] = &vec![1, 2, 3];
```
このコードでは、右辺がそれぞれ `&[u8; 3]`, `&Vec<u8>` になっています。
つまり、**スライスを定義するときは配列やベクタの参照を型強制する**ことになります。

ちなみに、配列由来のスライスとベクタ由来のスライスで機能的な差はありません。そのため、スライスにしてしまえば配列とベクタを纏めて処理する、といったことも可能[^3]です。

先ほどの例では、配列やベクタ全体に対してスライスを定義しましたが、一部を切り出して定義することも可能です。
```rust
let ar: [u8; 3] = &[1, 2, 3];
let sl: &[u8] = &ar[0..2];
println!("{:?}", sl);  // [1, 2]
```

なお、配列やベクタからスライスを生成（定義）していますが、スライスに値がコピーされるわけではありません。スライスの各要素は配列やベクタの各要素を指し示しているだけです。

## 特徴・使いどころ
配列やベクタと同じように、

- メモリ上で要素が連続して配置されている
- インデックスアクセスが可能
- `for`文やイテレータの使用が可能

などといった性質をもっています。
また、一番重要なのは**元のデータを所有せず、データへの参照を提供する**という点でしょう。
次のコードは、スライスを介して配列の要素を変更する例[^4]です。
```rust
let mut ar: [u8; 3] = [1, 2, 3];
let sl: &mut [u8] = &ar;
println!("{:?}", ar);  // [1, 2, 3]
sl[0] = 0;
println!("{:?}", ar);  // [0, 2, 3]
```
スライスを介して元の配列の要素を書き換えることができました。
しかし、この例ではスライスを介する意味があまりありません。

では、次の場合はどうでしょう。
```rust
let mut ar: [u8; 5] = [1, 2, 3, 4, 5];
let sl: &mut [u8] = &ar[1..4];  // [2, 3, 4]
println!("{:?}", ar);  // [1, 2, 3, 4, 5]
for elm in sl {
    *elm *= 2;
}
println!("{:?}", ar);  // [1, 4, 6, 8, 5]
```
配列の一部をスライスに切り出し、その要素全てに2をかけてみました。
このように、**特定の範囲の要素を一括して変更したいケースなど**でスライスは有用です[^5]。

# まとめ
- **スライスは配列やベクタの要素を指し示す参照**[^6]である
- 配列やベクタの一部を指し示すことが可能
- スライス自体は**データを所有しない**
- 全く別の配列やベクタが欲しいわけでなければ、**スライスを使用することで効率的にメモリを使用できる**
- スライスは**配列の参照やベクタの参照を型強制して定義**する

[2023/12/22追記]
マクロでのスライス型の扱いに関する記事も投稿しました。

https://qiita.com/Awayume/items/b92c73cbe99ad9ec7c64

## 参考
スライスについて詳しく知りたい方は次のページが役に立つと思います。
- [Rustの配列やベクタ、スライスについて - Qiita](https://qiita.com/k-yaina60/items/26bf1d2e372042eff022)
- [スライス | RustCoder 避難所 - Zenn](https://zenn.dev/toga/books/rust-atcoder-old/viewer/21-slice)
- [配列とスライス - Rust By Example 日本語版](https://doc.rust-jp.rs/rust-by-example-ja/primitives/array.html)
- [slice - Rust](https://doc.rust-lang.org/std/primitive.slice.html)

[^1]: この場合は不変のスライスになります。可変にしたい場合、`&mut [T]`のように書きます。
[^2]: そもそもスライス自体を直接定義する方法は存在しないので、適当に書いてみました。どう見てもただの配列とベクタやろがい。かなり支離滅裂。
[^3]: そもそもそういうシチュエーションってあるんでしょうか。
[^4]: もちろんベクタでも可能です。
[^5]: この例の場合、Rangeでforを回してインデックスアクセスをすればいいような気もします。それに、どちらかといえばforでスライス全体を回すよりもイテレータを使った方が綺麗かも。
[^6]: 参照と書きましたが、**スライスの要素が参照というわけではありません**。