この記事は、はじめてのアドベントカレンダー Advent Calendar 2023 の13日目です。
Qiita初投稿です。
記事執筆時点の環境
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
の場合、次のように書きます。
let sl: &[u8];
ここで「何故&
が付くのか?」とお思いかもしれません。
では外してみるとどうなるか。
fn main() {
let sl: [u8];
}
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はコンパイルエラーになります。
let sl: &[u8] = [1, 2, 3];
let vsl: &[u8] = vec![1, 2, 3];
それもそのはず。型を &[T]
と参照にしているのにも関わらず、明らかに右辺は参照ではありません。
次のコードはコンパイルが通ります。
let sl: &[u8] = &[1, 2, 3];
let vsl: &[u8] = &vec![1, 2, 3];
このコードでは、右辺がそれぞれ &[u8; 3]
, &Vec<u8>
になっています。
つまり、スライスを定義するときは配列やベクタの参照を型強制することになります。
ちなみに、配列由来のスライスとベクタ由来のスライスで機能的な差はありません。そのため、スライスにしてしまえば配列とベクタを纏めて処理する、といったことも可能3です。
先ほどの例では、配列やベクタ全体に対してスライスを定義しましたが、一部を切り出して定義することも可能です。
let ar: [u8; 3] = &[1, 2, 3];
let sl: &[u8] = &ar[0..2];
println!("{:?}", sl); // [1, 2]
なお、配列やベクタからスライスを生成(定義)していますが、スライスに値がコピーされるわけではありません。スライスの各要素は配列やベクタの各要素を指し示しているだけです。
特徴・使いどころ
配列やベクタと同じように、
- メモリ上で要素が連続して配置されている
- インデックスアクセスが可能
-
for
文やイテレータの使用が可能
などといった性質をもっています。
また、一番重要なのは元のデータを所有せず、データへの参照を提供するという点でしょう。
次のコードは、スライスを介して配列の要素を変更する例4です。
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]
スライスを介して元の配列の要素を書き換えることができました。
しかし、この例ではスライスを介する意味があまりありません。
では、次の場合はどうでしょう。
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追記]
マクロでのスライス型の扱いに関する記事も投稿しました。
参考
スライスについて詳しく知りたい方は次のページが役に立つと思います。
- Rustの配列やベクタ、スライスについて - Qiita
- スライス | RustCoder 避難所 - Zenn
- 配列とスライス - Rust By Example 日本語版
- slice - Rust