Rust入門(7)「スライス」

スポンサーリンク
Rust

前回は、値を貸し借りする借用について学んだ。今回は、文字列や配列の「一部分」だけを借用するスライスについて学ぶ。これで、所有権シリーズ(第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リポジトリ

GitHub – Rocky-Seven/rust-practice: Rustの学習用リポジトリ
Rustの学習用リポジトリ. Contribute to Rocky-Seven/rust-practice development by creating an account on GitHub.

次回予告

次回からは、複数のデータをまとめて扱う構造体(struct)について学んでいく予定である。

コメント

タイトルとURLをコピーしました