Rustの算術演算子と式を完全マスター!初心者向けに使い方と優先順位を徹底解説
生徒
「Rustでプログラミングを始めたいんですが、まずは計算のやり方から知りたいです。他の言語と同じようにプラスやマイナスを使えばいいんですか?」
先生
「基本的にはその通りです!Rustでも算術演算子を使って、足し算や引き算、掛け算、割り算が簡単にできますよ。ただ、Rustは型に厳しい言語なので、いくつか注意点があります。」
生徒
「型に厳しい…?例えば、整数と小数で計算しようとするとどうなるんですか?」
先生
「そこが大事なポイントです。Rustでは異なるデータ型同士を直接計算することはできません。エラーを防ぐための安全な仕組みなんです。今回は、演算子の種類から型変換、計算の優先順位までじっくり解説しますね!」
1. Rustで使える基本的な算術演算子の種類
Rustには、数学の計算を行うための標準的な算術演算子が揃っています。プログラミングの基本となるこれらを使って、数値を操作する方法を学びましょう。Rustで主に使用される演算子は以下の5種類です。
- 足し算(+):二つの値を加算します。
- 引き算(-):左の値から右の値を減算します。
- 掛け算(*):二つの値を乗算します。
- 割り算(/):左の値を右の値で除算します。
- 剰余(%):割り算をしたときの「余り」を算出します。
これらの演算子は、整数型(i32やu64など)だけでなく、浮動小数点型(f32やf64)でも使用可能です。ただし、Rustの大きな特徴として、「同じ型同士でしか計算できない」というルールがあります。例えば、32ビットの整数と64ビットの整数を足そうとすると、コンパイルエラーが発生します。これにより、予期せぬ精度の損失やバグを未然に防いでいるのです。
まずは、最もシンプルな計算の例を見てみましょう。変数を使って数値を定義し、それらを計算して結果を表示するプログラムです。
fn main() {
let a = 10;
let b = 3;
let sum = a + b; // 足し算: 13
let difference = a - b; // 引き算: 7
let product = a * b; // 掛け算: 30
let quotient = a / b; // 割り算: 3 (整数の場合は切り捨て)
let remainder = a % b; // 剰余: 1
println!("和: {}, 差: {}, 積: {}, 商: {}, 余り: {}", sum, difference, product, quotient, remainder);
}
和: 13, 差: 7, 積: 30, 商: 3, 余り: 1
2. 整数の割り算と余りの計算における注意点
Rustでの計算において、初心者が最初につまずきやすいのが整数の割り算です。上記のサンプルコードでも見たように、整数同士で割り算を行うと、結果も整数になります。つまり、小数点以下は自動的に切り捨てられます。これを「整数除算」と呼びます。
例えば、10 / 3の結果は3.333...ではなく3になります。もし正確な小数点以下の値が必要な場合は、数値を浮動小数点型として定義する必要があります。また、剰余演算子(%)は、特定の数値が偶数か奇数かを判定したり、ループ処理で一定の間隔ごとに処理を行いたい場合に非常に便利です。
さらに、Rustでは「ゼロ除算(0で割ること)」に対して非常に厳格です。コンパイル時に判明する場合はエラーになりますし、実行時にゼロで割ろうとするとプログラムがパニック(強制終了)を起こします。これは、安全性を重視するRustならではの仕様であり、開発者が事前に異常な事態を想定することを促しています。
3. 複合代入演算子でコードを簡潔に書く方法
プログラミングでは、変数自身の値を更新する場面が多々あります。例えば、「スコアに10点加算する」といった処理です。このとき、score = score + 10;と書くこともできますが、Rustにはもっと短く書ける複合代入演算子が用意されています。
複合代入演算子には、+=、-=、*=、/=、%=などがあります。これらを使うことで、コードの可読性が高まり、タイピングミスも減らすことができます。ただし、注意点として、値を更新する変数は必ず可変(mut)として宣言されている必要があります。Rustの変数はデフォルトで不変(immutable)なので、書き換えを許可するキーワードを忘れないようにしましょう。
以下のコードで、実際に変数の値を更新していく様子を確認してみましょう。スコア計算やカウントアップの処理でよく使われるパターンです。
fn main() {
let mut score = 100;
println!("初期スコア: {}", score);
score += 50; // 150に加算
score -= 20; // 130に減算
score *= 2; // 260に倍増
score /= 4; // 65に除算
println!("最終スコア: {}", score);
}
初期スコア: 100
最終スコア: 65
4. Rustの比較演算子と論理式による条件分岐
算術演算子と並んで重要なのが、二つの値を比較するための比較演算子です。これらは計算結果として「真(true)」か「偽(false)」という論理値を返します。条件分岐(if文)などと組み合わせて、プログラムの動きを制御するために不可欠な要素です。
- 等価(==):二つの値が等しいか。
- 不等(!=):二つの値が異なるか。
- より大きい(>)、以上(>=):大きさの比較。
- より小さい(<)、以下(<=):大きさの比較。
また、複数の条件を組み合わせるための論理演算子も存在します。&&(かつ/AND)、||(または/OR)、!(否定/NOT)を使いこなすことで、複雑な条件式を組み立てることが可能です。Rustは論理値に関しても厳格で、他の言語のように「数値の0をfalseとみなす」といった曖昧な解釈は行いません。必ずbool型(true/false)を期待する場所には、比較の結果を渡す必要があります。
5. 型変換(キャスト)が必要な計算のケース
最初にお話しした通り、Rustは異なる型同士の計算を許しません。しかし、現実の開発では「整数を小数で割りたい」とか「小さな整数を大きな整数の器に移したい」という場面があります。その際に使用するのが、asキーワードによる型変換(キャスト)です。
型変換を行うことで、一時的にデータの型を変更し、計算を可能にします。ただし、浮動小数点型から整数型に変換すると小数点以下が切り捨てられたり、大きな型から小さな型へ変換すると値が溢れて予期しない結果になったり(オーバーフロー)することもあるため、慎重に行う必要があります。
具体的な型変換を用いた計算例を見てみましょう。ここでは整数を浮動小数点に変換して、精度の高い平均値を求めています。
fn main() {
let total_points = 500;
let items_count = 7;
// 整数同士の割り算(結果は整数)
let integer_avg = total_points / items_count;
// floatにキャストして計算(結果は小数)
let float_avg = total_points as f64 / items_count as f64;
println!("整数での平均: {}", integer_avg);
println!("小数での平均: {:.2}", float_avg);
}
整数での平均: 71
小数での平均: 71.43
6. 演算子の優先順位と括弧を使った制御
数学の授業で習った通り、Rustの演算子にも優先順位があります。例えば、足し算よりも掛け算の方が先に計算されます。このルールを正しく理解していないと、意図しない計算結果になってしまうことがあります。基本的な順位は以下の通りです。
- 単項演算子(
-x、!xなど) - 乗除演算(
*、/、%) - 加減演算(
+、-) - 比較演算(
==、!=、<など) - 論理演算(
&&、||)
優先順位を無理に暗記する必要はありません。最も確実で、かつ他の人がコードを読んだときに分かりやすくする方法は、括弧( )を使うことです。括弧で囲まれた部分は最優先で計算されます。複雑な計算式を書くときは、意図を明確にするために積極的に括弧を活用しましょう。
7. Rust特有の「式」と「文」の違いについて
Rustを学ぶ上で避けて通れないのが、「式(Expression)」と「文(Statement)」の違いです。算術演算の多くは「式」であり、値を返します。Rustでは関数の最後にセミコロンを付けずに式を書くと、それが戻り値として扱われるというユニークな特徴があります。
「文」は、行動を指示するもので値を返しません。例えば、let x = 5;は変数宣言の文であり、それ自体が値を持つわけではありません。一方で、x + 1は式であり、計算結果の値を生成します。この考え方は、条件分岐のifやmatchを式として扱い、その結果を変数に直接代入できるRustの柔軟な設計につながっています。
最後に、ifを「式」として使った、Rustらしい計算の記述方法を見てみましょう。条件によって計算式を切り替え、その結果を直接変数に格納する例です。
fn main() {
let is_sale = true;
let base_price = 1000.0;
// ifは式なので、計算結果を変数に代入できる
let final_price = if is_sale {
base_price * 0.8 // 20%オフ
} else {
base_price
};
println!("最終価格は {} 円です。", final_price);
}
最終価格は 800 円です。
8. オーバーフローの挙動と安全な開発
Rustの算術演算において、高度な話題ですが知っておくべきなのが整数オーバーフローです。例えば、8ビットの整数型(u8)が保持できる最大値は255ですが、これに1を足すとどうなるでしょうか?
Rustでは、デバッグモードで実行している場合は、オーバーフローが発生した瞬間にパニック(エラー停止)を起こして教えてくれます。これは、意図しない数値の回り込みによるバグを防ぐためです。リリースモード(最適化あり)では、エラーにはならず、値が最小値に戻る「ラップアラウンド」という挙動をしますが、基本的にはオーバーフローが発生しないように設計するのがRustの作法です。安全な計算を行うために、扱うデータの範囲を予測して適切な型(i32やi64など)を選ぶことが、信頼性の高いシステム開発への第一歩となります。