Rustで整数値がオーバーフローした場合の挙動をまとめてみます。
オーバーフローの起きる式
オーバーフローを起こす以下のようなコードを書いて、Rust Playgroundに貼り付けて実行してみます。
fn main() {
let a: u8 = 200;
let b: u8 = 100;
let c: u8 = a + b;
println!("{}", c);
}
これを実行すると以下のような結果になります。
thread 'main' panicked at 'attempt to add with overflow', src/main.rs:5:17
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
というわけで、Rustは整数オーバーフローを検知し、オーバーフロー発生時にはパニックを起こします。
Releaseモードではどうでしょうか。
44
Releaseモードだとオーバーフローを検知せず、無視した結果が返ってきました。パフォーマンス上の問題から、Releaseモードではオーバーフローを検知するコードは取り除かれるようです。
C言語などは符号付き整数のオーバーフローは未定義動作になりますが、Rustでは未定義動作にはなりません。そのため、Releaseモードでオーバーフローが起きた途端に全て壊れる、ということはありません。もちろん、捕捉されないオーバーフローは起こさないようにすべきなので、オーバーフローが起き得る箇所では以下に述べるようにプログラマが明示的に制御すべきでしょう。
Rustでの整数オーバーフローの扱いは、Myths and Legends about Integer Overflow in Rustという記事が参考になります。
オーバーフローを制御する
整数の操作で起こるオーバーフローは、Debugモードではチェックされ、Releaseモードでは無視されます。それでは、プログラマが明示的にオーバーフローをチェックしたり、無視するにはどうすればよいのでしょうか。そのために、Rustでは整数型にいくつかのメソッドが用意されています。
wrapping
wrappingで始まるメソッドは、オーバーフローがおきても溢れた桁を無視した結果を返します。
assert_eq!(250u8.wrapping_add(10), 4);
assert_eq!(4u8.wrapping_sub(10), 250);
wrapping_add
で足し算、wrapping_sub
で引き算になります。他の四則演算や余り等の計算にもメソッドが用意されています。
checked
checkedで始まるメソッドは、オーバーフローが起きた場合にNoneを返します。
assert_eq!(200u8.checked_add(50), Some(250));
assert_eq!(200u8.checked_add(60), None);
wrapping系のメソッドと同様、四則演算やその他の演算のためのメソッドが用意されています。以下同様です。
overflowing
overflowingで始まるメソッドは、計算結果とその演算でオーバーフローが起こったかどうかのbool値のタプルを返します。計算結果はwrapping系のメソッドと同じになります。
assert_eq!(200u8.overflowing_add(50), (250, false));
assert_eq!(200u8.overflowing_add(60), (4, true));
saturating
saturatingで始まるメソッドは、オーバーフローが起こるような場合に、計算結果がその型の最大値もしくは最小値で飽和します。
assert_eq!(200u8.saturating_add(50), 250);
assert_eq!(200u8.saturating_add(60), 255);
assert_eq!((-100i8).saturating_sub(50), -128);
Wrapping
std::num::Wrapping
を使って整数をラップすると、+-*/
といった演算子がwrapping系の演算に置き換わります。
use std::num::Wrapping;
assert_eq!(Wrapping(250u8) + Wrapping(10u8), Wrapping(4u8));
assert_eq!(Wrapping(4u8) - Wrapping(10u8), Wrapping(250u8));
まとめ
整数オーバーフローはバグの温床になりやすいところで、それに対するRustのアプローチは、オーバーフローが予想されるところではプログラマが明示的に処理し、その他の場所ではDebugモードでできるだけ捕捉するというものです。Pythonのように整数オーバーフローが無い言語もありますが、Rustはパフォーマンスも重視する言語ですのである程度プログラマが面倒を見てやる必要があります。ただ、Cなどだとアドレスの計算中に整数オーバーフローが起きるとメモリ関連のバグの原因になるところを、Rustではunsafe
なことをしない限り整数オーバーフローが起きてもメモリ安全性が保たれるのはRustらしいところです。