動機
まずは以下のコードを見てみましょう。
fn main() {
let mut num1 = 10_u8;
num1 *= 80;
println!("{}", num1);
}
num1
に関してはu8が指定されているのでnum1の最大値は255です。
しかしこのコードでは10 * 80の演算をしているので、結果は800となり、オーバーフローするでしょう。
実際に実行してみましょう。
cargo run
thread 'main' panicked at src/main.rs:3:5:
attempt to multiply with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
しっかりエラーはいてくれました。
では、これをリリースモードで実行するとどうなるでしょう。
cargo run --release
32
今度はpanicせずになぞの数字32が得られました。
実はこれは、800 % 256した結果です。
こんな感じで、Rustは整数型のオーバーフローをエラー検出できません。
しかし、実際にはこれをハンドリングする必要があると思いますので
今回はこれをまとめてみました。
saturating
まずはsaturatingです。
saturatingはその型の最大値を超える値は 最大値に張り付きます。
fn main() {
let num1 = 10_u8;
// オーバーフローしないときはそのままの結果が得られる
assert_eq!(num1.saturating_mul(10), 100);
// オーバーフローする場合は型の最大値に張り付く
assert_eq!(num1.saturating_mul(80), 255);
}
checked
checkedは結果がOption型で渡されます。
オーバーフローするときはNoneが返されます。
fn main() {
let num1 = 10_u8;
// 結果がOption型で渡される。
// オーバーフローしないときはSomeに、
assert_eq!(num1.checked_mul(10), Some(100));
// オーバーフローするときはNoneが返される
assert_eq!(num1.checked_mul(80), None);
}
wrapping
wrappingはその型の最大値 + 1で割ったあまりを返します。
cargo run --release したときのデフォルト挙動はこれなのかな?と勝手に理解していますが、
裏どりしてません。
fn main() {
let num1 = 10_u8;
// オーバーフローしないときはそのまま
assert_eq!(num1.wrapping_mul(10), 100);
// オーバーフローするときはその型の最大値で割ったあまりになる
let amari = 10_usize * 80 % 256;
assert_eq!(num1.wrapping_mul(80), amari as u8);
}
overflowing
タプルで返され、1-indexedで2番目の要素にtrue, falseでOverflowしているか否かが返されます。
fn main() {
let num1 = 10_u8;
// オーバーフローしないときは(戻り値).1にfalseが渡される
assert_eq!(num1.overflowing_mul(10), (100, false));
// オーバーフローするときは(戻り値).1にtrueが入る
let amari = 10_usize * 80 % 256;
assert_eq!(num1.overflowing_mul(80), (amari as u8, true));
}
おわりに
今回は掛け算の例で紹介したのでsuffixが_mulでしたが、足し算(_add)や引き算(_sub)等の演算にもちゃんと対応しています。
ここをちゃんとハンドリングしてないと、「なにもしていないのに壊れた」になりかねないので注意しておくと後々役立つことがあるかもです。