Chapter 20

様々なループ

break 式 / continue

break

for 式のブロック中で break; と書くと,そこで即座にループが終了します.

fn main() {
    let array = [2, 3, 0, 4, 5];
    for &i in &array {
        if i == 0 {
            break;
        }
        print!("{}, ", i);
    }
    println!();
}

このプログラムを実行すると,次のような流れになります.

  1. 最初のループでは i の値が 2 です.
    1. if 式のブロックは実行されません.
    2. print! マクロが 2, と出力します.
  2. 次のループでは i の値が 3 です.
    1. if 式のブロックは実行されません.
    2. print! マクロが 3, と出力します.
  3. 次のループでは i の値が 0 です.
    1. if 式のブロックが実行されます.ブロックの中身が break; なので, for 式のループが即座に終了します.
  4. ループ後の println!(); が実行され,改行が出力されます.

よって全体の出力は次のようになります.

標準出力
2, 3, 

continue

for 式のブロック中で continue; と書くと,そこで即座に次のループに移ります.

fn main() {
    let array = [2, 3, 0, 4, 5];
    for &i in &array {
        if i == 0 {
            continue;
        }
        print!("{}, ", i);
    }
    println!();
}

このプログラムを実行すると,次のような流れになります.

  1. 最初のループでは i の値が 2 です.
    1. if 式のブロックは実行されません.
    2. print! マクロが 2, と出力します.
  2. 次のループでは i の値が 3 です.
    1. if 式のブロックは実行されません.
    2. print! マクロが 3, と出力します.
  3. 次のループでは i の値が 0 です.
    1. if 式のブロックが実行されます.ブロックの中身が continue; なので,次の print! マクロは実行されず即座に次のループに移ります.
  4. 次のループでは i の値が 4 です.
    1. if 式のブロックは実行されません.
    2. print! マクロが 4, と出力します.
  5. 次のループでは i の値が 5 です.
    1. if 式のブロックは実行されません.
    2. print! マクロが 5, と出力します.
  6. ループ後の println!(); が実行され,改行が出力されます.

よって全体の出力は次のようになります.

標準出力
2, 3, 4, 5, 

.. 演算子

for 式で in の後に書けるものとして,今までに「配列への参照」「ベクタへの参照」の 2 つが登場しました.ここではさらにもう一つ紹介します.

次のコードを実行してみてください.

fn main() {
    for i in 0..5 {
        println!("{}", i);
    }
}

出力は

標準出力
0
1
2
3
4

となります.

in の後に 0..5 と書くと, for 式のブロックは i01234 のときそれぞれについて実行されます.

a..b の形式で, a 以上 b 未満の範囲を表します. ab の型は同じでなければなりません.

ab の型がともに i32 であれば, i の型も同じ i32 になります.参照型 &i32 ではないことに注意してください.

一方, ....= に変えて a..=b とすると, a 以上 b 以下になります.

fn main() {
    for i in 0..=5 {
        println!("{}", i);
    }
}
標準出力
0
1
2
3
4
5

また,上限を書かずに a.. と書くと,内部で break 式が実行されない限り永遠にループし続けます.

fn main() {
    for i in 3.. {
        println!("{}", i);
        if i * i > 30 {
            break;
        }
    }
}

なんと出力されるか予想してください.実際に実行して,予想が合っていたか確かめてみてください.

a..b において a \geq b のときと, a..=b において a > b のときは,ブロックが一度も実行されません.

loop

for 式の代わりに loopを使うと, break; 式が実行されない限りブロックが繰り返し実行され続けます.

use proconio::input;

fn main() {
    loop {
        input! {
            x: i32,
        }
        if x > 0 {
            println!("{}", x * 2);
        } else {
            break;
        }
    }
}

入力を繰り返し読みこんで,正なら 2 倍して出力し,負であるようなものを読み込んだ時点で終了します.たとえば入力として

標準入力
3 10 6 -5 2 7

を与えると

標準出力
6
20
12

と出力されます.

値を返す loop

loop 式では, break の直後に式を書くと値を返すことができます.

use proconio::input;

fn main() {
    let value = loop {
        input! {
            x: i32,
        }
        if x > 0 {
            println!("{}", x * 2);
        } else {
            break x; // x の値を返す
        }
    }; // セミコロンが必要
    println!("value: {}", value);
}

先ほどと同様に,負であるようなものを読み込んだ時点で終了しますが,そのときに x の値を返しています.これが value に代入され,最後に println! マクロで出力されています.

よって入力が

標準入力
3 10 6 -5 2 7

であれば

標準出力
6
20
12
value: -5

と出力されます.

while

whileは,ある条件が成り立っている間ずっとループします.

fn main() {
    let mut x = 15;
    let mut v = Vec::new();
    while x > 0 {
        v.push(x);
        x /= 2;
    }
    assert_eq!(x, 0);
    assert_eq!(v, vec![15, 7, 3, 1]);
}
  1. x15v に空のベクタが代入されます.
  2. x = 15 より x > 0 が成り立つので,ブロックが実行されます.
    1. v15 が追加され, vec![15] になります.
    2. x2 で割られ, 7 になります.
  3. x = 7 より x > 0 が成り立つので,ブロックが実行されます.
    1. v7 が追加され, vec![15, 7] になります.
    2. x2 で割られ, 3 になります.
  4. x = 3 より x > 0 が成り立つので,ブロックが実行されます.
    1. v3 が追加され, vec![15, 7, 3] になります.
    2. x2 で割られ, 1 になります.
  5. x = 1 より x > 0 が成り立つので,ブロックが実行されます.
    1. v1 が追加され, vec![15, 7, 3, 1] になります.
    2. x2 で割られ, 0 になります.
  6. x = 0 より x > 0 が成り立たないので,ブロックが実行されずにループが終了します.
  7. x の値は 0v の値は vec![15, 7, 3, 1] なのでアサートが成功します.

2 重ループとラベル

2 重ループ

for 式のブロック中に,さらに for 式を書くことができます.

fn main() {
    for i in 0..4 {
        for j in 0..i {
            print!("({}, {}) ", i, j);
        }
        println!();
    }
}
  1. 外側のループ 1 周目: i = 0
    1. 範囲 0..0 は空なので,内側のループは一度も実行されません.
    2. 改行が出力されます.
  2. 外側のループ 2 周目: i = 1
    1. 0..1 の中の各 j について内側のループが実行されます.
    2. 内側のループ 1 周目: j = 0
      1. (1, 0) と出力されます.
    3. 改行が出力されます.
  3. 外側のループ 3 周目: i = 2
    1. 0..2 の中の各 j について内側のループが実行されます.
    2. 内側のループ 1 周目: j = 0
      1. (2, 0) と出力されます.
    3. 内側のループ 2 周目: j = 1
      1. (2, 1) と出力されます.
    4. 改行が出力されます.
  4. 外側のループ 4 周目: i = 3
    1. 0..3 の中の各 j について内側のループが実行されます.
    2. 内側のループ 1 周目: j = 0
      1. (3, 0) と出力されます.
    3. 内側のループ 2 周目: j = 1
      1. (3, 1) と出力されます.
    4. 内側のループ 3 周目: j = 2
      1. (3, 2) と出力されます.
    5. 改行が出力されます.

よって実行すると次のように出力されます.

標準出力

(1, 0) 
(2, 0) (2, 1) 
(3, 0) (3, 1) (3, 2) 

0..02..1 のような空の範囲だと, for 式のブロックは一度も実行されません.よって一行目は空のまま改行だけが行われます.

break

次は,このコードに break; を含む if 式を追加して

fn main() {
    for i in 0..4 {
        for j in 0..i {
            if i * j >= 2 {
                break;
            }
            print!("({}, {}) ", i, j);
        }
        println!();
    }
}

としてみます.出力はどうなるでしょうか?

break; によって終了するのは,一番内側のループただ一つです.

よって,出力は次のようになります.

標準出力

(1, 0) 
(2, 0) 
(3, 0) 

i = 2, j = 1 のときに ij \geq 2 となるため, (2, 1) が出力される前に内側のループが終了しますが,外側のループは続いているため i = 3 の場合が実行されます.

ラベル

一方, break; によって外側のループを終了し,出力が

標準出力

(1, 0) 
(2, 0) 

となるようにしたいときはどうすれば良いでしょうか.

このようなときは,ループにラベルを付けます.

'outer: for i in 0..4 {

アポストロフィ ' で始まっている部分 'outer がラベルです.このように書くと, for 式に 'outer という名前を付けることができます.ラベルは先頭に ' が付いていなければなりません.また,ラベルと for の間にコロン : を書くのを忘れてはいけません.

こうして付けた名前は, break 式や continue 式で使うことができます.

fn main() {
    'outer: for i in 0..4 {
        for j in 0..i {
            if i * j >= 2 {
                println!();
                break 'outer;
            }
            print!("({}, {}) ", i, j);
        }
        println!();
    }
}

break 'outer; によって終了するのは,ラベル 'outer が付いた外側の for 式です.よって出力

標準出力

(1, 0) 
(2, 0) 

が得られます.

値を返すラベル付き loop

ラベル付き break でさらに値を返すには,

break ラベル 式;

とします.次は「入力を受け取って,素数ならまた次の入力を受け取り,合成数なら最小の素因数を出力して終了する」プログラムです.

use proconio::input;

fn main() {
    let factor = 'input: loop {
        input! {
            x: i32,
        }
        for i in 2.. {
            // 2 以上の各整数について,
            // x を割り切るか順に調べる
            if i * i > x {
                // i が x の平方根より大きくなったら
                // それ以上調べても意味がない
                break;
            } else if x % i == 0 {
                // i は x を割り切る最小の整数
                break 'input i;
            }
        }
    };
    println!("{}", factor);
}

14 行目の break; では内側のループ, 17 行目の break 'input i; では外側のループが終了します.

たとえば入力が

7 11 5 9 13 8

なら,このうち 7 11 5 9 までを読み込んで 3 と出力します.

練習問題

分からなかったら解答例を見てください.説明できるところはコメントで説明しています.