Rustの演算子と式を徹底解説!式ベース言語の仕組みと初心者向け基本ガイド
生徒
「Rustのコードを読んでいると、セミコロンがあったりなかったり、他の言語と少し書き方が違う気がするんです。演算子や計算のルールに特徴はありますか?」
先生
「鋭いですね!Rustは『式ベースの言語』と呼ばれていて、ほとんどの構文が値を返す『式』として扱われます。演算子の使い方は他の言語と似ていますが、この『式』の考え方を理解するのが上達の近道ですよ。」
生徒
「式ベース……。つまり、計算だけじゃなくて、if文とかも値を返したりするんですか?」
先生
「その通りです!算術演算子や比較演算子といった基本的な道具を使いこなしながら、Rust特有の『式』の書き方を覚えると、コードが驚くほどスッキリ書けるようになります。まずは基本的な演算子から順番に見ていきましょう!」
1. Rustの演算子とは?基本の算術演算を学ぼう
Rustにおける演算子とは、値に対して特定の計算や操作を行うための記号のことです。プログラミングの基本となる「四則演算」は、他の多くの言語と同じように直感的に記述することができます。Rustでは、整数や浮動小数点数といったデータ型に対してこれらの演算子が用意されています。
代表的な算術演算子には、足し算(+)、引き算(-)、掛け算(*)、割り算(/)、そして余りを求める剰余(%)があります。初心者が最初に意識すべき点は、Rustが「型」に対して非常に厳格であるということです。例えば、整数型と浮動小数点数型を直接計算しようとすると、コンパイルエラーになります。これは、予期せぬ計算ミスを防ぐためのRustの優しさでもあります。計算を行う際は、同じデータ型同士であることを確認しましょう。
fn main() {
let sum = 10 + 5; // 足し算
let difference = 20 - 7; // 引き算
let product = 4 * 8; // 掛け算
let quotient = 21 / 4; // 割り算(整数同士なら結果も整数)
let remainder = 21 % 4; // 剰余(あまり)
println!("和: {}, 差: {}, 積: {}, 商: {}, 余り: {}", sum, difference, product, quotient, remainder);
}
和: 15, 差: 13, 積: 32, 商: 5, 余り: 1
2. 比較演算子と論理演算子で条件を作る
プログラムの中で「もし〜ならば」という条件分岐を作る際に欠かせないのが、比較演算子と論理演算子です。これらは条件式を評価し、最終的に「真(true)」か「偽(false)」のいずれかの値(論理値)を返します。
比較演算子には、等しい(==)、等しくない(!=)、より大きい(>)、より小さい(<)、以上(>=)、以下(<=)があります。これらを組み合わせることで、数値の大小比較やフラグの一致確認が行えます。また、論理演算子には、かつ(&&)、または(||)、否定(!)があり、複数の条件を複雑に組み合わせる際に威力を発揮します。Rustでは、これらの演算結果も一つの「式」として扱われ、そのまま変数に代入することも可能です。
fn main() {
let age = 20;
let has_id = true;
// 比較演算と論理演算の組み合わせ
let can_enter = age >= 18 && has_id;
if can_enter {
println!("入場可能です。");
} else {
println!("入場できません。");
}
}
入場可能です。
3. Rust最大の特徴「式ベース言語」の仕組み
Rustを学ぶ上で最も重要な概念の一つが、Rustが「式ベース(Expression-oriented)の言語」であるという点です。多くのプログラミング言語では、「文(Statement)」と「式(Expression)」が明確に分かれていますが、Rustではほとんどの記述が「式」です。
「文」とは、アクションを実行するものの値を返さない命令です。例えば、変数を宣言する let x = 5; は文です。一方で「式」とは、評価されて何らかの値を生成するものを指します。数値の 5 自体も式ですし、5 + 5 も式です。Rustでは、関数呼び出しやマクロ呼び出し、さらに後述する「ブロック({})」までもが式として扱われます。この性質により、Rustのコードは非常に表現力豊かで、かつ簡潔に記述できるようになっています。式の最後にはセミコロンを付けないというルールがありますが、これについては次のセクションで詳しく解説します。
4. セミコロンの有無で変わる!式と文の境界線
Rust初心者の方が最初によく戸惑うのが、行末のセミコロン(;)の扱いです。Rustにおいて、セミコロンは「式を文に変える」という役割を持っています。式として値を返したい場合はセミコロンを省き、単に命令を実行して値を捨てたい場合にはセミコロンを付けます。
例えば、関数の最後の一行にセミコロンを書かない場合、その行の評価結果がそのまま関数の戻り値になります。逆にセミコロンを付けてしまうと、その式は文となり、何も返さない(正確には空のタプル () を返す)ことになってしまいます。この挙動は、後述する if や match 、スコープブロックにおいても同様です。セミコロン一つでプログラムの動作が変わるため、「今、自分は値を返したいのか、それとも単に処理を終わらせたいのか」を意識することが大切です。
5. ブロック式を活用した柔軟な変数代入
Rustでは波括弧 { } で囲まれたスコープ(ブロック)も「式」になります。つまり、ブロックの中で計算を行い、その最終結果を変数に直接代入することができるのです。これを利用すると、一時的な変数を最小限に抑え、コードの可読性を高めることができます。
ブロック内の最終行にセミコロンを付けずに記述すると、その値がブロック全体の評価値となります。これは非常に強力な機能で、複雑な初期化処理を一つの変数代入の中に閉じ込めることが可能です。他の言語では、関数の外で変数を用意してから中で書き換えるといった手順が必要な場面でも、Rustならブロック式を使ってスマートに記述できます。スコープが明確になるため、メモリ管理の面でも非常に優れた設計と言えます。
fn main() {
let x = 10;
// ブロックを変数代入に使う
let y = {
let x_squared = x * x;
let x_cube = x_squared * x;
x_cube + x_squared // セミコロンなし:これがブロックの値になる
};
println!("計算結果: {}", y);
}
計算結果: 1100
6. 条件分岐も式になる!if式の便利な使い方
Rustにおける if は「文」ではなく「式」です。つまり、条件分岐の結果を直接変数に代入することができます。多くの言語に存在する「三項演算子(condition ? a : b)」がRustに存在しないのは、この if 式がその役割を完全に果たせるからです。
if 式を使って値を代入する場合、if ブロックと else ブロックが返す値の型は必ず一致していなければなりません。一方は整数、もう一方は文字列といった使い方はできません。この厳格な型チェックのおかげで、実行時に型が原因でエラーが発生することを防いでいます。初心者の方は、まず「ifは値を返すもの」という感覚を身につけると、Rustらしい美しいコードが書けるようになりますよ。
fn main() {
let condition = true;
// if式の評価結果を変数に代入
let number = if condition { 5 } else { 6 };
println!("選択された数値: {}", number);
}
選択された数値: 5
7. 代入演算子と複合代入による効率的な更新
変数の値を更新する際に便利なのが代入演算子です。Rustでは、変数を宣言する際に mut キーワードを付けることで、その値を後から変更(再代入)できるようになります。基本的な代入演算子(=)のほかに、計算と代入を同時に行う「複合代入演算子」も利用可能です。
例えば、x = x + 5 は x += 5 と書くことができます。これには +=, -=, *=, /=, %= など、主要な算術演算子に対応したものが揃っています。Rustの代入操作自体は、他の言語と異なり値を返さない(() を返す)ため、let x = (y = 5); のような連続した代入は推奨されないか、期待通りの動作にならない点に注意しましょう。あくまで変数の状態を安全かつ明示的に変更するための手段として活用されます。
8. 演算子の優先順位と読みやすいコードの書き方
複雑な数式を記述する場合、どの演算子が先に計算されるかという「優先順位」を知っておく必要があります。一般的には、掛け算や割り算は足し算や引き算よりも先に計算されます。Rustでもこの数学的なルールが踏襲されています。
例えば、2 + 3 * 4 は 20 ではなく 14 になります。もし足し算を先に優先させたい場合は、丸括弧 ( ) を使って明示的に囲む必要があります。しかし、プログラミングにおいては「正しく動くこと」と同じくらい「読みやすいこと」が重要です。優先順位を完璧に暗記していなくても、少しでも複雑だと感じたら括弧を使って意図を明確にすることをお勧めします。これにより、自分以外の開発者がコードを読んだ際の誤解を防ぎ、保守性の高いプログラムを作成することができます。
9. 型変換と演算子の密接な関係
Rustの演算子を扱う上で避けて通れないのが「型変換(キャスト)」です。先述の通り、Rustは異なる型同士の演算を許しません。例えば i32 型(32ビット整数)と f64 型(64ビット浮動小数点数)を足そうとするとエラーになります。このような場合、as キーワードを使用して明示的に型を変換する必要があります。
let result = (5 as f64) + 1.5; のように記述することで、整数を浮動小数点数に変換してから計算を行うことができます。このようにRustでは、演算子を使う場面で「今、扱っているデータの型は何なのか」を常に意識させる設計になっています。一見すると手間に感じるかもしれませんが、この徹底した型管理こそが、大規模なシステム開発においてデータが壊れたり、意図しない計算が行われたりするのを防ぐ最強の盾となるのです。
10. まとめ的な知識:演算子オーバーロードの存在
基礎を終えた後のステップアップとして知っておきたいのが、Rustにおける「演算子オーバーロード」です。Rustでは、自分で定義した構造体(独自のデータ型)に対しても、演算子の挙動を定義することができます。例えば、「ベクトル」という構造体同士を + 記号で足し合わせる、といったことが可能です。
これは、標準ライブラリで提供されている「トレイト」という仕組みを実装することで実現されます。初心者のうちは自分でこれを作る必要はありませんが、「Rustの演算子は柔軟に拡張できる設計になっているんだな」と知っておくだけでも、将来複雑なデータ構造を扱う際に役立ちます。基本の算術・比較・論理演算子、そして何より「式」としての性質をマスターすることで、Rustの真の力を引き出す準備が整います。まずは小さなコードから書いて、セミコロンの有無による挙動の違いを体感してみてください!