前回は、値を貸し借りする借用について学んだ。今回は、文字列や配列の「一部分」だけを借用するスライスについて学ぶ。これで、所有権シリーズ(第5〜7回)は完結となる。
学習の進め方
今回も同じく、2段階で進める。
まだ、GitHub Codespacesで環境構築をしていない方は、第1回「GitHub Codespaces で Rust を始めよう!」を先にお読みください。
1. 体験する(trials/)
- ブログ記事を読みながら、
trials/07_slices/で自由にコードを試す - 自分で書き換えたり、わざとエラーを起こしたりして実験する
2. 練習する(exercises/)
- 体験が終わったら、
exercises/07_slices/で構造化された練習問題に挑戦する todo!()の部分を自分のコードに書き換えて完成させる- 解答例も用意されている
準備
# リポジトリを最新の状態にする
cd /workspaces/rust-practice
git pull
# 体験用プロジェクトに移動
cd trials/07_slices
# VSCodeでファイルを開く
code src/main.rs
# 動作確認
cargo run=== 第07回:スライスを学ぼう! ===
と表示されれば成功。
文字列スライス
文字列の一部分を取り出したいとき、&文字列[開始..終了]という書き方を使う。
fn main() {
let s = String::from("Hello, Rust!");
let hello = &s[0..5];
let rust = &s[7..11];
println!("{}", hello);
println!("{}", rust);
}
Hello
Rust&s[0..5]は「sの0バイト目から5バイト目の手前まで」を指すスライスである。前回学んだ範囲(0..5)の書き方と同じで、終了位置は含まれない。
注意:日本語などのマルチバイト文字を扱う場合
Rustの文字列スライスは、文字数ではなくバイト位置で範囲を指定する。UTF-8では、日本語などの文字は複数バイトで表現されるため、文字の途中を範囲として指定すると、実行時エラー(パニック)になることがある。
let s = "こんにちは";
let x = &s[0..1]; // panic!※日本語などの文字を扱う場合は、文字の途中で切らないように注意しよう。
ポイント:スライスは「借用」の一種である
スライスは、元の文字列データを指す参照であり、データを複製しているわけではない。そのため、スライスが存在する間は、元の文字列を変更できない(前回学んだ借用のルールがここでも働く)。
文字列全体を受け取る関数
スライスを使うと、「Stringでも文字列リテラルでも受け取れる」柔軟な関数が書ける。
fn main() {
let s1 = String::from("こんにちは Rust");
let s2 = "Hello World"; // 文字列リテラル(&str型)
println!("{}", first_word(&s1));
println!("{}", first_word(s2));
}
fn first_word(text: &str) -> &str {
let bytes = text.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &text[0..i];
}
}
text
}
こんにちは
Hello実は、これまで何度も登場してきた&strという型は、「文字列スライス」の正式な型名である。関数のパラメータを&str型にしておくと、Stringからも文字列リテラルからも、どちらからでも呼び出せる。
配列スライス
スライスは、文字列だけでなく配列にも使える。
fn main() {
let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4];
println!("{:?}", middle);
println!("合計: {}", sum(middle));
}
fn sum(nums: &[i32]) -> i32 {
let mut total = 0;
for n in nums {
total += n;
}
total
}
[20, 30, 40]
合計: 90&[i32]が、整数のスライスを表す型である。sum関数は配列の一部分だけでも、配列全体(&numbers[..]または単に&numbers)でも受け取れる。
練習問題
配列スライスを受け取り、その中の最大値を返すfind_max関数を作ってみよう。
解答例
fn main() {
let numbers = [3, 7, 2, 9, 4];
println!("最大値: {}", find_max(&numbers));
println!("一部分の最大値: {}", find_max(&numbers[0..3]));
}
fn find_max(nums: &[i32]) -> i32 {
let mut max = nums[0];
for &n in nums {
if n > max {
max = n;
}
}
max
}
配列全体でも、一部分のスライスでも、同じ関数で扱えることが確認できる。
保存しよう
cd /workspaces/rust-practice
git add .
git commit -m "Complete lesson 7: slices"
git push所有権シリーズのまとめ
3回にわたり、Rustの核心である所有権を学んだ。
- 第5回:値には必ず1つの所有者がいる。代入や関数呼び出しで、所有権はムーブする
- 第6回:複製したいならクローン、所有権を移さず貸したいなら借用(
&、&mut) - 第7回:一部分だけを借用するのがスライス。
&strはその代表例
これらのルールにより、Rustはガベージコレクションなしに、メモリの二重解放や不正アクセスをコンパイル時に防いでいる。CやPythonと見比べながら、挙動の違いを意識すると理解が深まるはずである。
(参考)筆者のGitHubリポジトリ
次回予告
次回からは、複数のデータをまとめて扱う構造体(struct)について学んでいく予定である。


コメント