はじめに
これはあくまでメモなので、基本的にググってわかるようなことは詳しく書きません。
よろしくお願いします。
また、以下のプログラムでは面倒なのでfn main() {}
を省略することがあります。
ご承知おきください。
おさらい
前回は2版の4章の4.2に目を通して、以下のことを学びました。
- 参照の方法
- 借用に関するルール
以下のリンクで参照できます。
https://qiita.com/task4233/items/95d9e4ca2610100bcd3b
この記事の目的
Rust Tutorialの各章ごとでメモを残しておくことにより、一通り目を通した後に見返せるようにすることを目的としています。
そのため、各章の最初に何を目的とするか、最後に小さなまとめをメモしています。
なお、Rust Tutorial2版のリンクはこちらです。
(https://doc.rust-jp.rs/book/second-edition/foreword.html)
4.3 スライス
スライスの扱い方を理解すること。
スライス型
スライスは所有権がありませんが、スライス内の要素を参照することができます。
したがって、関数内でスライスを作成して返せば所有権が存在しないためエラーは起きません。
文字列スライス
文字列スライスは、文字列の部分文字列を参照したもので、以下のように書きます。
書き方は、&s[部分文字列の左端
..部分文字列の右端
]です。
ただし、部分文字列は半開区間
で、0-indexed
であることに注意しましょう。
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{}", hello);
println!("{}", world);
// [output]
// hello
// world
文字列スライスを取得する時、以下のルールに則り省略することができます。
- 左端を0から始める場合は、左端を省略可。
- 右端を末尾にする場合は、右端を省略可。
省略すると以下のように書くことができます。
let s = String::from("hello");
let len = s.len(); // len: i32 = 5
// 最初の3文字のスライス(下の記述と等価)
let left_slice = &s[..2];
// let left_slice = &s[0..2];
// 後ろ3文字のスライス(下の記述と等価)
let right_slice = &s[2..];
// let right_slice = &s[2..len];
// 文字列全体のスライス(下の記述と等価)
let full_slice = &s[..];
// let full_slice = &s[0..len];
この記法を利用して、以下のように文字列のうちの最初の単語を抜き出す関数を書くことができます。
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("Hello World");
let word = first_word(&s);
println!("first word: {}", word);
}
// [output]
// first word: Hello
ただし、以下のコードはコンパイルエラーになります。
なぜなら、.clear()
はミュータブルな借用が行われているからです。
ただ、mut
を加えても2回以上のミュータブルな借用になるのでエラーは消えません。
どうすれば.clear()
を使用できるんでしょうね。
借用の規則については、前回の記事を参照してください。
https://qiita.com/task4233/items/95d9e4ca2610100bcd3b
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("Hello World");
let word = first_word(&s);
s.clear();
println!("first word: {}", word);
}
// [output]
//error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
// (イミュータブルな借用が為されているので、ミュータブルな借用はできないよ)
// --> prog.rs:17:3
// |
//15 | let word = first_word(&s);
// | - immutable borrow occurs here
// (イミュータブルな借用が為されてるよ)
//16 |
//17 | s.clear();
// | ^ mutable borrow occurs here
// (ミュータブルな借用が為されてるよ)
//...
//20 | }
// | - immutable borrow ends here
// (イミュータブルな借用終わり)
//
//error: aborting due to previous error
文字列リテラル
今まで文字列リテラルの型を書いていませんでしたが、ここでやっと型を書けます。
文字列リテラルはバイナリの特定の位置をさすスライスです。
let s = "Hello, world!"; // s: &str
これを用いることで、先ほどの関数の引数を以下のように書き換えることが出来る。
fn first_word(s: &str) -> &str {
// 省略
}
そうすることで、以下のように文字列リテラルに対しても機能します。
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let s = String::from("Hello World");
let word = first_word(&s[..]);
let new_s = "Hello World";
let new_word = first_word(&new_s[..]);
let new_word_without_slice = first_word(&new_s[..]);
println!("word: {}", word);
println!("new word: {}", new_word);
println!("new word without slice: {}", new_word_without_slice);
// [output]
// word: Hello
// new word: Hello
// new word without slice: Hello
}
他のスライス
当然ながら、以下のように通常の配列に対してもスライスをとることができます。
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3]; // slice: &[i32]
スライスは部分的な参照で、デフォルトではイミュータブル。
文字列のみでなく、一般的なスライス型も存在する。
おわりに
2版4章4.3では以下のことが書かれていました。
- スライスの扱い方
例などに触れたものの未だにスライスの理解は不十分な気がします。
使い方は理解したので、今後の応用例に期待したいところです。