Vec でリストを構築する

RustBeginner
オンラインで実践に進む

はじめに

変数には 1 つの値を格納します。一方、多くのプログラムでは、長さを変更できる順序付きリストが必要です。Rust の Vec<T> は、要素を追加できるシーケンスです。すべての要素は型 T を持ち、値の順序が保たれ、ベクターが要素を所有します。

この実験では、カフェの注文リストを作成し、特定の位置を安全に読み取り、リストを消費せずにすべての注文を確認し、可変参照を使って格納されたすべての文字列を更新します。

ベクターを作成して要素を追加する

このステップでは、最初は空のベクターに、所有権を持つ文字列を 3 つ追加します。

用意されたプロジェクトに移動し、ソースファイルを開きます。

cd /home/labex/project/cafe-orders
nano src/main.rs

Vec<String> は「要素が所有権を持つ String 値であるベクター」を意味します。これは、Option<u32> で見たものと同じ、コンテナの型を指定する山括弧の記法です。ここでは、格納される要素の型が String です。このような再利用可能な型の定義方法については、後のジェネリクスの Lab で学びます。Vec::new() は空のベクターを作成します。要素を追加するとベクターが変更されるため、束縛には mut が必要です。

Step 1 のコメントを、次の 3 つの push 呼び出しに置き換えます。

    orders.push(String::from("Latte"));
    orders.push(String::from("Tea"));
    orders.push(String::from("Cake"));

push は要素を末尾に追加し、追加した順序を維持します。Ctrl+O で保存し、Enter キーを押してから、Ctrl+X で終了します。続けて、チェックと実行を行います。

cargo check
cargo run --quiet

出力は次のようになります。

Order count: 3
Orders: ["Latte", "Tea", "Cake"]

len() は要素数として 3 を返します。:? プレースホルダーは Rust のデバッグ形式を使用して、文字列を囲む引用符も含め、ベクター全体を表示します。

位置を安全に読み取る

このステップでは、範囲外アクセスによるパニックを発生させずに、ベクターの 2 番目の位置を読み取ります。

ベクターの位置は 0 から始まるため、インデックス 1 は 2 番目の注文を示します。get メソッドは Option<&String> を返します。指定した位置が存在する場合、Some に共有参照が格納され、位置が存在しない場合は None になります。この参照を使うと、ベクターが要素の所有者であり続けたまま、要素を読み取れます。

ソースファイルを開きます。

nano src/main.rs

Step 2 のコメントを、次のコードに置き換えます。

    match orders.get(1) {
        Some(order) => println!("Second order: {order}"),
        None => println!("Second order: missing"),
    }

このコードは、ベクターから String を取り出して所有権を移動させずに、要素を読み取ります。保存して終了したら、次のコマンドを実行します。

cargo run --quiet

出力の最後に、次の行が追加されます。

Second order: Tea

この行から、位置 1 が存在し、追加した順序も維持されていることを確認できます。

共有参照を使って反復処理する

このステップでは、後でベクターを使用できる状態を保ったまま、すべての注文を順番に確認します。

&orders と書くと、ベクターを for ループに移動する代わりに借用できます。そのため、ループ内の各値は、格納された 1 つの String への共有参照になります。

ソースファイルを開きます。

nano src/main.rs

Step 3 のコメントを、次のループと後続の確認コードに置き換えます。

    for order in &orders {
        println!("Queued: {order}");
    }
    println!("Stored after display: {}", orders.len());

保存して終了したら、次のコマンドを実行します。

cargo run --quiet

追加された行は次のようになります。

Queued: Latte
Queued: Tea
Queued: Cake
Stored after display: 3

ループがベクターを借用し、各共有参照を次の反復に進む前に返しているため、ループの後でも最後の要素数を使用できます。

可変参照を使って更新する

このステップでは、各ベクター要素を可変で借用し、表示ループが実行される前に、その場で更新します。

1 つの値に対して、同時に有効な可変参照は 1 つだけです。&mut orders に対する for ループは、1 回の反復で 1 つの要素への可変参照を取り出し、その反復で使用してから、次の要素へ進むことで、このルールを守ります。

ソースファイルを開きます。

nano src/main.rs

Step 4 のコメントを、次のコードに置き換えます。

    for order in &mut orders {
        order.push_str(" - ready");
    }

push_str は、借用したテキストを所有権を持つ各 String の末尾に追加します。Rust はこのメソッド呼び出しで、各参照の背後にある可変な String を自動的に使用します。

保存して終了したら、チェックと実行を行います。

cargo check
cargo run --quiet

完全な出力は次のようになります。

Order count: 3
Orders: ["Latte", "Tea", "Cake"]
Second order: Tea
Queued: Latte - ready
Queued: Tea - ready
Queued: Cake - ready
Stored after display: 3

更新されたキューの行から、すべての要素がその場で変更されたことを確認できます。最後の要素数から、ベクターが同じ 3 つの文字列を引き続き所有していることを確認できます。

まとめ

Vec<String> を作成して要素を追加し、get を使って位置を安全に読み取り、ベクターを消費せずに反復処理し、可変参照を通してすべての要素を更新しました。