Chapter 10

アサート

assert! マクロ

アサートは,プログラムの異常に気付きやすいようにするための手段です.次のコードを見てください.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let r = x % 10;
    assert!(0 <= r && r < 10);
    println!("あまりは {}", r);
}

input! マクロで整数 x を受け取り, 10 で割った余りを変数 r に代入しています.このとき, 0 \leq r < 10 は成り立つでしょうか.これをチェックするのが, assert! マクロです.

assert!( ) の括弧内に条件を書き, assert!(0 <= r && r < 10); とします.

  • ここでもし 0 <= r && r < 10 が成り立っていれば,何事もなかったかのようにプログラムの続きが実行されます.
  • 一方,もし 0 <= r && r < 10 が成り立っていなければ,プログラムはこの時点で実行時エラーとなり,続きは実行されませんpanic! マクロと同じ).

まず,このプログラムに標準入力として 123 を与えて実行してみましょう. x = 123 なので r = 3 となり, 0 \leq r < 10 が成り立つはずです.やってみると あまりは 3 と出力され,ちゃんと assert! の続きが実行されていることが分かります.

次に,標準入力を -123 に変えてみましょう.おや?標準出力に何も出力されません(println! が実行されていません).代わりに標準エラー出力に

thread 'main' panicked at 'assertion failed: 0 <= r && r < 10', src/main.rs:8:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

と出力され,終了コードが 0 以外の値になっています.これが, assert! で括弧内の条件が成り立っていなかったときの挙動です.

つまり,このプログラムでは x = -123 のときに 0 \leq r < 10 が成り立たなくなることが分かります.

実は,/ / % 演算子を使うときは,負の場合の挙動に注意する必要があります. xy の正負を変えながら x / yx % y を計算すると次のようになります.

x y x / y x % y
123 10 12 3
-123 10 -12 -3
123 -10 -12 3
-123 -10 12 -3

x < 0 のときの結果が,「(-x) \div y の結果の符号を反転させたもの」になっています.

これを避け, (-123) \div 10 = -13 \cdots 7 という割り算を行うには,代わりに x.div_euclid(y) / x.rem_euclid(y) と書きます.

use proconio::input;

fn main() {
    input! {
        x: i32,
    }
    let r = x.rem_euclid(10);
    assert!(0 <= r && r < 10);
    println!("あまりは {}", r);
}
x y x.div_euclid(y) x.rem_euclid(y)
123 10 12 3
-123 10 -13 7
123 -10 -12 3
-123 -10 13 7

今回は println! で余りを出力するだけでしたが,もしこの前後に膨大な処理が書かれていた場合,どこに異常があるのか特定するのが困難になります.アサートを使うことで,どこで異常が発生しているのか気付きやすくなります.

メッセージ

assert!(0 <= r && r < 10);

の代わりに

assert!(0 <= r && r < 10, "割った余りが想定の範囲を超えています");

と書くと,失敗したときの文面を

thread 'main' panicked at '割った余りが想定の範囲を超えています', src/main.rs:8:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

のように変えられます. println! マクロと同じようにプレースホルダー {} を置いて

assert!(0 <= r && r < 10, "割った余りが {} で,想定の範囲を超えています", r);

と書くこともできます.

assert_eq! / assert_ne! マクロ

assert! マクロの中で == を使って assert!(a == b) のように書くときは,代わりに assert_eq! マクロを使って assert_eq!(a, b) と書くこともできます.

use proconio::input;

fn main() {
    input! {
        x: i32,
        y: i32
    }
    let rounded = x / y * y;
    assert_eq!(rounded % y, 0); // rounded % y == 0 をチェック
}

同様に, assert!(a != b) の代わりに assert_ne!(a, b) と書くことができます.

assert_eq! / assert_ne! の場合も, assert_eq!(a, b, "メッセージ"); のようにして失敗時のメッセージを追加することができます.