Rustの定数ってなんだろう? プログラミングを始めたばかりだと、変数(let)との違いがよく分からないかもしれませんね。
この記事では、Rustのプログラムで変わらない値を扱うconst(定数)について、基本からしっかり解説していきます。
この記事を読むとわかること
- Rustの定数(const)の基本的な意味
- 定数の正しい書き方と命名ルール
- 実際のコードでの定数の使い方
- 変数(let)との決定的な違い
- 静的変数(static)とのちょっと難しい違い
- 定数を使うときの注意点
Rustの定数(const)とは?プログラムで変わらない値を扱う基本
プログラムを書いていると、円周率(3.14159...)や、ゲームの最大レベル、消費税率みたいに、絶対に変わってほしくない値が出てきますよね。
そんな、プログラムの実行中にずっと変わらない値を扱うための仕組みが「定数(const)」です。
なぜ定数を使うのでしょうか?
一つは、コードが読みやすくなるからです。「MAX_PLAYERS」という名前が付いていれば、「ああ、プレイヤーの最大人数なんだな」とすぐに意味が分かります。
もう一つは、安全のためです。間違って値を書き換えてしまう心配がありません。
Rustでは、定数を定義するために const
というキーワードを使います。いわば、プログラムの中での「変わらないお約束」を宣言する感じです。
Rustの定数の基本的な書き方
定数の宣言は、変数(let)とは少しだけルールが違います。
基本の形は const 定数名: 型 = 値;
です。ポイントは、必ず型を指定することと、値はプログラムを部品に組み立てる(コンパイルする)ときに決まっている必要があることです。
難しく考えず、まずは書き方に慣れていきましょう。
const
キーワードを使った宣言と型指定
実際にコードで見てみましょう。こんなふうに書きます。
// 符号なし8ビット整数の定数 const MAXIMUM_NUMBER: u8 = 100; // 32ビット浮動小数点数の定数 const PI: f32 = 3.14159; // ブール値(真偽値)の定数 const IS_PRODUCTION: bool = false; // 文字列リテラル(&str型)の定数 const GREETING: &str = "こんにちは!"; fn main() { println!("最大数は {}", MAXIMUM_NUMBER); println!("円周率は {}", PI); if !IS_PRODUCTION { println!("{}", GREETING); } }
コードを見てください。const
の後に定数名(大文字のスネークケース)、コロン(:)、型、イコール(=)、そして値を書いて、最後にセミコロン(;)を付けます。
変数宣言 `let` と違って、型(例: `u8`, `f32`, `bool`, `&str`)を省略できないのが大きな特徴です。面倒に思うかもしれませんが、型をはっきりさせることで、プログラムの意図が明確になるのですよ。
定数名の命名規則:わかりやすい名前をつけるルール
先ほどのコード例でも見ましたが、Rustの定数名は、すべて大文字にして、単語の間をアンダースコア(_)でつなぐのがお約束です。これを「大文字スネークケース」と呼びます。
- 良い例:
MAX_POINTS
,USER_LIMIT
,DEFAULT_TIMEOUT
- 避けるべき例:
max_points
,MaxPoints
,userLimit
なぜ全部大文字にするのかというと、パッと見て「あ、これは定数だな」と分かるようにするためです。変数(普通は小文字のスネークケース `let user_name = ...;`)と区別がつきやすいので、コードを読む人が混乱しにくくなります。
みんなが守っているルールなので、ぜひ覚えておきましょう。
Rustの定数の実践的な使い方
定数がどんな場面で活躍するのか、実際のコード例で見ていきましょう。
コードを読むだけでなく、ぜひ手元で動かしてみてくださいね。動かすと理解がぐっと深まります。
【サンプル1】計算で使う値を定数にする
例えば、商品の税込み価格を計算するプログラムを考えてみましょう。消費税率は頻繁には変わりませんが、もし変わった場合、コードのあちこちに税率が直接書かれていたら修正が大変です。
そこで、定数を使います。
// 消費税率を定数として定義 const SALES_TAX_RATE: f64 = 0.10; // 10% fn calculate_price_with_tax(price: f64) -> f64 { // 定数を使って税込み価格を計算 price * (1.0 + SALES_TAX_RATE) } fn main() { let book_price = 1500.0; let price_with_tax = calculate_price_with_tax(book_price); println!("書籍の価格(税抜き): {} 円", book_price); println!("書籍の価格(税込み): {} 円", price_with_tax); let pen_price = 120.0; let pen_price_with_tax = calculate_price_with_tax(pen_price); println!("ペンの価格(税抜き): {} 円", pen_price); println!("ペンの価格(税込み): {} 円", pen_price_with_tax); }
実行結果
書籍の価格(税抜き): 1500 円 書籍の価格(税込み): 1650 円 ペンの価格(税抜き): 120 円 ペンの価格(税込み): 132 円
このコードでは、SALES_TAX_RATE
という定数で消費税率を定義しています。
もし将来、税率が変更になったとしても、修正するのは const SALES_TAX_RATE = ...;
の1行だけで済みます。コードの保守性がぐんと上がりますね!
【サンプル2】設定値や制限値を定数にする
プログラムの設定や、処理の制限回数なども定数で管理すると便利です。
例えば、ユーザーにパスワードを何回まで間違えてもOKにするか、という制限値を考えてみましょう。
// パスワード試行の最大回数 const MAX_LOGIN_ATTEMPTS: u32 = 3; fn main() { let mut attempts = 0; loop { println!("パスワードを入力してください (試行回数 {}/{})", attempts + 1, MAX_LOGIN_ATTEMPTS); // ここでパスワード入力とチェック処理があると仮定 let password_ok = false; // 仮に失敗したとする attempts += 1; if password_ok { println!("ログイン成功!"); break; } if attempts >= MAX_LOGIN_ATTEMPTS { println!("試行回数が上限に達しました。アカウントをロックします。"); break; } } }
実行結果(パスワードが常に違う場合)
パスワードを入力してください (試行回数 1/3) パスワードを入力してください (試行回数 2/3) パスワードを入力してください (試行回数 3/3) 試行回数が上限に達しました。アカウントをロックします。
MAX_LOGIN_ATTEMPTS
という定数名で最大試行回数が定義されているので、コードを読んだときに「3」という数字が何を表しているのか一目瞭然です。
コードの中に直接「3」と書く(マジックナンバーと呼ばれる)よりも、意味が分かりやすく、後から変更するのも簡単になります。
ここが重要!Rustの定数(const)と変数(let)の決定的な違い
さて、ここで一度、普段よく使う変数(let
で宣言するもの)と定数(const
)の違いを整理しておきましょう。
似ているようで、実は結構違います。
- 値の変更
let
: 基本的に不変(変更不可)ですが、let mut
とすれば可変(変更可能)にできます。const
: 常に不変です。mut
を付けることはできません。
- 宣言時のキーワード
let
:let
またはlet mut
を使います。const
:const
を使います。
- 型指定
let
: 多くの場合、コンパイラが型を推測してくれるので、型指定を省略できます。const
: 必ず型を指定する必要があります。省略はできません。
- 値が決まるタイミング
let
: プログラムを実行している途中で値が決まってもOKです(関数の戻り値など)。const
: プログラムを部品に組み立てる(コンパイルする)時点で値が決まっている必要があります。
- 使える場所
let
: 基本的に関数の中で使います。const
: 関数の外(グローバルスコープ)でも中でも、どこでも宣言できます。
一番大きな違いは、やはり「値が変更できるかどうか」と「いつ値が決まるか」ですね。
絶対に変わらない値、かつプログラムを作る時点で分かっている値には const
を使う、と覚えておくと良いでしょう。
さらに理解を深める!Rustの定数(const)と静的変数(static)の違い
Rustには、定数(const
)とちょっと似たものに「静的変数(static
)」というものもあります。これもプログラム実行中にずっと存在する値を扱うのですが、const
とは性質が異なります。
const
(定数)- イメージとしては、値そのものに名前を付けている感じです。
- コードの中で
const
定数を使うと、コンパイル時にその値が直接埋め込まれる(インライン展開される)ことが多いです。 - メモリ上の特定のアドレスを持つわけではありません。
- 常に不変です。
static
(静的変数)- プログラムの実行期間中、ずっと存在するメモリ上の特定の場所(アドレス)を確保する変数です。
static mut
とすれば可変にもできますが、複数の場所から同時に書き換えようとすると問題が起きる可能性があるため、安全に使うには特別な注意(unsafe
ブロック)が必要です。- ライフタイム('static)を持ちます。
ちょっと難しい話になりましたが、基本的には const
を使うのが一般的です。
static
は、プログラム全体で共有したい状態を持つ場合や、C言語ライブラリとの連携などで固定のアドレスが必要な場合など、少し特別なケースで使われます。
最初のうちは、「変わらない値にはまず const
を使う」と考えておけば大丈夫でしょう。
Rustの定数を使う上での注意点とルール
定数を使う上で、いくつか守らなければいけないルールがあります。これを守らないと、プログラムがコンパイルエラーになってしまいます。
【注意点1】型アノテーションは省略できない
何度も出てきましたが、定数を宣言するときは、必ず型を明記する必要があります。let
のように型推論に頼ることはできません。
// これはOK const COUNT: i32 = 10; // これはNG!型がないとコンパイルエラー // const COUNT = 10; // error[E0308]: mismatched types
なぜ型指定が必須なのかというと、Rustがプログラムの安全性をとても配慮しているからです。型を明記することで、意図しない型で定数が使われるのを防いでくれます。
【注意点2】コンパイル時に値が決まっている必要がある
定数の値は、プログラムをビルド(コンパイル)する時点で確定していなければなりません。つまり、プログラムを実行しないと分からないような値は使えません。
fn get_runtime_value() -> i32 { // 実行時に何らかの計算をする関数(仮) 5 } // これはOK (リテラル値) const VALUE_A: i32 = 10; // これもOK (他の定数を使った簡単な計算) const VALUE_B: i32 = VALUE_A * 2; // これはNG!関数の呼び出し結果はコンパイル時には分からない // const VALUE_C: i32 = get_runtime_value(); // error[E0015]: calls in constants are limited to constant functions, tuple structs and tuple variants
リテラル(10
や "hello"
のような直接書かれた値)や、他の定数を使った簡単な計算などはOKです。
しかし、関数を呼び出した結果や、実行時の状況によって変わるような値は定数にはできません。「プログラムが動き出す前にはもう決まっている値」だけが定数になれる、と覚えてください。
また、定数名の命名規則(大文字スネークケース)も、エラーにはなりませんが、Rustコミュニティの慣習として守るようにしましょう。
【まとめ】Rustの定数を使いこなそう!
お疲れ様でした!Rustの定数(const
)について、基本的なところから変数(let
)や静的変数(static
)との違い、注意点まで見てきました。
定数を使うと、
- コードの意味が分かりやすくなる(可読性アップ!)
- 値が変わらないことが保証される(安全性アップ!)
- 後からの変更が楽になる(保守性アップ!)
といった良いことがあります。最初は変数との違いに戸惑うかもしれませんが、慣れてくると非常に便利な機能です。
プログラムの中で「この値、絶対変わらないな」と思ったら、ぜひ const
を使ってみてください。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。